libyui-gtk  2.46.0
YGTreeView.cc
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 /*
5  Textdomain "gtk"
6  */
7 
8 #include "YGi18n.h"
9 #define YUILogComponent "gtk"
10 #include <yui/Libyui_config.h>
11 #include "YGUI.h"
12 #include "YGUtils.h"
13 #include "YGWidget.h"
14 #include "YSelectionWidget.h"
15 #include "YGSelectionStore.h"
16 #include "ygtktreeview.h"
17 #include "YGMacros.h"
18 #include <string.h>
19 
20 /* A generic widget for table related widgets. */
21 
23 {
24 protected:
25  guint m_blockTimeout;
26  int markColumn;
27  GtkWidget *m_count;
28 
29 public:
30  YGTreeView (YWidget *ywidget, YWidget *parent, const std::string &label, bool tree)
31  : YGScrolledWidget (ywidget, parent, label, YD_VERT, YGTK_TYPE_TREE_VIEW, NULL),
32  YGSelectionStore (tree)
33  {
34  gtk_tree_view_set_headers_visible (getView(), FALSE);
35 
36  /* Yast tools expect the user to be unable to un-select the row. They
37  generally don't check to see if the returned value is -1. So, just
38  disallow un-selection. */
39  gtk_tree_selection_set_mode (getSelection(), GTK_SELECTION_BROWSE);
40 
41  connect (getSelection(), "changed", G_CALLBACK (selection_changed_cb), this);
42  connect (getWidget(), "row-activated", G_CALLBACK (activated_cb), this);
43  connect (getWidget(), "right-click", G_CALLBACK (right_click_cb), this);
44 
45  m_blockTimeout = 0; // GtkTreeSelection idiotically fires when showing widget
46  markColumn = -1; m_count = NULL;
47  blockSelected();
48  g_signal_connect (getWidget(), "map", G_CALLBACK (block_init_cb), this);
49  }
50 
51  virtual ~YGTreeView()
52  { if (m_blockTimeout) g_source_remove (m_blockTimeout); }
53 
54  inline GtkTreeView *getView()
55  { return GTK_TREE_VIEW (getWidget()); }
56  inline GtkTreeSelection *getSelection()
57  { return gtk_tree_view_get_selection (getView()); }
58 
59  void addTextColumn (int iconCol, int textCol)
60  { addTextColumn ("", YAlignUnchanged, iconCol, textCol); }
61 
62  void addTextColumn (const std::string &header, YAlignmentType align, int icon_col, int text_col)
63  {
64  gfloat xalign = -1;
65  switch (align) {
66  case YAlignBegin: xalign = 0.0; break;
67  case YAlignCenter: xalign = 0.5; break;
68  case YAlignEnd: xalign = 1.0; break;
69  case YAlignUnchanged: break;
70  }
71 
72  GtkTreeViewColumn *column = gtk_tree_view_column_new();
73  gtk_tree_view_column_set_title (column, header.c_str());
74 
75  GtkCellRenderer *renderer;
76  renderer = gtk_cell_renderer_pixbuf_new();
77  gtk_tree_view_column_pack_start (column, renderer, FALSE);
78  gtk_tree_view_column_set_attributes (column, renderer, "pixbuf", icon_col, NULL);
79 
80  renderer = gtk_cell_renderer_text_new();
81  gtk_tree_view_column_pack_start (column, renderer, TRUE);
82  gtk_tree_view_column_set_attributes (column, renderer, "text", text_col, NULL);
83  if (xalign != -1)
84  g_object_set (renderer, "xalign", xalign, NULL);
85 
86  gtk_tree_view_column_set_resizable (column, TRUE);
87  gtk_tree_view_append_column (getView(), column);
88  if (gtk_tree_view_get_search_column (getView()) == -1)
89  gtk_tree_view_set_search_column (getView(), text_col);
90  }
91 
92  void addCheckColumn (int check_col)
93  {
94  GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new();
95  g_object_set_data (G_OBJECT (renderer), "column", GINT_TO_POINTER (check_col));
96  GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (
97  NULL, renderer, "active", check_col, NULL);
98  gtk_tree_view_column_set_cell_data_func (column, renderer, inconsistent_mark_cb, this, NULL);
99  g_signal_connect (G_OBJECT (renderer), "toggled",
100  G_CALLBACK (toggled_cb), this);
101 
102  gtk_tree_view_column_set_resizable (column, TRUE);
103  gtk_tree_view_append_column (getView(), column);
104  if (markColumn == -1)
105  markColumn = check_col;
106  }
107 
108  void readModel()
109  { gtk_tree_view_set_model (getView(), getModel()); }
110 
111  void addCountWidget (YWidget *yparent)
112  {
113  bool mainWidget = !yparent || !strcmp (yparent->widgetClass(), "YVBox") || !strcmp (yparent->widgetClass(), "YReplacePoint");
114  if (mainWidget) {
115  m_count = gtk_label_new ("0");
116  GtkWidget *hbox = YGTK_HBOX_NEW(4);
117  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
118 
119  GtkWidget *label = gtk_label_new (_("Total selected:"));
120  //gtk_box_pack_start (GTK_BOX (hbox), gtk_event_box_new(), TRUE, TRUE, 0);
121  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
122  gtk_box_pack_start (GTK_BOX (hbox), m_count, FALSE, TRUE, 0);
123  gtk_box_pack_start (GTK_BOX (YGWidget::getWidget()), hbox, FALSE, TRUE, 0);
124  gtk_widget_show_all (hbox);
125  }
126  }
127 
128  void syncCount()
129  {
130  if (!m_count) return;
131 
132  struct inner {
133  static gboolean foreach (
134  GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer _pThis)
135  {
136  YGTreeView *pThis = (YGTreeView *) _pThis;
137  gboolean mark;
138  gtk_tree_model_get (model, iter, pThis->markColumn, &mark, -1);
139  if (mark) {
140  int count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (model), "count"));
141  g_object_set_data (G_OBJECT (model), "count", GINT_TO_POINTER (count+1));
142  }
143  return FALSE;
144  }
145  };
146 
147  GtkTreeModel *model = getModel();
148  g_object_set_data (G_OBJECT (model), "count", 0);
149  gtk_tree_model_foreach (model, inner::foreach, this);
150 
151  int count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (model), "count"));
152  gchar *str = g_strdup_printf ("%d", count);
153  gtk_label_set_text (GTK_LABEL (m_count), str);
154  g_free (str);
155  }
156 
157  void focusItem (YItem *item, bool select)
158  {
159  GtkTreeIter iter;
160  getTreeIter (item, &iter);
161  blockSelected();
162 
163  if (select) {
164  GtkTreePath *path = gtk_tree_model_get_path (getModel(), &iter);
165  gtk_tree_view_expand_to_path (getView(), path);
166 
167  if (gtk_tree_selection_get_mode (getSelection()) != GTK_SELECTION_MULTIPLE)
168  gtk_tree_view_scroll_to_cell (getView(), path, NULL, TRUE, 0.5, 0);
169  gtk_tree_path_free (path);
170 
171  gtk_tree_selection_select_iter (getSelection(), &iter);
172  }
173  else
174  gtk_tree_selection_unselect_iter (getSelection(), &iter);
175  }
176 
177  void unfocusAllItems()
178  {
179  blockSelected();
180  gtk_tree_selection_unselect_all (getSelection());
181  }
182 
183  void unmarkAll()
184  {
185  struct inner {
186  static gboolean foreach_unmark (
187  GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer _pThis)
188  {
189  YGTreeView *pThis = (YGTreeView *) _pThis;
190  pThis->setRowMark (iter, pThis->markColumn, FALSE);
191  return FALSE;
192  }
193  };
194 
195  gtk_tree_model_foreach (getModel(), inner::foreach_unmark, this);
196  }
197 
198  YItem *getFocusItem()
199  {
200  GtkTreeIter iter;
201  if (gtk_tree_selection_get_selected (getSelection(), NULL, &iter))
202  return getYItem (&iter);
203  return NULL;
204  }
205 
206  virtual bool _immediateMode() { return true; }
207  virtual bool _shrinkable() { return false; }
208  virtual bool _recursiveSelection() { return false; }
209 
210  void setMark (GtkTreeIter *iter, YItem *yitem, gint column, bool state, bool recursive)
211  {
212  setRowMark (iter, column, state);
213  yitem->setSelected (state);
214 
215  if (recursive)
216  for (YItemConstIterator it = yitem->childrenBegin();
217  it != yitem->childrenEnd(); it++) {
218  GtkTreeIter _iter;
219  getTreeIter (*it, &_iter);
220  setMark (&_iter, *it, column, state, true);
221  }
222  }
223 
224  void toggleMark (GtkTreePath *path, gint column)
225  {
226  GtkTreeIter iter;
227  if (!gtk_tree_model_get_iter (getModel(), &iter, path))
228  return;
229  gboolean state;
230  gtk_tree_model_get (getModel(), &iter, column, &state, -1);
231  state = !state;
232 
233  YItem *yitem = getYItem (&iter);
234  setMark (&iter, yitem, column, state, _recursiveSelection());
235  syncCount();
236  emitEvent (YEvent::ValueChanged);
237  }
238 
239  // YGWidget
240 
241  virtual unsigned int getMinSize (YUIDimension dim)
242  {
243  if (dim == YD_VERT)
244  return YGUtils::getCharsHeight (getWidget(), _shrinkable() ? 2 : 5);
245  return 80;
246  }
247 
248 protected:
249  static gboolean block_selected_timeout_cb (gpointer data)
250  {
251  YGTreeView *pThis = (YGTreeView *) data;
252  pThis->m_blockTimeout = 0;
253  return FALSE;
254  }
255 
256  void blockSelected()
257  { // GtkTreeSelection only fires when idle; so set a timeout
258  if (m_blockTimeout) g_source_remove (m_blockTimeout);
259  m_blockTimeout = g_timeout_add_full (G_PRIORITY_LOW, 50, block_selected_timeout_cb, this, NULL);
260  }
261 
262  static void block_init_cb (GtkWidget *widget, YGTreeView *pThis)
263  { pThis->blockSelected(); }
264 
265  // callbacks
266 
267  static bool all_marked (GtkTreeModel *model, GtkTreeIter *iter, int mark_col)
268  {
269  gboolean marked;
270  GtkTreeIter child_iter;
271  if (gtk_tree_model_iter_children (model, &child_iter, iter))
272  do {
273  gtk_tree_model_get (model, &child_iter, mark_col, &marked, -1);
274  if (!marked) return false;
275  all_marked (model, &child_iter, mark_col);
276  } while (gtk_tree_model_iter_next (model, &child_iter));
277  return true;
278  }
279 
280  static void inconsistent_mark_cb (GtkTreeViewColumn *column,
281  GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
282  { // used for trees -- show inconsistent if one node is check but another isn't
283  YGTreeView *pThis = (YGTreeView *) data;
284  gboolean marked;
285  gtk_tree_model_get (model, iter, pThis->markColumn, &marked, -1);
286  gboolean consistent = !marked || all_marked (model, iter, pThis->markColumn);
287  g_object_set (G_OBJECT (cell), "inconsistent", !consistent, NULL);
288  }
289 
290  static void selection_changed_cb (GtkTreeSelection *selection, YGTreeView *pThis)
291  {
292  struct inner {
293  static gboolean foreach_sync_select (
294  GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer _pThis)
295  {
296  YGTreeView *pThis = (YGTreeView *) _pThis;
297  GtkTreeSelection *selection = pThis->getSelection();
298  bool sel = gtk_tree_selection_iter_is_selected (selection, iter);
299  pThis->getYItem (iter)->setSelected (sel);
300  return FALSE;
301  }
302  };
303 
304  if (pThis->m_blockTimeout) return;
305  if (pThis->markColumn == -1)
306  gtk_tree_model_foreach (pThis->getModel(), inner::foreach_sync_select, pThis);
307  if (pThis->_immediateMode())
308  pThis->emitEvent (YEvent::SelectionChanged, IF_NOT_PENDING_EVENT);
309  }
310 
311  static void activated_cb (GtkTreeView *tree_view, GtkTreePath *path,
312  GtkTreeViewColumn *column, YGTreeView* pThis)
313  {
314  if (pThis->markColumn >= 0)
315  pThis->toggleMark (path, pThis->markColumn);
316  else {
317  // for tree - expand/collpase double-clicked rows
318  if (gtk_tree_view_row_expanded (tree_view, path))
319  gtk_tree_view_collapse_row (tree_view, path);
320  else
321  gtk_tree_view_expand_row (tree_view, path, FALSE);
322 
323  pThis->emitEvent (YEvent::Activated);
324  }
325  }
326 
327  static void toggled_cb (GtkCellRendererToggle *renderer, gchar *path_str,
328  YGTreeView *pThis)
329  {
330  GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
331  gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderer), "column"));
332  pThis->toggleMark (path, column);
333  gtk_tree_path_free (path);
334 
335  // un/marking a sub-node can cause changes of "inconsistency"
336  if (gtk_tree_path_get_depth (path) >= 2)
337  gtk_widget_queue_draw (pThis->getWidget());
338  }
339 
340 
341  static void right_click_cb (YGtkTreeView *view, gboolean outreach, YGTreeView *pThis)
342  { pThis->emitEvent (YEvent::ContextMenuActivated); }
343 };
344 
345 #include "YTable.h"
346 #include "YGDialog.h"
347 #include <gdk/gdkkeysyms.h>
348 #include <string.h>
349 
350 class YGTable : public YTable, public YGTreeView
351 {
352 public:
353  YGTable (YWidget *parent, YTableHeader *headers, bool multiSelection)
354  : YTable (NULL, headers, multiSelection),
355  YGTreeView (this, parent, std::string(), false)
356  {
357  gtk_tree_view_set_headers_visible (getView(), TRUE);
358  gtk_tree_view_set_rules_hint (getView(), columns() > 1);
359  ygtk_tree_view_set_empty_text (YGTK_TREE_VIEW (getView()), _("No entries."));
360  if (multiSelection)
361  gtk_tree_selection_set_mode (getSelection(), GTK_SELECTION_MULTIPLE);
362 
363  GType types [columns()*2];
364  for (int i = 0; i < columns(); i++) {
365  int t = i*2;
366  types[t+0] = GDK_TYPE_PIXBUF;
367  types[t+1] = G_TYPE_STRING;
368  addTextColumn (header(i), alignment (i), t, t+1);
369  }
370  createStore (columns()*2, types);
371  readModel();
372  if (!keepSorting())
373  setSortable (true);
374 
375  // if last col is aligned: add some dummy so that it doesn't expand.
376  YAlignmentType lastAlign = alignment (columns()-1);
377  if (lastAlign == YAlignCenter || lastAlign == YAlignEnd)
378  gtk_tree_view_append_column (getView(), gtk_tree_view_column_new());
379 
380  g_signal_connect (getWidget(), "key-press-event", G_CALLBACK (key_press_event_cb), this);
381  }
382 
383  void setSortable (bool sortable)
384  {
385  int n = 0;
386  GList *columns = gtk_tree_view_get_columns (getView());
387  for (GList *i = columns; i; i = i->next, n++) {
388  GtkTreeViewColumn *column = (GtkTreeViewColumn *) i->data;
389  if (n >= YGTable::columns())
390  break;
391  if (sortable) {
392  int index = (n*2)+1;
393  gtk_tree_sortable_set_sort_func (
394  GTK_TREE_SORTABLE (getModel()), index, tree_sort_cb,
395  GINT_TO_POINTER (index), NULL);
396  gtk_tree_view_column_set_sort_column_id (column, index);
397  }
398  else
399  gtk_tree_view_column_set_sort_column_id (column, -1);
400  }
401  g_list_free (columns);
402  }
403 
404  void setCell (GtkTreeIter *iter, int column, const YTableCell *cell)
405  {
406  if (!cell) return;
407  std::string label (cell->label());
408  if (label == "X")
409  label = YUI::app()->glyph (YUIGlyph_CheckMark);
410 
411  int index = column * 2;
412  setRowText (iter, index, cell->iconName(), index+1, label, this);
413  }
414 
415  // YGTreeView
416 
417  virtual bool _immediateMode() { return immediateMode(); }
418 
419  // YTable
420 
421  virtual void setKeepSorting (bool keepSorting)
422  {
423  YTable::setKeepSorting (keepSorting);
424  setSortable (!keepSorting);
425  if (!keepSorting) {
426  GtkTreeViewColumn *column = gtk_tree_view_get_column (getView(), 0);
427  if (column)
428  gtk_tree_view_column_clicked (column);
429  }
430  }
431 
432  virtual void cellChanged (const YTableCell *cell)
433  {
434  GtkTreeIter iter;
435  getTreeIter (cell->parent(), &iter);
436  setCell (&iter, cell->column(), cell);
437  }
438 
439  // YGSelectionStore
440 
441  void doAddItem (YItem *_item)
442  {
443  YTableItem *item = dynamic_cast <YTableItem *> (_item);
444  if (item) {
445  GtkTreeIter iter;
446  addRow (item, &iter);
447  int i = 0;
448  for (YTableCellIterator it = item->cellsBegin();
449  it != item->cellsEnd(); it++)
450  if (i >= columns())
451  {
452  yuiWarning() << "Item contains too many columns, current column is (starting from 0) " << i
453  << " but only " << columns() << " columns are configured. Skipping..." << std::endl;
454  }
455  else
456  setCell (&iter, i++, *it);
457  if (item->selected())
458  focusItem (item, true);
459  }
460  else
461  yuiError() << "Can only add YTableItems to a YTable.\n";
462  }
463 
464  void doSelectItem (YItem *item, bool select)
465  { focusItem (item, select); }
466 
467  void doDeselectAllItems()
468  { unfocusAllItems(); }
469 
470  // callbacks
471 
472  static void activateButton (YWidget *button)
473  {
474  YWidgetEvent *event = new YWidgetEvent (button, YEvent::Activated);
475  YGUI::ui()->sendEvent (event);
476  }
477 
478  static void hack_right_click_cb (YGtkTreeView *view, gboolean outreach, YGTable *pThis)
479  {
480  if (pThis->notifyContextMenu())
481  return YGTreeView::right_click_cb (view, outreach, pThis);
482 
483  // If no context menu is specified, hack one ;-)
484 
485  struct inner {
486  static void key_activate_cb (GtkMenuItem *item, YWidget *button)
487  { activateButton (button); }
488  static void appendItem (GtkWidget *menu, const gchar *stock, int key)
489  {
490  YWidget *button = YGDialog::currentDialog()->getFunctionWidget (key);
491  if (button) {
492  GtkWidget *item;
493  item = gtk_menu_item_new_with_mnemonic (stock);
494  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
495  g_signal_connect (G_OBJECT (item), "activate",
496  G_CALLBACK (key_activate_cb), button);
497  }
498  }
499  };
500 
501  GtkWidget *menu = gtk_menu_new();
502  YGDialog *dialog = YGDialog::currentDialog();
503  if (dialog->getClassWidgets ("YTable").size() == 1) {
504  // if more than one table exists, function keys would be ambiguous
505  if (outreach) {
506  if (dialog->getFunctionWidget(3))
507  inner::appendItem (menu, "list-add", 3);
508  }
509  else {
510  if (dialog->getFunctionWidget(4))
511  inner::appendItem (menu, "edit-cut", 4);
512  if (dialog->getFunctionWidget(5))
513  inner::appendItem (menu, "list-remove", 5);
514  }
515  }
516 
517  menu = ygtk_tree_view_append_show_columns_item (YGTK_TREE_VIEW (view), menu);
518  ygtk_tree_view_popup_menu (view, menu);
519  }
520 
521  static gboolean key_press_event_cb (GtkWidget *widget, GdkEventKey *event, YGTable *pThis)
522  {
523  if (event->keyval == GDK_KEY_Delete) {
524  YWidget *button = YGDialog::currentDialog()->getFunctionWidget (5);
525  if (button)
526  activateButton (button);
527  else
528  gtk_widget_error_bell (widget);
529  return TRUE;
530  }
531  return FALSE;
532  }
533 
534  static gint tree_sort_cb (
535  GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer _index)
536  {
537  int index = GPOINTER_TO_INT (_index);
538  gchar *str_a, *str_b;
539  gtk_tree_model_get (model, a, index, &str_a, -1);
540  gtk_tree_model_get (model, b, index, &str_b, -1);
541  if (!str_a) str_a = g_strdup ("");
542  if (!str_b) str_b = g_strdup ("");
543  int ret = strcmp (str_a, str_b);
544  g_free (str_a); g_free (str_b);
545  return ret;
546  }
547 
548  YGLABEL_WIDGET_IMPL (YTable)
549  YGSELECTION_WIDGET_IMPL (YTable)
550 };
551 
552 YTable *YGWidgetFactory::createTable (YWidget *parent, YTableHeader *headers,
553  bool multiSelection)
554 {
555  return new YGTable (parent, headers, multiSelection);
556 }
557 
558 #include "YSelectionBox.h"
559 
560 class YGSelectionBox : public YSelectionBox, public YGTreeView
561 {
562 public:
563  YGSelectionBox (YWidget *parent, const std::string &label)
564  : YSelectionBox (NULL, label),
565  YGTreeView (this, parent, label, false)
566  {
567  GType types [2] = { GDK_TYPE_PIXBUF, G_TYPE_STRING };
568  addTextColumn (0, 1);
569  createStore (2, types);
570  readModel();
571  }
572 
573  // YGTreeView
574 
575  virtual bool _shrinkable() { return shrinkable(); }
576 
577  // YGSelectionStore
578 
579  void doAddItem (YItem *item)
580  {
581  GtkTreeIter iter;
582  addRow (item, &iter);
583  setRowText (&iter, 0, item->iconName(), 1, item->label(), this);
584  if (item->selected())
585  focusItem (item, true);
586  }
587 
588  void doSelectItem (YItem *item, bool select)
589  { focusItem (item, select); }
590 
591  void doDeselectAllItems()
592  { unfocusAllItems(); }
593 
594  YGLABEL_WIDGET_IMPL (YSelectionBox)
595  YGSELECTION_WIDGET_IMPL (YSelectionBox)
596 };
597 
598 YSelectionBox *YGWidgetFactory::createSelectionBox (YWidget *parent, const std::string &label)
599 { return new YGSelectionBox (parent, label); }
600 
601 #include "YMultiSelectionBox.h"
602 
603 class YGMultiSelectionBox : public YMultiSelectionBox, public YGTreeView
604 {
605 public:
606  YGMultiSelectionBox (YWidget *parent, const std::string &label)
607  : YMultiSelectionBox (NULL, label),
608  YGTreeView (this, parent, label, false)
609  {
610  GType types [3] = { G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING };
611  addCheckColumn (0);
612  addTextColumn (1, 2);
613  createStore (3, types);
614  readModel();
615  addCountWidget (parent);
616  }
617 
618  // YGTreeView
619 
620  virtual bool _shrinkable() { return shrinkable(); }
621 
622  // YGSelectionStore
623 
624  void doAddItem (YItem *item)
625  {
626  GtkTreeIter iter;
627  addRow (item, &iter);
628  setRowMark (&iter, 0, item->selected());
629  setRowText (&iter, 1, item->iconName(), 2, item->label(), this);
630  syncCount();
631  }
632 
633  void doSelectItem (YItem *item, bool select)
634  {
635  GtkTreeIter iter;
636  getTreeIter (item, &iter);
637  setRowMark (&iter, 0, select);
638  syncCount();
639  }
640 
641  void doDeselectAllItems()
642  { unmarkAll(); syncCount(); }
643 
644  // YMultiSelectionBox
645 
646  virtual YItem *currentItem()
647  { return getFocusItem(); }
648 
649  virtual void setCurrentItem (YItem *item)
650  { focusItem (item, true); }
651 
652  YGLABEL_WIDGET_IMPL (YMultiSelectionBox)
653  YGSELECTION_WIDGET_IMPL (YMultiSelectionBox)
654 };
655 
656 YMultiSelectionBox *YGWidgetFactory::createMultiSelectionBox (YWidget *parent, const std::string &label)
657 { return new YGMultiSelectionBox (parent, label); }
658 
659 #include "YTree.h"
660 #include "YTreeItem.h"
661 
662 class YGTree : public YTree, public YGTreeView
663 {
664 public:
665  YGTree (YWidget *parent, const std::string &label, bool multiselection, bool recursiveSelection)
666  : YTree (NULL, label, multiselection, recursiveSelection),
667  YGTreeView (this, parent, label, true)
668  {
669  if (multiselection) {
670  GType types [3] = { GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_BOOLEAN };
671  addCheckColumn (2);
672  addTextColumn (0, 1);
673  createStore (3, types);
674  addCountWidget (parent);
675  }
676  else
677  {
678  GType types [2] = { GDK_TYPE_PIXBUF, G_TYPE_STRING };
679  addTextColumn (0, 1);
680  createStore (2, types);
681  }
682  readModel();
683 
684  g_signal_connect (getWidget(), "row-collapsed", G_CALLBACK (row_collapsed_cb), this);
685  g_signal_connect (getWidget(), "row-expanded", G_CALLBACK (row_expanded_cb), this);
686  }
687 
688  virtual bool _recursiveSelection() { return recursiveSelection(); }
689 
690  void addNode (YItem *item, GtkTreeIter *parent)
691  {
692  GtkTreeIter iter;
693  addRow (item, &iter, parent);
694  setRowText (&iter, 0, item->iconName(), 1, item->label(), this);
695  if (item->selected()) {
696  if (hasMultiSelection())
697  setRowMark (&iter, 2, item->selected());
698  else
699  focusItem (item, true);
700  }
701  if (((YTreeItem *) item)->isOpen())
702  expand (&iter);
703  for (YItemConstIterator it = item->childrenBegin();
704  it != item->childrenEnd(); it++)
705  addNode (*it, &iter);
706  }
707 
708 
709  void expand (GtkTreeIter *iter)
710  {
711  GtkTreePath *path = gtk_tree_model_get_path (getModel(), iter);
712  gtk_tree_view_expand_row (getView(), path, FALSE);
713  gtk_tree_path_free (path);
714  }
715 #if 0
716  bool isReallyOpen (YTreeItem *item) // are parents open as well?
717  {
718  for (YTreeItem *i = item; i; i = i->parent())
719  if (!i->isOpen())
720  return false;
721  return true;
722  }
723 #endif
724 
725  // YTree
726 
727  virtual void rebuildTree()
728  {
729  blockSelected();
730 
731  doDeleteAllItems();
732  for (YItemConstIterator it = YTree::itemsBegin(); it != YTree::itemsEnd(); it++)
733  addNode (*it, NULL);
734 
735  int depth = getTreeDepth();
736  gtk_tree_view_set_show_expanders (getView(), depth > 1);
737  gtk_tree_view_set_enable_tree_lines (getView(), depth > 3);
738 
739  // for whatever reason, we need to expand nodes only after the model
740  // is fully initialized
741  struct inner {
742  static gboolean foreach_sync_open (
743  GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer _pThis)
744  {
745  YGTree *pThis = (YGTree *) _pThis;
746  YTreeItem *item = (YTreeItem *) pThis->getYItem (iter);
747  if (item->isOpen())
748  gtk_tree_view_expand_row (pThis->getView(), path, FALSE);
749  return FALSE;
750  }
751  };
752 
753  g_signal_handlers_block_by_func (getWidget(), (gpointer) row_expanded_cb, this);
754  gtk_tree_model_foreach (getModel(), inner::foreach_sync_open, this);
755  g_signal_handlers_unblock_by_func (getWidget(), (gpointer) row_expanded_cb, this);
756 
757  syncCount();
758  }
759 
760  virtual YTreeItem *currentItem()
761  { return (YTreeItem *) getFocusItem(); }
762 
763  void _markItem (YItem *item, bool select, bool recursive) {
764  GtkTreeIter iter;
765  getTreeIter (item, &iter);
766  setRowMark (&iter, 2, select);
767 
768  if (recursive) {
769  YTreeItem *_item = (YTreeItem *) item;
770  for (YItemConstIterator it = _item->childrenBegin();
771  it != _item->childrenEnd(); it++)
772  _markItem (*it, select, true);
773  }
774  }
775 
776  // YGSelectionStore
777 
778  void doAddItem (YItem *item) {} // rebuild will be called anyway
779 
780  void doSelectItem (YItem *item, bool select)
781  {
782  if (hasMultiSelection()) {
783  _markItem (item, select, recursiveSelection());
784  syncCount();
785  }
786  else
787  focusItem (item, select);
788  }
789 
790  void doDeselectAllItems()
791  {
792  if (hasMultiSelection()) {
793  unmarkAll();
794  syncCount();
795  }
796  else
797  unfocusAllItems();
798  }
799 
800  // callbacks
801 
802  void reportRowOpen (GtkTreeIter *iter, bool open)
803  {
804  YTreeItem *item = static_cast <YTreeItem *> (getYItem (iter));
805  item->setOpen (open);
806  }
807 
808  static void row_collapsed_cb (GtkTreeView *view, GtkTreeIter *iter,
809  GtkTreePath *path, YGTree *pThis)
810  { pThis->reportRowOpen (iter, false); }
811 
812  static void row_expanded_cb (GtkTreeView *view, GtkTreeIter *iter,
813  GtkTreePath *path, YGTree *pThis)
814  { pThis->reportRowOpen (iter, true); }
815 
816 #if 0
817  // we do a bit of a work-around here to mimic -qt behavior... A node can
818  // be initialized as open, yet its parent, or some grand-parent, be closed.
819  // We thus honor the open state when its parent gets open.
820  YTreeItem *item = static_cast <YTreeItem *> (pThis->getYItem (iter));
821  for (YItemConstIterator it = item->childrenBegin();
822  it != item->childrenEnd(); it++) {
823  const YTreeItem *child = static_cast <YTreeItem *> (*it);
824  if (child->isOpen()) {
825  GtkTreeIter iter;
826  if (pThis->getIter (child, &iter))
827  pThis->expand (&iter);
828  }
829  }
830 #endif
831 
832  // NOTE copy of Qt one
833  void activate()
834  {
835  // send an activation event for this widget
836  if ( notify() )
837  YGUI::ui()->sendEvent( new YWidgetEvent( this,YEvent::Activated ) );
838  }
839 
840 
841  YGLABEL_WIDGET_IMPL (YTree)
842  YGSELECTION_WIDGET_IMPL (YTree)
843 };
844 
845 YTree *YGWidgetFactory::createTree (YWidget *parent, const std::string &label, bool multiselection, bool recursiveSelection)
846 { return new YGTree (parent, label, multiselection, recursiveSelection); }
847