libyui-gtk  2.49.0
ygtktreeview.c
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 /* YGtkTreeView widget */
5 // check the header file for information about this widget
6 
7 /*
8  Textdomain "gtk"
9  */
10 
11 #include <yui/Libyui_config.h>
12 #include "ygtktreeview.h"
13 #include <gtk/gtk.h>
14 #define YGI18N_C
15 #include "YGi18n.h"
16 
17 extern char *ygutils_mapKBAccel (const char *src);
18 
19 static guint right_click_signal = 0;
20 
21 G_DEFINE_TYPE (YGtkTreeView, ygtk_tree_view, GTK_TYPE_TREE_VIEW)
22 
23 static void ygtk_tree_view_init (YGtkTreeView *view)
24 {
25 }
26 
27 static void ygtk_tree_view_finalize (GObject *object)
28 {
29  YGtkTreeView *view = YGTK_TREE_VIEW (object);
30  if (view->empty_text) {
31  g_free (view->empty_text);
32  view->empty_text = NULL;
33  }
34  G_OBJECT_CLASS (ygtk_tree_view_parent_class)->finalize (object);
35 }
36 
37 static void _gtk_widget_destroy (gpointer widget)
38 { gtk_widget_destroy (GTK_WIDGET (widget)); }
39 
40 static guint32 _popup_time = 0;
41 static gint _popup_x = 0, _popup_y = 0;
42 
43 #if GTK_CHECK_VERSION (3, 22, 0)
44 #else
45 static void _ygtk_tree_view_menu_position_func (
46  GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
47 { *x = _popup_x; *y = _popup_y; }
48 #endif
49 
50 void ygtk_tree_view_popup_menu (YGtkTreeView *view, GtkWidget *menu)
51 {
52  GtkWidget *widget = GTK_WIDGET (view);
53  // popup hack -- we can't destroy the menu at hide because it may not have
54  // emitted signals yet -- destroy it when replaced or the widget destroyed
55  g_object_set_data_full (G_OBJECT (view), "popup", menu, _gtk_widget_destroy);
56 
57  gtk_menu_attach_to_widget (GTK_MENU (menu), widget, NULL);
58 
59 # if GTK_CHECK_VERSION (3, 22, 0)
60  gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
61 # else
62  gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
63  _ygtk_tree_view_menu_position_func,
64  widget, 3, _popup_time);
65 # endif
66 
67  gtk_widget_show_all (menu);
68 }
69 
70 static gboolean ygtk_tree_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
71 {
72  if (event->button == 3) {
73  GtkTreeView *view = GTK_TREE_VIEW (widget);
74  GtkTreePath *path;
75  gboolean outreach;
76  outreach = !gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path, NULL, NULL, NULL);
77  if (!outreach) { // select row if it is not
78  GtkTreeSelection *selection = gtk_tree_view_get_selection (view);
79  GtkTreeModel *model = gtk_tree_view_get_model (view);
80  GtkTreeIter iter;
81  gtk_tree_model_get_iter (model, &iter, path);
82 
83  if (!gtk_tree_selection_iter_is_selected (selection, &iter))
84  gtk_tree_view_set_cursor (view, path, NULL, FALSE);
85  gtk_tree_path_free (path);
86  }
87 
88  _popup_time = event->time;
89  _popup_x = event->x_root; _popup_y = event->y_root;
90 
91  gtk_widget_grab_focus (widget);
92  g_signal_emit (widget, right_click_signal, 0, outreach);
93  return TRUE;
94  }
95  return GTK_WIDGET_CLASS (ygtk_tree_view_parent_class)->button_press_event (widget, event);
96 }
97 
98 static gboolean _ygtk_tree_view_popup_menu (GtkWidget *widget)
99 {
100  GtkTreeView *view = GTK_TREE_VIEW (widget);
101  GtkTreeSelection *selection = gtk_tree_view_get_selection (view);
102  gboolean outreach = gtk_tree_selection_count_selected_rows (selection) == 0;
103 
104  _popup_time = gtk_get_current_event_time();
105  if (outreach)
106  _popup_x = (_popup_y = 0);
107  else {
108  GList *rows = gtk_tree_selection_get_selected_rows (selection, NULL);
109  GtkTreePath *path = (GtkTreePath *) g_list_last (rows)->data;
110 
111  // in case that path is not visible:
112  gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
113 
114  GdkRectangle rect;
115  gtk_tree_view_get_cell_area (view, path, NULL, &rect);
116  _popup_x = 0;
117  _popup_y = rect.y + rect.height;
118 
119  g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
120  g_list_free (rows);
121  }
122 
123  gtk_tree_view_convert_bin_window_to_widget_coords (
124  view, _popup_x, _popup_y, &_popup_x, &_popup_y);
125  gint x_orig, y_orig;
126  gdk_window_get_origin (gtk_widget_get_window(widget), &x_orig, &y_orig);
127  _popup_x += x_orig; _popup_y += y_orig;
128 
129  g_signal_emit (widget, right_click_signal, 0, outreach);
130  return TRUE;
131 }
132 
133 static gboolean _ygtk_tree_view_draw (GtkWidget *widget, cairo_t *cr)
134 {
135  GTK_WIDGET_CLASS (ygtk_tree_view_parent_class)->draw(widget, cr);
136 
137  GtkTreeView *view = GTK_TREE_VIEW (widget);
138  YGtkTreeView *yview = YGTK_TREE_VIEW (widget);
139  if (yview->empty_text) {
140  GtkTreeModel *model = gtk_tree_view_get_model (view);
141  GtkTreeIter iter;
142  if (!model || !gtk_tree_model_get_iter_first (model, &iter)) { // empty tree-view
143  const gchar *text = yview->empty_text;
144  if (!model)
145  text = _("Loading...");
146  PangoLayout *layout;
147  layout = gtk_widget_create_pango_layout (widget, text);
148 
149  PangoAttrList *attrs = pango_attr_list_new();
150  pango_attr_list_insert (attrs, pango_attr_foreground_new (160<<8, 160<<8, 160<<8));
151  pango_layout_set_attributes (layout, attrs);
152  pango_attr_list_unref (attrs);
153 
154  int width, height;
155  pango_layout_get_pixel_size (layout, &width, &height);
156  GtkAllocation allocation;
157  gtk_widget_get_allocation(widget, &allocation);
158  int x, y;
159  x = (allocation.width - width) / 2;
160  y = (allocation.height - height) / 2;
161  cairo_move_to (cr, x, y);
162 
163  pango_cairo_show_layout (cr, layout);
164  g_object_unref (layout);
165  }
166  }
167  return FALSE;
168 }
169 
170 static void show_column_cb (GtkCheckMenuItem *item, GtkTreeView *view)
171 {
172  int col = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "column"));
173  GtkTreeViewColumn *column = gtk_tree_view_get_column (view, col);
174  gboolean visible = gtk_check_menu_item_get_active (item);
175  gtk_tree_view_column_set_visible (column, visible);
176 }
177 
178 GtkWidget *ygtk_tree_view_create_show_columns_menu (YGtkTreeView *view)
179 {
180  GtkWidget *menu = gtk_menu_new();
181  GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (view));
182  // see ygtk_tree_view_append_column(): we re-order arabic column insertion
183  gboolean reverse = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL &&
184  gtk_widget_get_direction (GTK_WIDGET (view)) != GTK_TEXT_DIR_RTL;
185  int n;
186  GList *i;
187  for (n = 0, i = columns; i; i = i->next, n++) {
188  GtkTreeViewColumn *column = (GtkTreeViewColumn *) i->data;
189  const gchar *header = gtk_tree_view_column_get_title (column);
190  if (header) {
191  GtkWidget *item = gtk_check_menu_item_new_with_label (header);
192  g_object_set_data (G_OBJECT (item), "column", GINT_TO_POINTER (n));
193 
194  gboolean visible = gtk_tree_view_column_get_visible (column);
195  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), visible);
196  g_signal_connect (G_OBJECT (item), "toggled",
197  G_CALLBACK (show_column_cb), view);
198 
199  if (reverse)
200  gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
201  else
202  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
203 
204  // if the user were to remove all columns, the right-click would no longer work
205  if (n == 0 && !reverse)
206  gtk_widget_set_sensitive (item, FALSE);
207  }
208  }
209  g_list_free (columns);
210  return menu;
211 }
212 
213 GtkWidget *ygtk_tree_view_append_show_columns_item (YGtkTreeView *view, GtkWidget *menu)
214 {
215  if (!menu)
216  menu = gtk_menu_new();
217 
218  GList *children = gtk_container_get_children (GTK_CONTAINER (menu));
219  g_list_free (children);
220  if (children) // non-null if it has children (in which case, add separator)
221  gtk_menu_shell_append (GTK_MENU_SHELL (menu), gtk_separator_menu_item_new());
222 
223  GtkWidget *submenu = ygtk_tree_view_create_show_columns_menu (view);
224  char *label = ygutils_mapKBAccel (_("&Show column"));
225  GtkWidget *item = gtk_menu_item_new_with_mnemonic (label);
226  g_free (label);
227  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
228  gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
229  return menu;
230 }
231 
232 void ygtk_tree_view_append_column (YGtkTreeView *view, GtkTreeViewColumn *column)
233 {
234  int pos = -1;
235  if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL) {
236  gtk_widget_set_direction (GTK_WIDGET (view), GTK_TEXT_DIR_LTR);
237 
238  GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)), *i;
239  for (i = renderers; i; i = i->next) {
240  GtkCellRenderer *renderer = (GtkCellRenderer *) i->data;
241  if (GTK_IS_CELL_RENDERER_TEXT (renderer)) {
242  PangoAlignment alignment;
243  g_object_get (G_OBJECT (renderer), "alignment", &alignment, NULL);
244  if (alignment == PANGO_ALIGN_LEFT)
245  alignment = PANGO_ALIGN_RIGHT;
246  else if (alignment == PANGO_ALIGN_RIGHT)
247  alignment = PANGO_ALIGN_LEFT;
248  g_object_set (G_OBJECT (renderer), "alignment", alignment, NULL);
249 
250  gfloat xalign;
251  g_object_get (G_OBJECT (renderer), "xalign", &xalign, NULL);
252  xalign = 1.0 - xalign;
253  g_object_set (G_OBJECT (renderer), "xalign", xalign, NULL);
254 
255  PangoEllipsizeMode ellipsize;
256  g_object_get (G_OBJECT (renderer), "ellipsize", &ellipsize, NULL);
257  if (ellipsize == PANGO_ELLIPSIZE_END)
258  ellipsize = PANGO_ELLIPSIZE_START;
259  else if (ellipsize == PANGO_ELLIPSIZE_START)
260  ellipsize = PANGO_ELLIPSIZE_END;
261  g_object_set (G_OBJECT (renderer), "ellipsize", ellipsize, NULL);
262  }
263  }
264  g_list_free (renderers);
265  pos = 0;
266  }
267  gtk_tree_view_insert_column (GTK_TREE_VIEW (view), column, pos);
268 }
269 
270 GtkTreeViewColumn *ygtk_tree_view_get_column (YGtkTreeView *view, gint nb)
271 {
272  GtkTreeViewColumn *column;
273  if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL) {
274  GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (view));
275  nb = g_list_length (columns) - nb - 1;
276  column = g_list_nth_data (columns, nb);
277  g_list_free (columns);
278  }
279  else
280  column = gtk_tree_view_get_column (GTK_TREE_VIEW (view), nb);
281  return column;
282 }
283 
284 
285 void ygtk_tree_view_set_empty_text (YGtkTreeView *view, const gchar *empty_text)
286 {
287  if (view->empty_text)
288  g_free (view->empty_text);
289  view->empty_text = empty_text ? g_strdup (empty_text) : NULL;
290 }
291 
292 GtkWidget *ygtk_tree_view_new (const gchar *empty_text)
293 {
294  YGtkTreeView *view = (YGtkTreeView *) g_object_new (YGTK_TYPE_TREE_VIEW, NULL);
295  view->empty_text = empty_text ? g_strdup (empty_text) : NULL;
296  return GTK_WIDGET (view);
297 }
298 
299 static void ygtk_tree_view_class_init (YGtkTreeViewClass *klass)
300 {
301  GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
302  gtkwidget_class->button_press_event = ygtk_tree_view_button_press_event;
303  gtkwidget_class->popup_menu = _ygtk_tree_view_popup_menu;
304  gtkwidget_class->draw = _ygtk_tree_view_draw;
305 
306  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
307  gobject_class->finalize = ygtk_tree_view_finalize;
308 
309  right_click_signal = g_signal_new ("right-click",
310  G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST,
311  G_STRUCT_OFFSET (YGtkTreeViewClass, right_click),
312  NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
313 }
314