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