/******************************************************************************* * PROJECT: customtreemodel * * AUTHOR: Jonathon Jongsma * * Copyright (c) 2006 Jonathon Jongsma * * License: * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * *******************************************************************************/ #ifndef __CUSTOMTREEMODEL_H #define __CUSTOMTREEMODEL_H #include <gtkmm/treemodel.h> #include <gtkmm/treesortable.h> #include <gtkmm/treedragdest.h> #include <gtkmm/treedragsource.h> #include <gtkmm/treepath.h> #include <string> #include <vector> #include <iterator> /** This is an attempt to make a generic custom tree model. T should be a * container type such as std::vector or std::list (or something that * presents the same interface * * You must override the following functions * \li get_n_columns_vfunc(): to define the number of columns in the model * \li get_column_type_vfunc(): define the type of the specified column * \li get_value_vfunc(): get the value for the specified iter and column */ template <class T> class ContainerTreeModel: public Gtk::TreeModel, public Gtk::TreeDragSource, public Gtk::TreeDragDest, public Gtk::TreeSortable { public: typedef Gtk::TreeModel::iterator iterator; //static Glib::RefPtr<ContainerTreeModel> create(T& container); protected: ContainerTreeModel(T& container); virtual Gtk::TreeModelFlags get_flags_vfunc(void) const; //virtual int get_n_columns_vfunc(void) const; //virtual GType get_column_type_vfunc(int index) const; //virtual void get_value_vfunc(const Gtk::TreeModel::iterator& iter, int column, Glib::ValueBase& value) const; virtual bool iter_next_vfunc(const iterator& iter, iterator& iter_next) const; virtual bool iter_children_vfunc(const iterator& parent, iterator& iter) const; virtual bool iter_has_child_vfunc(const iterator& iter) const; virtual int iter_n_children_vfunc(const iterator& iter) const; virtual int iter_n_root_children_vfunc(void) const; virtual bool iter_nth_child_vfunc(const iterator& parent, int n, iterator& iter) const; virtual bool iter_nth_root_child_vfunc(int n, iterator& iter) const; virtual bool iter_parent_vfunc(const iterator& child, iterator& iter) const; virtual Gtk::TreeModel::Path get_path_vfunc(const iterator& iter) const; virtual bool get_iter_vfunc(const Gtk::TreeModel::Path& path, iterator& iter) const; virtual bool iter_is_valid(const iterator& iter) const; // virtual functions from Gtk::TreeDragSource virtual bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const; // virtual functions from Gtk::TreeDragDest virtual bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data) const; // virtual functions from Gtk::TreeSortable Gtk::TreeModel::ColumnRecord m_columnRecord; T* m_pContainer; int m_stamp; struct GlueItem { GlueItem(const typename T::iterator& i) : iter(i) { } typename T::iterator iter; }; struct GlueList { typedef std::vector<GlueItem*> gluelist_t; ~GlueList() { for (typename gluelist_t::iterator iter = m_items.begin(); iter != m_items.end(); ++iter) { delete *iter; } } gluelist_t m_items; }; mutable GlueList m_glueList; }; template <class T> ContainerTreeModel<T>::ContainerTreeModel(T& container) : //Glib::ObjectBase(typeid(ContainerTreeModel< T >)), // register custom GType //Glib::Object(), // the GType is actually registered here m_pContainer(&container), m_stamp(1) { // some GType stuff to take care of -- I don't completely understand it // -- borrowed from the gtkmm custom treemodel example //GType gtype = G_OBJECT_TYPE(gobj()); // the custom GType created in the Object constructor //Gtk::TreeModel::add_interface(gtype); } template <class T> Gtk::TreeModelFlags ContainerTreeModel<T>::get_flags_vfunc(void) const { // no flags set return Gtk::TreeModelFlags(0); } template <class T> bool ContainerTreeModel<T>::iter_next_vfunc(const iterator& iter, iterator& iter_next) const { if (iter_is_valid(iter)) { iter_next = iterator(); iter_next.set_stamp(m_stamp); // abuse the user_data field of the underlying GtkTreeIter to store // a pointer to an iterator of the underlying container GlueItem* pItem = (GlueItem*) (iter.gobj()->user_data); typename T::iterator stl_iter = pItem->iter; // increment the underlying iterator ++stl_iter; // check whether the next iterator is between the beginning and end of // the container if (m_pContainer->begin() <= stl_iter && stl_iter < m_pContainer->end()) { // store the next row number in the user_data field of the next // iterator GlueItem* pItem = new GlueItem(stl_iter); iter_next.gobj()->user_data = (void*) pItem; m_glueList.m_items.push_back(pItem); return true; // next row ok } else { // row is beyond the size of the container, so we'll return // false when we drop out the end. } } else { iter_next = iterator(); // default iterator is invalid } return false; // no next row } template <class T> bool ContainerTreeModel<T>::iter_children_vfunc(const iterator& parent, iterator& iter) const { // invalid by default. This treemodel has no children so always return // an invalid iterator iter = iterator(); return false; } template <class T> bool ContainerTreeModel<T>::iter_has_child_vfunc(const iterator& iter) const { // this treemodel has no children return false; } template <class T> int ContainerTreeModel<T>::iter_n_children_vfunc(const iterator& iter) const { // no children in this model return 0; } template <class T> int ContainerTreeModel<T>::iter_n_root_children_vfunc(void) const { // all rows are root children return m_pContainer->size(); } template <class T> bool ContainerTreeModel<T>::iter_nth_child_vfunc(const iterator& parent, int n, iterator& iter) const { iter = iterator(); // invalid by default -- we have no children return false; } template <class T> bool ContainerTreeModel<T>::iter_nth_root_child_vfunc(int n, iterator& iter) const { iter = iterator(); // invalid by default. typename T::iterator stl_iter = m_pContainer->begin(); std::advance(stl_iter, n); if (stl_iter < m_pContainer->end()) { iter.set_stamp(m_stamp); // again, abuse the user_data field to store the row number GlueItem* pItem = new GlueItem(stl_iter); iter.gobj()->user_data = (void*) pItem; m_glueList.m_items.push_back(pItem); return true; } return false; } template <class T> bool ContainerTreeModel<T>::iter_parent_vfunc(const iterator& child, iterator& iter) const { // since there are no children in this model, there are also no parents iter = iterator(); // invalid by default return false; } template <class T> Gtk::TreeModel::Path ContainerTreeModel<T>::get_path_vfunc(const iterator& iter) const { Gtk::TreeModel::Path path; GlueItem* pItem = (GlueItem*) iter->gobj()->user_data; path.push_back(std::distance(m_pContainer->begin(), pItem->iter)); return path; } template <class T> bool ContainerTreeModel<T>::get_iter_vfunc(const Gtk::TreeModel::Path& path, iterator& iter) const { iter = iterator(); // path size must be exactly one. if it's zero, there's no path, and if // it's greater than one, it's asking for a child node, which doesn't // exist in this model if (path.size() != 1) { return false; } else if (path[0] < static_cast<int>(m_pContainer->size())) { iter.set_stamp(m_stamp); // again, abuse the user_data field... typename T::iterator stl_iter = m_pContainer->begin(); std::advance(stl_iter, path[0]); GlueItem* pItem = new GlueItem(stl_iter); iter.gobj()->user_data = (void*) pItem; return true; } return false; } template <class T> bool ContainerTreeModel<T>::iter_is_valid(const iterator& iter) const { if (iter.get_stamp() != m_stamp) { return false; } else { // TODO: not sure why we do this but it's done in the gtkmm // example... return Gtk::TreeModel::iter_is_valid(iter); } } template <class T> bool ContainerTreeModel<T>::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const { Gtk::TreeIter iter; get_iter_vfunc(path, iter); if (iter_is_valid(iter)) { return true; } return false; } template <class T> bool ContainerTreeModel<T>::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data) const { Gtk::TreeIter iter; get_iter_vfunc(dest, iter); if (iter_is_valid(iter)) { return true; } return false; } #endif // __CUSTOMTREEMODEL_H