// This may look like C code, but it's really -*- C++ -*- /* * Copyright (C) 2008 Emweb bv, Herent, Belgium. * * See the LICENSE file for terms of use. */ #ifndef WABSTRACT_ITEM_MODEL_H_ #define WABSTRACT_ITEM_MODEL_H_ #include #include #include #include #include namespace Wt { class WDropEvent; /*! \class WAbstractItemModel Wt/WAbstractItemModel.h Wt/WAbstractItemModel.h * \brief An abstract model for use with %Wt's view classes. * * This abstract model is used by several %Wt view widgets as data models. * * It may model data for both tree-like and table-like view * widgets. Data is therefore organized in a hierarchical structure of * tables, where every item stores data and items in column 0 can be * the parent of a nested table of data. Every data item is uniquely * identified by their row, column and parent index, and items may be * referenced using the helper class WModelIndex. * * Each item may provide data for one or more \link Wt::ItemDataRole * roles\endlink, and indicate options using \link Wt::ItemFlag * flags\endlink. The different roles can be used to model different * aspects of an item (its text value, an icon, style class), or to * hold auxiliary custom information. The flags provide information to * the View on possible interactivity. * * \if cpp * Side::Top level data have an \link WModelIndex::isValid() invalid\endlink * parent WModelIndex. * \endif * \if java * Side::Top level data have a \c null parent WModelIndex. * \endif * * \if cpp * The data itself is of type Wt::any, which can either be * empty, or hold any type of data. Depending on the role however, * view classes may expect certain types of data (e.g. a string for * Wt::ItemDataRole::StyleClass). * * Wt's standard view classes can display (Wt::ItemDataRole::Display) * the following data: * * - strings of type WString or std::string * - WDate, WTime, WDateTime, WLocalDateTime * - standard C++ numeric types (int, double, etc...) * - bool * * The view classes know how to interpret data of these types \link * Wt::asString() as a string\endlink or \link Wt::asNumber() as a * number\endlink. * * \elseif java * * The data itself is of type Object, which can either be \c null, or be * any type of data. Depending on the role however, view classes may * expect certain types of data (e.g. numerical types for charts) or * will convert the data to a string (e.g. for Wt::ItemDataRole::Display). * * \endif * * \if cpp * Conversion between native types and Wt::any is done like this: *
    *
  • Conversion from v (of type Type) to Wt::any a * (for setData() and setHeaderData()) *
     * Wt::any a = Wt::any(v);
     *    
    * For example: *
     * WDate d(1976,6,14);
     * model->setData(row, column, Wt::any(d));
     *    
    * *
  • *
  • Conversion from Wt::any a to v (of type Type) (for data() and headerData()): *
     * Type v = Wt::any_cast<Type>(a);
     *    
    * For example: *
     * WDate d = Wt::any_cast(model->data(row, column));
     *    
    *
  • *
  • Checking if a Wt::any a holds a value:
  • *
     * if (!a.empty()) {
     *   ...
     * }
     *    
    * *
  • Determining the value type of a Wt::any a, for example:
  • *
     * if (a.type() == typeid(double)) {
     *   ...
     * }
     *    
    * *
