mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
- dispatch filesystem events on the main thread in FileSystemListener, preserving model, tree, document, and callback ordering - add address + generation node identity to close the allocator-reuse hole - return null/empty results for stale indexes instead of root data - assert the main-thread contract and token-guard queued listener actions - add attached-view and stale-selection crash regression tests Fixes crash reported in discussion SpartanJ/ecode#952
243 lines
7.8 KiB
C++
243 lines
7.8 KiB
C++
#ifndef EE_UI_MODELS_MODEL_HPP
|
|
#define EE_UI_MODELS_MODEL_HPP
|
|
|
|
#include <eepp/system/lock.hpp>
|
|
#include <eepp/system/mutex.hpp>
|
|
#include <eepp/ui/models/modelindex.hpp>
|
|
#include <eepp/ui/models/modelrole.hpp>
|
|
#include <eepp/ui/models/variant.hpp>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <stack>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
|
|
using namespace EE::Graphics;
|
|
using namespace EE::Math;
|
|
|
|
namespace EE { namespace UI { namespace Abstract {
|
|
class UIAbstractView;
|
|
}}} // namespace EE::UI::Abstract
|
|
|
|
using namespace EE::UI::Abstract;
|
|
|
|
namespace EE { namespace UI { namespace Models {
|
|
|
|
class PersistentHandle;
|
|
|
|
enum class SortOrder { None, Ascending, Descending };
|
|
|
|
class EE_API Model {
|
|
public:
|
|
using ModelStyler = std::function<Variant( const ModelIndex&, const void* data )>;
|
|
|
|
class EE_API Client {
|
|
public:
|
|
virtual ~Client() {}
|
|
|
|
virtual void onModelUpdated( unsigned flags ) = 0;
|
|
|
|
virtual void modelDidInsertRows( [[maybe_unused]] ModelIndex const& parent,
|
|
[[maybe_unused]] int first, [[maybe_unused]] int last ) {}
|
|
virtual void modelDidInsertColumns( [[maybe_unused]] ModelIndex const& parent,
|
|
[[maybe_unused]] int first,
|
|
[[maybe_unused]] int last ) {}
|
|
virtual void modelDidMoveRows( [[maybe_unused]] ModelIndex const& source_parent,
|
|
[[maybe_unused]] int first, [[maybe_unused]] int last,
|
|
[[maybe_unused]] ModelIndex const& target_parent,
|
|
[[maybe_unused]] int target_index ) {}
|
|
virtual void modeldidMoveColumns( [[maybe_unused]] ModelIndex const& source_parent,
|
|
[[maybe_unused]] int first, [[maybe_unused]] int last,
|
|
[[maybe_unused]] ModelIndex const& target_parent,
|
|
[[maybe_unused]] int target_index ) {}
|
|
virtual void modelDidDeleteRows( [[maybe_unused]] ModelIndex const& parent,
|
|
[[maybe_unused]] int first, [[maybe_unused]] int last ) {}
|
|
virtual void modelDidDeleteColumns( [[maybe_unused]] ModelIndex const& parent,
|
|
[[maybe_unused]] int first,
|
|
[[maybe_unused]] int last ) {}
|
|
};
|
|
|
|
enum UpdateFlag {
|
|
DontInvalidateIndexes = 0,
|
|
InvalidateAllIndexes = 1 << 0,
|
|
};
|
|
|
|
virtual ~Model() {};
|
|
|
|
virtual bool hasChildren( const ModelIndex& modelIndex = ModelIndex() ) const {
|
|
return rowCount( modelIndex ) > 0;
|
|
}
|
|
|
|
virtual size_t rowCount( const ModelIndex& = ModelIndex() ) const = 0;
|
|
|
|
virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const = 0;
|
|
|
|
virtual std::string columnName( const size_t& /*column*/ ) const { return {}; }
|
|
|
|
virtual std::string rowName( const size_t& /*row*/ ) const { return {}; }
|
|
|
|
virtual Variant data( const ModelIndex&, ModelRole = ModelRole::Display ) const = 0;
|
|
|
|
virtual void update() { onModelUpdate(); }
|
|
|
|
virtual ModelIndex parentIndex( const ModelIndex& ) const { return {}; }
|
|
|
|
virtual ModelIndex index( int row, int column = 0, const ModelIndex& = ModelIndex() ) const {
|
|
return createIndex( row, column );
|
|
}
|
|
|
|
virtual ModelIndex sibling( int row, int column, const ModelIndex& parent ) const;
|
|
|
|
virtual void setData( const ModelIndex&, const Variant& ) {}
|
|
|
|
virtual size_t treeColumn() const { return 0; }
|
|
|
|
virtual bool acceptsDrag( const ModelIndex&, const std::string& dataType );
|
|
|
|
virtual bool isColumnSortable( const size_t& /*columnIndex*/ ) const { return true; }
|
|
|
|
virtual std::string dragDataType() const { return {}; }
|
|
|
|
virtual bool isEditable( const ModelIndex& ) const { return false; }
|
|
|
|
virtual bool isValid( const ModelIndex& index ) const {
|
|
auto parentIndex = this->parentIndex( index );
|
|
return index.row() >= 0 && index.row() < (Int64)rowCount( parentIndex ) &&
|
|
index.column() >= 0 && index.column() < (Int64)columnCount( parentIndex );
|
|
}
|
|
|
|
virtual int keyColumn() const { return -1; }
|
|
|
|
virtual SortOrder sortOrder() const { return SortOrder::None; }
|
|
|
|
virtual bool isSortable() { return false; }
|
|
|
|
virtual void sort( const size_t& /*column*/, const SortOrder& /*order*/ ) {}
|
|
|
|
virtual bool classModelRoleEnabled() { return false; }
|
|
|
|
virtual bool tooltipModelRoleEnabled() { return false; }
|
|
|
|
void registerView( UIAbstractView* );
|
|
|
|
void unregisterView( UIAbstractView* );
|
|
|
|
void registerClient( Client* );
|
|
|
|
void unregisterClient( Client* );
|
|
|
|
void refreshView() const;
|
|
|
|
void setOnUpdate( const std::function<void()>& onUpdate );
|
|
|
|
virtual void invalidate( unsigned int flags = Model::UpdateFlag::InvalidateAllIndexes );
|
|
|
|
std::weak_ptr<PersistentHandle> registerPersistentIndex( ModelIndex const& );
|
|
|
|
void beginInsertRows( ModelIndex const& parent, int first, int last );
|
|
void beginInsertColumns( ModelIndex const& parent, int first, int last );
|
|
void beginMoveRows( ModelIndex const& sourceParent, int first, int last,
|
|
ModelIndex const& targetParent, int target_index );
|
|
void beginMoveColumns( ModelIndex const& sourceParent, int first, int last,
|
|
ModelIndex const& targetParent, int target_index );
|
|
bool beginDeleteRows( ModelIndex const& parent, int first, int last );
|
|
bool beginDeleteColumns( ModelIndex const& parent, int first, int last );
|
|
|
|
void endInsertRows();
|
|
void endInsertColumns();
|
|
void endMoveRows();
|
|
void endMoveColumns();
|
|
void endDeleteRows();
|
|
void endDeleteColumns();
|
|
|
|
Mutex& resourceMutex();
|
|
|
|
void acquireResourceMutex();
|
|
|
|
void releaseResourceMutex();
|
|
|
|
Uint32 subscribeModelStyler( const ModelStyler& styler );
|
|
|
|
void unsubscribeModelStyler( Uint32 id );
|
|
|
|
protected:
|
|
Model() {};
|
|
|
|
void forEachView( std::function<void( UIAbstractView* )> ) const;
|
|
|
|
void onModelUpdate( unsigned flags = UpdateFlag::InvalidateAllIndexes );
|
|
|
|
ModelIndex createIndex( int row, int column, const void* data = nullptr,
|
|
const Int64& internalId = 0 ) const;
|
|
|
|
enum class OperationType { Invalid = 0, Insert, Move, Delete, Reset };
|
|
enum class Direction { Row, Column };
|
|
|
|
struct Operation {
|
|
struct PersistentMove {
|
|
ModelIndex index;
|
|
ModelIndex targetParent;
|
|
int targetDimension;
|
|
};
|
|
|
|
OperationType type{ OperationType::Invalid };
|
|
Direction direction{ Direction::Row };
|
|
ModelIndex sourceParent;
|
|
int first{ 0 };
|
|
int last{ 0 };
|
|
ModelIndex targetParent;
|
|
int target{ 0 };
|
|
std::vector<PersistentMove> persistentMoves;
|
|
|
|
Operation( OperationType type ) : type( type ) {}
|
|
|
|
Operation( OperationType type, Direction direction, ModelIndex const& parent, int first,
|
|
int last ) :
|
|
type( type ),
|
|
direction( direction ),
|
|
sourceParent( parent ),
|
|
first( first ),
|
|
last( last ) {}
|
|
|
|
Operation( OperationType type, Direction direction, ModelIndex const& sourceParent,
|
|
int first, int last, ModelIndex const& targetParent, int target ) :
|
|
type( type ),
|
|
direction( direction ),
|
|
sourceParent( sourceParent ),
|
|
first( first ),
|
|
last( last ),
|
|
targetParent( targetParent ),
|
|
target( target ) {}
|
|
};
|
|
|
|
void handleInsert( Operation const& );
|
|
void handleMove( Operation const& );
|
|
void handleDelete( Operation const& );
|
|
void saveMovedIndices( Operation& );
|
|
void notifyIndexDeleted( const void* internalData ) const;
|
|
void applyPersistentIndexChanges(
|
|
const std::vector<std::pair<ModelIndex, ModelIndex>>& indexChanges );
|
|
|
|
template <bool IsRow> void saveDeletedIndices( ModelIndex const& parent, int first, int last );
|
|
|
|
Variant stylizeModel( const ModelIndex& index, const void* data = nullptr ) const;
|
|
|
|
UnorderedMap<ModelIndex, std::shared_ptr<PersistentHandle>> mPersistentHandles;
|
|
std::stack<Operation> mOperationStack;
|
|
// NOTE: We need to save which indices have been deleted before the delete
|
|
// actually happens, because we can't figure out which persistent handles
|
|
// belong to us in end_delete_rows/columns (because accessing the parents of
|
|
// the indices might be impossible).
|
|
std::stack<std::vector<ModelIndex>> mDeletedIndicesStack;
|
|
std::unordered_set<UIAbstractView*> mViews;
|
|
std::unordered_set<Client*> mClients;
|
|
std::function<void()> mOnUpdate;
|
|
mutable Mutex mResourceLock;
|
|
Uint32 mLastStylerId{ 0 };
|
|
std::unordered_map<Uint32, ModelStyler> mStylers;
|
|
};
|
|
|
|
}}} // namespace EE::UI::Models
|
|
|
|
#endif // EE_UI_MODELS_MODEL_HPP
|