1/* Copyright 2002 The gtkmm Development Team 2 * 3 * This library is free software; you can redistribute it and/or 4 * modify it under the terms of the GNU Lesser General Public 5 * License as published by the Free Software Foundation; either 6 * version 2.1 of the License, or (at your option) any later version. 7 * 8 * This library is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 * Lesser General Public License for more details. 12 * 13 * You should have received a copy of the GNU Lesser General Public 14 * License along with this library; if not, write to the Free Software 15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 */ 17 18#include <glibmm/vectorutils.h> 19 20#include <gtkmm/treeviewcolumn.h> 21#include <gtkmm/treeview_private.h> 22#include <gtkmm/treemodel.h> 23#include <gtkmm/treemodelfilter.h> 24#include <gtkmm/treemodelsort.h> 25#include <gtkmm/entry.h> 26#include <gtk/gtk.h> 27 28namespace 29{ 30 31//This target name is used in the GTK+ implementation: 32static const char treeview_target_row[] = "GTK_TREE_MODEL_ROW"; 33 34} // anonymous namespace 35 36 37static void SignalProxy_Mapping_gtk_callback(GtkTreeView* tree_view, GtkTreePath* path, void* data) 38{ 39 auto the_slot = static_cast<Gtk::TreeView::SlotMapping*>(data); 40 41 try 42 { 43 (*the_slot)(Glib::wrap(tree_view), Gtk::TreePath(path, true)); 44 } 45 catch(...) 46 { 47 Glib::exception_handlers_invoke(); 48 } 49} 50 51static gboolean SignalProxy_SearchEqual_gtk_callback(GtkTreeModel* model, int column, const char* key, 52 GtkTreeIter* iter, void* data) 53{ 54 auto the_slot = static_cast<Gtk::TreeView::SlotSearchEqual*>(data); 55 56 try 57 { 58 return (*the_slot)(Glib::wrap(model, true), column, key, Gtk::TreeIter(model, iter)); 59 } 60 catch(...) 61 { 62 Glib::exception_handlers_invoke(); 63 } 64 65 return 0; // arbitrary value 66} 67 68static void SignalProxy_SearchEqual_gtk_callback_destroy(void* data) 69{ 70 delete static_cast<Gtk::TreeView::SlotSearchEqual*>(data); 71} 72 73static gboolean SignalProxy_ColumnDrop_gtk_callback(GtkTreeView* tree_view, GtkTreeViewColumn* column, 74 GtkTreeViewColumn* prev_column, 75 GtkTreeViewColumn* next_column, void* data) 76{ 77 auto the_slot = static_cast<Gtk::TreeView::SlotColumnDrop*>(data); 78 79 try 80 { 81 return (*the_slot)(Glib::wrap(tree_view), Glib::wrap(column), 82 Glib::wrap(prev_column), Glib::wrap(next_column)); 83 } 84 catch(...) 85 { 86 Glib::exception_handlers_invoke(); 87 } 88 89 return 0; // arbitrary value 90} 91 92static void SignalProxy_ColumnDrop_gtk_callback_destroy(void* data) 93{ 94 delete static_cast<Gtk::TreeView::SlotColumnDrop*>(data); 95} 96 97 98static void SignalProxy_SearchPosition_gtk_callback(GtkTreeView* /* tree_view */, GtkWidget* search_dialog, gpointer user_data) 99{ 100 auto the_slot = static_cast<Gtk::TreeView::SlotSearchPosition*>(user_data); 101 102 try 103 { 104 (*the_slot)(Glib::wrap(search_dialog)); 105 } 106 catch(...) 107 { 108 Glib::exception_handlers_invoke(); 109 } 110} 111 112static void SignalProxy_SearchPosition_gtk_callback_destroy(void* data) 113{ 114 delete static_cast<Gtk::TreeView::SlotSearchPosition*>(data); 115} 116 117namespace Gtk 118{ 119 120int TreeView::insert_column_with_data_func(int position, const Glib::ustring& title, CellRenderer& cell, const SlotTreeCellData& slot) 121{ 122 //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter. 123 //It will be deleted when TreeView_Private::SignalProxy_CellData_gtk_callback_destroy() is called. 124 auto slot_copy = new SlotTreeCellData(slot); 125 126 return gtk_tree_view_insert_column_with_data_func( 127 gobj(), position, title.c_str(), cell.gobj(), 128 &TreeView_Private::SignalProxy_CellData_gtk_callback, slot_copy, 129 &TreeView_Private::SignalProxy_CellData_gtk_callback_destroy); 130} 131 132void TreeView::set_cursor(const TreeModel::Path& path) 133{ 134 gtk_tree_view_set_cursor(gobj(), const_cast<GtkTreePath*>(path.gobj()), nullptr, false); 135} 136 137void TreeView::get_cursor(TreeModel::Path& path, TreeViewColumn*& focus_column) 138{ 139 GtkTreePath* pTreePath = nullptr; 140 GtkTreeViewColumn* pTreeViewColumn = nullptr; 141 gtk_tree_view_get_cursor(gobj(), &pTreePath, &pTreeViewColumn); 142 143 path = TreeModel::Path(pTreePath, false); /* Use the existing underlying GtkTreePath instance without copying and freeing, because gtk_tree_view_get_cursor() gives us ownernship. */ 144 focus_column = Glib::wrap(pTreeViewColumn); 145} 146 147 148void TreeView::enable_model_drag_source(const std::vector<TargetEntry>& targets, 149 Gdk::ModifierType start_button_mask, 150 Gdk::DragAction actions) 151{ 152 gtk_tree_view_enable_model_drag_source( 153 gobj(), (GdkModifierType) start_button_mask, 154 Glib::ArrayHandler<TargetEntry, TargetEntryTraits>::vector_to_array(targets).data(), 155 targets.size(), (GdkDragAction) actions); 156} 157 158void TreeView::enable_model_drag_source(Gdk::ModifierType start_button_mask, Gdk::DragAction actions) 159{ 160 std::vector<TargetEntry> targets (1, TargetEntry (treeview_target_row)); 161 162 enable_model_drag_source(targets, start_button_mask, actions); 163} 164 165void TreeView::enable_model_drag_dest(const std::vector<TargetEntry>& targets, Gdk::DragAction actions) 166{ 167 gtk_tree_view_enable_model_drag_dest( 168 gobj(), Glib::ArrayHandler<TargetEntry, TargetEntryTraits>::vector_to_array(targets).data(), 169 targets.size(), (GdkDragAction) actions); 170} 171 172void TreeView::enable_model_drag_dest(Gdk::DragAction actions) 173{ 174 std::vector<TargetEntry> targets (1, TargetEntry (treeview_target_row)); 175 176 enable_model_drag_dest(targets, actions); 177} 178 179bool TreeView::get_path_at_pos(int x, int y, TreeModel::Path& path, TreeViewColumn*& column, int& cell_x, int& cell_y) const 180{ 181 GtkTreePath* pTreePath = nullptr; 182 GtkTreeViewColumn* pTreeViewColumn = nullptr; 183 const bool result = gtk_tree_view_get_path_at_pos(const_cast<GtkTreeView*>(gobj()), x, y, &pTreePath, &pTreeViewColumn, &cell_x, &cell_y); 184 185 path = TreeModel::Path(pTreePath, false /* don't take a copy, because the gtk_tree_view_get_path_at_pos() docs say that we must free the path */ ); 186 column = Glib::wrap(pTreeViewColumn); 187 return result; 188} 189 190bool TreeView::get_path_at_pos(int x, int y, TreeModel::Path& path) const 191{ 192 GtkTreePath* pTreePath = nullptr; 193 const bool result = gtk_tree_view_get_path_at_pos(const_cast<GtkTreeView*>(gobj()), x, y, &pTreePath, nullptr, nullptr, nullptr); 194 195 path = TreeModel::Path(pTreePath, false /* don't take a copy, because the gtk_tree_view_get_path_at_pos() docs say that we must free the path */ ); 196 return result; 197} 198 199int TreeView::insert_column(const Glib::ustring& title, CellRenderer& cell, int position) 200{ 201 return gtk_tree_view_insert_column_with_attributes( 202 gobj(), position, const_cast<char*>(title.c_str()), cell.gobj(), nullptr); 203} 204 205int TreeView::append_column(const Glib::ustring& title, CellRenderer& cell) 206{ 207 return insert_column(title, cell, -1 /* at the end */); 208} 209 210void TreeView::get_drag_dest_row(TreeModel::Path& path, TreeViewDropPosition& pos) const 211{ 212 GtkTreePath* pTreePath = nullptr; 213 gtk_tree_view_get_drag_dest_row(const_cast<GtkTreeView*>(gobj()), &pTreePath, (GtkTreeViewDropPosition*) &pos); 214 path = TreeModel::Path(pTreePath, true); //true = take_copy. 215} 216 217bool TreeView::get_dest_row_at_pos(int drag_x, int drag_y, TreeModel::Path& path, TreeViewDropPosition& pos) const 218{ 219 GtkTreePath* pTreePath = nullptr; 220 const bool bResult = gtk_tree_view_get_dest_row_at_pos( 221 const_cast<GtkTreeView*>(gobj()), drag_x, drag_y, &pTreePath, (GtkTreeViewDropPosition*) &pos); 222 223 path = TreeModel::Path(pTreePath, true); //true = take_copy. 224 return bResult; 225} 226 227void TreeView::map_expanded_rows(const SlotMapping& slot) 228{ 229 gtk_tree_view_map_expanded_rows(gobj(), &SignalProxy_Mapping_gtk_callback, const_cast<SlotMapping*>(&slot)); 230} 231 232void TreeView::set_search_equal_func(const SlotSearchEqual& slot) 233{ 234 //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter. 235 //It will be deleted when SignalProxy_SearchEqual_gtk_callback_destroy() is called. 236 auto slot_copy = new SlotSearchEqual(slot); 237 238 gtk_tree_view_set_search_equal_func(gobj(), 239 &SignalProxy_SearchEqual_gtk_callback, slot_copy, 240 &SignalProxy_SearchEqual_gtk_callback_destroy); 241} 242 243void TreeView::set_column_drag_function(const SlotColumnDrop& slot) 244{ 245 //Create a copt of the slot. A pointer to this will be passed through the callback's data parameter. 246 //It will be deleted when SignalProxy_ColumnDrop_gtk_callback_destroy() is called. 247 auto slot_copy = new SlotColumnDrop(slot); 248 249 gtk_tree_view_set_column_drag_function(gobj(), 250 &SignalProxy_ColumnDrop_gtk_callback, slot_copy, 251 &SignalProxy_ColumnDrop_gtk_callback_destroy); 252 253} 254 255void TreeView::unset_column_drag_function() 256{ 257 gtk_tree_view_set_column_drag_function(gobj(), 258 nullptr, nullptr, nullptr); /* See GTK+ docs about the nullptrs. */ 259} 260 261void TreeView::scroll_to_cell(const TreeModel::Path& path, TreeViewColumn& column, float row_align, float col_align) 262{ 263 gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), column.gobj(), TRUE, row_align, col_align); 264} 265 266void TreeView::scroll_to_cell(const TreeModel::Path& path, TreeViewColumn& column) 267{ 268 gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), column.gobj(), FALSE, 0.0, 0.0); 269} 270 271void TreeView::scroll_to_row(const TreeModel::Path& path, float row_align) 272{ 273 gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), nullptr, TRUE, row_align, 0.0); 274} 275 276void TreeView::scroll_to_row(const TreeModel::Path& path) 277{ 278 gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), nullptr, FALSE, 0.0, 0.0); 279} 280 281void TreeView::scroll_to_column(TreeViewColumn& column, float col_align) 282{ 283 gtk_tree_view_scroll_to_cell(gobj(), nullptr, column.gobj(), TRUE, 0.0, col_align); 284} 285 286void TreeView::scroll_to_column(TreeViewColumn& column) 287{ 288 gtk_tree_view_scroll_to_cell(gobj(), nullptr, column.gobj(), FALSE, 0.0, 0.0); 289} 290 291void TreeView::remove_all_columns() 292{ 293 //This method is not in GTK+, but it seems useful. 294 295 //Remove all View columns: 296 std::vector<Gtk::TreeView::Column*> vecViewColumns (get_columns()); 297 298 for (std::vector<Gtk::TreeView::Column*>::iterator iter (vecViewColumns.begin ()), columns_end (vecViewColumns.end ()); 299 iter != columns_end; 300 ++iter) 301 { 302 Gtk::TreeView::Column* pViewColumn (*iter); 303 304 if(pViewColumn) 305 { 306 remove_column(*pViewColumn); 307 } 308 } 309} 310 311 312CellRenderer* TreeView::get_column_cell_renderer(int n) 313{ 314 auto pColumn = get_column(n); 315 if(pColumn) 316 return pColumn->get_first_cell(); 317 else 318 return nullptr; 319} 320 321 322const CellRenderer* TreeView::get_column_cell_renderer(int n) const 323{ 324 //Do some const_cast-ing to avoid repetition of code: 325 auto pRenderer = const_cast<TreeView*>(this)->get_column_cell_renderer(n); 326 return pRenderer; 327} 328 329void TreeView::reset_expander_column() 330{ 331 gtk_tree_view_set_expander_column(gobj(), nullptr /* see C docs */); 332} 333 334void TreeView::_auto_store_on_cellrenderer_toggle_edited(const Glib::ustring& path_string, 335 int model_column) 336{ 337 _auto_store_on_cellrenderer_toggle_edited_with_model(path_string, model_column, get_model()); 338} 339 340void TreeView::_auto_store_on_cellrenderer_toggle_edited_with_model(const Glib::ustring& path_string, 341 int model_column, const Glib::RefPtr<Gtk::TreeModel>& model) 342{ 343 Gtk::TreePath path (path_string); 344 345 //Get the row from the path: 346 if(model) 347 { 348 auto iter = model->get_iter(path); 349 if(iter) 350 { 351 auto row = *iter; 352 353 //Get the new value: 354 //This seems to get the old value, not the new one, 355 //so we will just NOT the model value ourselves. 356 //bool bActive = cell_renderer->get_active(); 357 bool bActive = false; 358 row.get_value(model_column, bActive); 359 bActive = !bActive; 360 361 //Store the user's new text in the model: 362 row.set_value(model_column, bActive); 363 } 364 } 365} 366 367void TreeView::move_column_to_start(TreeViewColumn& column) 368{ 369 gtk_tree_view_move_column_after(gobj(), (column).gobj(), nullptr /* See C docs */); 370} 371 372void TreeView::set_row_separator_func(const SlotRowSeparator& slot) 373{ 374 //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter. 375 //It will be deleted when SignalProxy_RowSeparator_gtk_callback_destroy() is called. 376 auto slot_copy = new SlotRowSeparator(slot); 377 378 gtk_tree_view_set_row_separator_func(gobj(), 379 &TreeView_Private::SignalProxy_RowSeparator_gtk_callback, slot_copy, 380 &TreeView_Private::SignalProxy_RowSeparator_gtk_callback_destroy); 381} 382 383void TreeView::set_search_position_func(const SlotSearchPosition& slot) 384{ 385 //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter. 386 //It will be deleted when SignalProxy_SearchPosition_gtk_callback_destroy() is called. 387 auto slot_copy = new SlotSearchPosition(slot); 388 389 gtk_tree_view_set_search_position_func(gobj(), 390 &SignalProxy_SearchPosition_gtk_callback, slot_copy, 391 &SignalProxy_SearchPosition_gtk_callback_destroy); 392} 393 394bool TreeView::get_visible_range(TreeModel::Path& start_path, TreeModel::Path& end_path) const 395{ 396 GtkTreePath* pTreePathStart = nullptr; 397 GtkTreePath* pTreePathEnd = nullptr; 398 bool result = gtk_tree_view_get_visible_range(const_cast<GtkTreeView*>(gobj()), &pTreePathStart, &pTreePathEnd); 399 400 start_path = TreeModel::Path(pTreePathStart, false /* don't take a copy, because the gtk_tree_view_get_visible_range() docs say that we must free the path */ ); 401 end_path = TreeModel::Path(pTreePathEnd, false /* don't take a copy, because the gtk_tree_view_get_visible_range() docs say that we must free the path */ ); 402 return result; 403} 404 405void TreeView::unset_model() 406{ 407 gtk_tree_view_set_model(gobj(), nullptr); 408} 409 410bool 411TreeView::get_tooltip_context_path(int& x, int& y, 412 bool keyboard_tip, 413 TreeModel::Path& path) 414{ 415 GtkTreePath* cpath = nullptr; 416 417 gboolean result = 418 gtk_tree_view_get_tooltip_context(gobj(), 419 &x, &y, 420 keyboard_tip, 421 nullptr, 422 &cpath, 423 nullptr); 424 425 //If result is false, cpath is not modified, it's still nullptr. 426 //wrap() can handle that situation. 427 path = Glib::wrap(cpath, false /* take_copy=false */); 428 429 return result; 430} 431 432bool 433TreeView::get_tooltip_context_iter(int& x, int& y, 434 bool keyboard_tip, 435 Gtk::TreeModel::iterator& iter) 436{ 437 GtkTreeIter src_iter; 438 439 gboolean result = 440 gtk_tree_view_get_tooltip_context(gobj(), 441 &x, &y, 442 keyboard_tip, 443 nullptr, 444 nullptr, 445 &src_iter); 446 447 iter = TreeIter(gtk_tree_view_get_model(this->gobj()), &src_iter); 448 449 return result; 450} 451 452bool TreeView::is_blank_at_pos(int x, int y, TreePath& path, TreeViewColumn*& column, int& cell_x, int& cell_y) const 453{ 454 GtkTreePath* cpath = nullptr; 455 GtkTreeViewColumn* pcolumn = nullptr; 456 const bool result = gtk_tree_view_is_blank_at_pos(const_cast<GtkTreeView*>(gobj()), x, y, &cpath, &pcolumn, &(cell_x), &(cell_y)); 457 458 path = TreePath(cpath, false /* take ownership instead of taking a copy */); 459 column = Glib::wrap(pcolumn); 460 return result; 461} 462 463bool TreeView::is_blank_at_pos(int x, int y) const 464{ 465 return gtk_tree_view_is_blank_at_pos(const_cast<GtkTreeView*>(gobj()), x, y, nullptr, nullptr, nullptr, nullptr); 466} 467 468void TreeView::unset_drag_dest_row() 469{ 470 // The C docs specify that a 0 path means unset. 471 gtk_tree_view_set_drag_dest_row(gobj(), nullptr, GTK_TREE_VIEW_DROP_BEFORE /* Arbitrary. Ignored anyway. */); 472} 473 474void TreeView::unset_row_separator_func() 475{ 476 gtk_tree_view_set_row_separator_func(gobj(), nullptr, nullptr, nullptr); 477} 478 479} // namespace Gtk 480