* * \endif * * To implement a custom model, you need to reimplement the following methods: * - index() and parent() methods that allow one to navigate the model * - columnCount() and rowCount() to specify the top level geometry and the * nested geometry at every item * - data() to return the data for an item * - optionally, headerData() to return row and column header data * - optionally, flags() to indicate data options * * \if cpp * A crucial point in implementing a hierarchical model is to decide * how to reference an index in terms of an internal pointer * (WModelIndex::internalPointer()) or internal id * (WModelIndex::internalId()). Other than the top-level index, which * is special since it is referenced using an \link * WModelIndex::isValid() invalid\endlink index, every index with * children must be identifiable using this number or pointer. For * example, in the WStandardItemModel, the internal pointer points to * the parent WStandardItem. For table models, the internal pointer * plays no role, since only the toplevel index has children. * \elseif java * A crucial point in implementing a hierarchical model is to decide * how to reference an index in terms of an internal pointer * (WModelIndex::internalPointer()). * Other than the top-level index, which is special since it is * referenced using an invalid index, every index with * children must be identifiable using this object. For * example, in the WStandardItemModel, the internal pointer points to * the parent WStandardItem. For table models, the internal pointer * plays no role, since only the toplevel index has children. * \endif * * If you want to support editing of the model, then you need to * indicate this support using a Wt::ItemFlag::Editable flag, and * reimplement setData(). View classes will use the * \link Wt::ItemDataRole::Edit ItemDataRole::Edit\endlink to read and update * the data for the editor. * * When the model's data has been changed, the model must emit the * dataChanged() signal. * * Finally, there is a generic interface for insertion of new data or * removal of data (changing the geometry), although this interface is * not yet used by any View class: * * - insertRows() * - insertColumns() * - removeRows() * - removeColumns() * * Alternatively, you can provide your own API for changing the * model. In either case it is important that you call the * corresponding protected member functions which will emit the * relevant signals so that views can adapt themselves to the new * geometry. * * \ingroup modelview */ class WT_API WAbstractItemModel : public WObject { public: /*! \brief Data map. * * A map of data, indexed by a role. */ #ifndef WT_TARGET_JAVA typedef std::map DataMap; #else typedef std::treemap DataMap; #endif /*! \brief Creates a new data model. */ WAbstractItemModel(); virtual ~WAbstractItemModel(); /*! \brief Returns the number of columns. * * This returns the number of columns at index \p parent. * * \sa rowCount() */ virtual int columnCount(const WModelIndex& parent = WModelIndex()) const = 0; /*! \brief Returns the number of rows. * * This returns the number of rows at index \p parent. * * \sa columnCount() */ virtual int rowCount(const WModelIndex& parent = WModelIndex()) const = 0; // not yet used by views virtual bool canFetchMore(const WModelIndex& parent) const; // not yet used by views virtual void fetchMore(const WModelIndex& parent); /*! \brief Returns the flags for an item. * * The default implementation returns \link Wt::ItemFlag::Selectable * ItemFlag::Selectable\endlink. * * \sa Wt::ItemFlag */ virtual WFlags flags(const WModelIndex& index) const; /*! \brief Returns the flags for a header. * * The default implementation returns no flags set. * * \sa Wt::HeaderFlag */ virtual WFlags headerFlags (int section, Orientation orientation = Orientation::Horizontal) const; /*! \brief Returns if there are children at an index. * * Returns \c true when rowCount(index) > 0 and columnCount(index) > 0. * * \sa rowCount(), columnCount() */ virtual bool hasChildren(const WModelIndex& index) const; /*! \brief Returns the parent for a model index. * * An implementation should use createIndex() to create a model * index that corresponds to the parent of a given index. * * Note that the index itself may be stale (referencing a row/column * within the parent that is outside the model geometry), but its * parent (identified by the WModelIndex::internalPointer()) is * referencing an existing parent. A stale index can only be used * while the model geometry is being updated, i.e. during the * emission of the corresponding * [rows/columns](Being)[Removed/Inserted]() signals. * * \sa index() */ virtual WModelIndex parent(const WModelIndex& index) const = 0; /*! \brief Returns data at a specified model index for the given role. * * You should check the \p role to decide what data to * return. Usually a View class will ask for data for several roles * which affect not only the contents * (Wt::ItemDataRole::Display) but also icons * (Wt::ItemDataRole::Decoration), URLs * (Wt::ItemDataRole::Link), and other visual aspects. If your * item does not specify data for a particular role, it should * simply return a Wt::cpp17::any(). * * \sa flags(), headerData(), setData() */ virtual cpp17::any data(const WModelIndex& index, ItemDataRole role = ItemDataRole::Display) const = 0; /*! \brief Returns all data at a specific index. * * This is a convenience function that returns a map with data * corresponding to all standard roles. * * \sa data() */ virtual DataMap itemData(const WModelIndex& index) const; /*! \brief Returns the row or column header data. * * When \p orientation is \link Wt::Orientation::Horizontal * Orientation::Horizontal\endlink, \p section is a column number, when * \p orientation is \link Wt::Orientation::Vertical Orientation::Vertical\endlink, * \p section is a row number. * * \sa data(), setHeaderData() */ virtual cpp17::any headerData(int section, Orientation orientation = Orientation::Horizontal, ItemDataRole role = ItemDataRole::Display) const; /*! \brief Returns the child index for the given row and column. * * When implementing this method, you can use createIndex() to * create an index that corresponds to the item at \p row and * \p column within \p parent. * * If the location is invalid (out of bounds at the parent), then an * invalid index must be returned. * * \sa parent() */ virtual WModelIndex index(int row, int column, const WModelIndex& parent = WModelIndex()) const = 0; /*! \brief Returns an index list for data items that match. * * Returns an index list of data items that match, starting at * start, and searching further in that column. If flags specifies * \link Wt::MatchFlag::Wrap MatchFlag::Wrap \endlink then the search wraps around * from the start. If hits is not -1, then at most that number of * hits are returned. */ virtual WModelIndexList match(const WModelIndex& start, ItemDataRole role, const cpp17::any& value, int hits = -1, WFlags flags = WFlags(MatchFlag::StartsWith | MatchFlag::Wrap)) const; /*! \brief Returns the data item at the given column and row. * * This is a convenience method, and is equivalent to: * \code * index(row, column, parent).data(role) * \endcode * * \sa index(), data() */ cpp17::any data(int row, int column, ItemDataRole role = ItemDataRole::Display, const WModelIndex& parent = WModelIndex()) const; /*! \brief Returns if an index at the given position is valid * (i.e. falls within the column-row bounds). * * Equivalent to: * \code * return row >= 0 && column >= 0 * && row < rowCount(parent) && column < columnCount(parent); * \endcode * * \sa rowCount(), columnCount() */ virtual bool hasIndex(int row, int column, const WModelIndex& parent = WModelIndex()) const; /*! \brief Inserts one or more columns. * * In models that support column insertion, this inserts \c count * columns, starting at \c column, and returns \c true if the * operation was successful. The new columns are inserted under \p * parent. * * The default implementation returns \c false. * * The model implementation must call beginInsertColumns() and * endInsertColumns() before and after the operation whenever its * geometry is changed by inserting columns. This emits signals for * views to properly react to these changes. * * \sa insertRows(), removeColumns(), beginInsertColumns(), endInsertColumns() */ virtual bool insertColumns(int column, int count, const WModelIndex& parent = WModelIndex()); /*! \brief Inserts one or more rows. * * In models that support row insertion, this inserts \c count rows, * starting at \c row, and returns \c true if the operation was * successful. The new rows are inserted under \p parent. * * If parent had no children, then a single column is added with \c * count rows. * * The default implementation returns \c false. * * The model implementation must call beginInsertRows() and * endInsertRows() before and after the operation whenever its * geometry is changed by inserting rows. This emits signals for * views to properly react to these changes. * * \sa insertColumns(), removeRows(), beginInsertRows(), endInsertRows() */ virtual bool insertRows(int row, int count, const WModelIndex& parent = WModelIndex()); /*! \brief Removes columns. * * Returns \c true if the operation was successful. * * The default implementation returns \c false. * * The model implementation must call beginRemoveColumns() and * endRemoveColumns() before and after the operation whenever its * geometry is changed by removing columns. This emits signals for * views to properly react to these changes. * * \sa removeRows(), insertColumns(), beginRemoveColumns(), endRemoveColumns() */ virtual bool removeColumns(int column, int count, const WModelIndex& parent = WModelIndex()); /*! \brief Removes rows. * * Returns \c true if the operation was successful. * * The default implementation returns \c false. * * The model implementation must call beginRemoveRows() and * endRemoveRows() before and after the operation whenever its * geometry is changed by removing rows. This emits signals for * views to properly react to these changes. * * \sa removeColumns(), insertRows(), beginRemoveRows(), endRemoveRows() */ virtual bool removeRows(int row, int count, const WModelIndex& parent = WModelIndex()); /*! \brief Sets data at the given model index. * * Returns \c true if the operation was successful. * * The default implementation returns \c false. * * The model implementation must emit the dataChanged() signal after * data was changed. * * \sa data() */ virtual bool setData(const WModelIndex& index, const cpp17::any& value, ItemDataRole role = ItemDataRole::Edit); /*! \brief Sets data at the given model index. * * This is a convenience function that sets data for all roles at once. * * \sa setData() */ virtual bool setItemData(const WModelIndex& index, const DataMap& values); /*! \brief Sets header data for a column or row. * * Returns \c true if the operation was successful. * * \sa headerData() */ virtual bool setHeaderData(int section, Orientation orientation, const cpp17::any& value, ItemDataRole role = ItemDataRole::Edit); /*! \brief Sets column header data. * * Returns \c true if the operation was successful. * * \sa setHeaderData(int, Orientation, const cpp17::any&, int) */ bool setHeaderData(int section, const cpp17::any& value); /*! \brief Sorts the model according to a particular column. * * If the model supports sorting, then it should emit the * layoutAboutToBeChanged() signal, rearrange its items, and * afterwards emit the layoutChanged() signal. * * \sa layoutAboutToBeChanged(), layoutChanged() */ virtual void sort(int column, SortOrder order = SortOrder::Ascending); /*! \brief Expands a column. * * Expands a column. This may only be called by a view when the * Wt::HeaderFlag::ColumnIsCollapsed flag is set. * * The default implementation does nothing. * * \sa WAggregateProxyModel */ virtual void expandColumn(int column); /*! \brief Collapses a column. * * Collapses a column. This may only be called by a view when the * Wt::HeaderFlag::ColumnIsExpandedLeft or Wt::HeaderFlag::ColumnIsExpandedRight flag is set. * * The default implementation does nothing. * * \sa WAggregateProxyModel */ virtual void collapseColumn(int column); /*! \brief Converts a model index to a raw pointer that remains valid * while the model's layout is changed. * * Use this method to temporarily save model indexes while the model's * layout is changed by for example a sorting operation. * * The default implementation returns \c 0, which indicates that the * index cannot be converted to a raw pointer. If you reimplement * this method, you also need to reimplemnt fromRawIndex(). * * \sa layoutAboutToBeChanged, sort(), fromRawIndex() */ virtual void *toRawIndex(const WModelIndex& index) const; /*! \brief Converts a raw pointer to a model index. * * Use this method to create model index from temporary raw * pointers. It is the reciproce method of toRawIndex(). * * You can return an invalid modelindex if the rawIndex no longer points * to a valid item because of the layout change. * * \sa toRawIndex() */ virtual WModelIndex fromRawIndex(void *rawIndex) const; /*! \brief Returns a mime-type for dragging a set of indexes. * * This method returns a mime-type that describes dragging of a selection * of items. * * The drop event will indicate a \link WItemSelectionModel * selection model\endlink for this abstract item model as \link * WDropEvent::source() source\endlink. * * The default implementation returns a mime-type for generic * drag&drop support between abstract item models. * * \sa acceptDropMimeTypes() */ virtual std::string mimeType() const; /*! \brief Returns a list of mime-types that could be accepted for a * drop event. * * The default implementation only accepts drag&drop support between * abstract item models. * * \sa mimeType() */ virtual std::vector acceptDropMimeTypes() const; /*! \brief Handles a drop event. * * The default implementation only handles generic drag&drop between * abstract item models. Source item data is copied (but not the * source item's flags). * * The location in the model is indicated by the \p row and * \p column within the \p parent index. If \p row is * -1, then the item is appended to the \p parent. Otherwise, * the item is inserted at or copied over the indicated item (and * subsequent rows). When \p action is a \link Wt::DropAction::Move * DropAction::Move\endlink, the original items are deleted from the * source model. * * You may want to reimplement this method if you want to handle * other mime-type data, or if you want to refine how the drop event * of an item selection must be interpreted. * * \note Currently, only row selections are handled by the default * implementation. * * \sa mimeType(), WItemSelectionModel */ virtual void dropEvent(const WDropEvent& e, DropAction action, int row, int column, const WModelIndex& parent); /*! \brief Inserts one column. * * This is a convenience method that adds a single column, and is * equivalent to: * \code * insertColumns(column, 1, parent); * \endcode * * Returns \c true if the operation was successful. * * \sa insertColumns() */ bool insertColumn(int column, const WModelIndex& parent = WModelIndex()); /*! \brief Inserts one row. * * This is a convenience method that adds a single row, and is * equivalent to: * \code * insertRows(row, 1, parent); * \endcode * * Returns \c true if the operation was successful. * * \sa insertRows() */ bool insertRow(int row, const WModelIndex& parent = WModelIndex()); /*! \brief Removes one column. * * This is a convenience method that removes a single column, and is * equivalent to: * \code * removeColumns(column, 1, parent); * \endcode * * Returns \c true if the operation was successful. * * \sa removeColumns() */ bool removeColumn(int column, const WModelIndex& parent = WModelIndex()); /*! \brief Removes one row. * * This is a convenience method that removes a single row, and is * equivalent to: * \code * removeRows(row, 1, parent); * \endcode * * Returns \c true if the operation was successful. * * \sa removeRows() */ bool removeRow(int row, const WModelIndex& parent = WModelIndex()); /*! \brief Sets data at the given row and column. * * This is a convience method, and is equivalent to: * \code * setData(index(row, column, parent), value, role); * \endcode * * Returns \c true if the operation was successful. * * \sa setData(), index() */ bool setData(int row, int column, const cpp17::any& value, ItemDataRole role = ItemDataRole::Edit, const WModelIndex& parent = WModelIndex()); /*! \brief %Signal emitted before a number of columns will be inserted. * * The first argument is the parent index. The two integer arguments * are the column numbers that the first and last column will have when * inserted. * * \sa columnsInserted(), beginInsertColumns() */ virtual Signal& columnsAboutToBeInserted() { return columnsAboutToBeInserted_; } /*! \brief %Signal emitted before a number of columns will be removed. * * The first argument is the parent index. The two integer arguments * are the column numbers of the first and last column that will be * removed. * * \sa columnsRemoved(), beginRemoveColumns() */ virtual Signal& columnsAboutToBeRemoved() { return columnsAboutToBeRemoved_; } /*! \brief %Signal emitted after a number of columns were inserted. * * The first argument is the parent index. The two integer arguments * are the column numbers of the first and last column that were * inserted. * * \sa columnsAboutToBeInserted(), endInsertColumns() */ virtual Signal& columnsInserted() { return columnsInserted_; } /*! \brief %Signal emitted after a number of columns were removed. * * The first argument is the parent index. The two integer arguments * are the column numbers of the first and last column that were removed. * * \sa columnsAboutToBeRemoved(), endRemoveColumns() */ virtual Signal& columnsRemoved() { return columnsRemoved_; } /*! \brief %Signal emitted before a number of rows will be inserted. * * The first argument is the parent index. The two integer arguments * are the row numbers that the first and last row will have when * inserted. * * \sa rowsInserted(), beginInsertRows() */ virtual Signal& rowsAboutToBeInserted() { return rowsAboutToBeInserted_; } /*! \brief %Signal emitted before a number of rows will be removed. * * The first argument is the parent index. The two integer arguments * are the row numbers of the first and last row that will be * removed. * * \sa rowsRemoved(), beginRemoveRows() */ virtual Signal& rowsAboutToBeRemoved() { return rowsAboutToBeRemoved_; } /*! \brief %Signal emitted after a number of rows were inserted. * * The first argument is the parent index. The two integer arguments * are the row numbers of the first and last row that were inserted. * * \sa rowsAboutToBeInserted(), endInsertRows() */ virtual Signal& rowsInserted() { return rowsInserted_; } /*! \brief %Signal emitted after a number of rows were removed. * * The first argument is the parent index. The two integer arguments * are the row numbers of the first and last row that were removed. * * \sa rowsAboutToBeRemoved(), endRemoveRows() */ virtual Signal& rowsRemoved() { return rowsRemoved_; } /*! \brief %Signal emitted when some data was changed. * * The two arguments are the model indexes of the top-left and bottom-right * data items that span the rectangle of changed data items. * * \sa setData() */ virtual Signal& dataChanged() { return dataChanged_; } /*! \brief %Signal emitted when some header data was changed. * * The first argument indicates the orientation of the header, and * the two integer arguments are the row or column numbers of the * first and last header item of which the value was changed. * * \sa setHeaderData() */ virtual Signal& headerDataChanged() { return headerDataChanged_; } /*! \brief %Signal emitted when the layout is about to be changed. * * A layout change may reorder or add/remove rows in the model, but * columns are preserved. Model indexes are invalidated by a layout * change, but indexes may be ported across a layout change by using * the toRawIndex() and fromRawIndex() methods. * * \sa layoutChanged(), toRawIndex(), fromRawIndex() */ virtual Signal<>& layoutAboutToBeChanged() { return layoutAboutToBeChanged_; } /*! \brief %Signal emitted when the layout is changed. * * \sa layoutAboutToBeChanged() */ virtual Signal<>& layoutChanged() { return layoutChanged_; } /*! \brief %Signal emitted when the model was reset. * * A model reset invalidates all existing data, and the model may change * its entire geometry (column count, row count). * * \sa reset() */ virtual Signal<>& modelReset() { return modelReset_; } protected: /*! \brief Resets the model and invalidate any data. * * Informs any attached view that all data in the model was invalidated, * and the model's data should be reread. * * This causes the modelReset() signal to be emitted. */ void reset(); /*! \brief Creates a model index for the given row and column. * * Use this method to create a model index. \p ptr is an internal * pointer that may be used to identify the parent of the * corresponding item. For a flat table model, \p ptr can thus * always be 0. * * \sa WModelIndex::internalPointer() */ WModelIndex createIndex(int row, int column, void *ptr) const; /*! \brief Creates a model index for the given row and column. * * Use this method to create a model index. \p id is an internal id * that may be used to identify the parent of the * corresponding item. For a flat table model, \p ptr can thus * always be 0. * * \sa WModelIndex::internalId() */ WModelIndex createIndex(int row, int column, ::uint64_t id) const; /*! \brief Method to be called before inserting columns. * * If your model supports insertion of columns, then you should call * this method before inserting one or more columns, and * endInsertColumns() afterwards. These methods emit the necessary * signals to allow view classes to update themselves. * * \sa endInsertColumns(), insertColumns(), columnsAboutToBeInserted */ void beginInsertColumns(const WModelIndex& parent, int first, int last); /*! \brief Method to be called before inserting rows. * * If your model supports insertion of rows, then you should call * this method before inserting one or more rows, and * endInsertRows() afterwards. These methods emit the necessary * signals to allow view classes to update themselves. * * \sa endInsertRows(), insertRows(), rowsAboutToBeInserted */ void beginInsertRows(const WModelIndex& parent, int first, int last); /*! \brief Method to be called before removing columns. * * If your model supports removal of columns, then you should call * this method before removing one or more columns, and * endRemoveColumns() afterwards. These methods emit the necessary * signals to allow view classes to update themselves. * * \sa endRemoveColumns(), removeColumns(), columnsAboutToBeRemoved */ void beginRemoveColumns(const WModelIndex& parent, int first, int last); /*! \brief Method to be called before removing rows. * * If your model supports removal of rows, then you should call this * method before removing one or more rows, and endRemoveRows() * afterwards. These methods emit the necessary signals to allow * view classes to update themselves. * * \sa endRemoveRows(), removeRows(), rowsAboutToBeRemoved */ void beginRemoveRows(const WModelIndex& parent, int first, int last); /*! \brief Method to be called after inserting columns. * * \sa beginInsertColumns() */ void endInsertColumns(); /*! \brief Method to be called after inserting rows. * * \sa beginInsertRows() */ void endInsertRows(); /*! \brief Method to be called after removing columns. * * \sa beginRemoveColumns() */ void endRemoveColumns(); /*! \brief Method to be called after removing rows. * * \sa beginRemoveRows() */ void endRemoveRows(); private: int first_, last_; WModelIndex parent_; Signal columnsAboutToBeInserted_; Signal columnsAboutToBeRemoved_; Signal columnsInserted_; Signal columnsRemoved_; Signal rowsAboutToBeInserted_; Signal rowsAboutToBeRemoved_; Signal rowsInserted_; Signal rowsRemoved_; Signal dataChanged_; Signal headerDataChanged_; Signal<> layoutAboutToBeChanged_; Signal<> layoutChanged_; Signal<> modelReset_; static void copyData(const WAbstractItemModel *source, const WModelIndex& sIndex, WAbstractItemModel *destination, const WModelIndex& dIndex); friend class WAbstractProxyModel; }; } #endif // WABSTRACT_ITEM_MODEL_H_