Files
eepp/include/eepp/ui/models/model.hpp
Martín Lucas Golini 9c794fc12f Fix FileSystemModel crash on stale filesystem-model indexes
- 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
2026-08-20 00:24:16 -03:00

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