• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/gtk/gtk_custom_menu_item.h"
6 
7 #include "base/i18n/rtl.h"
8 #include "chrome/browser/ui/gtk/gtk_custom_menu.h"
9 
10 // This method was autogenerated by the program glib-genmarshall, which
11 // generated it from the line "BOOL:INT". Two different attempts at getting gyp
12 // to autogenerate this didn't work. If we need more non-standard marshallers,
13 // this should be deleted, and an actual build step should be added.
chrome_marshall_BOOLEAN__INT(GClosure * closure,GValue * return_value G_GNUC_UNUSED,guint n_param_values,const GValue * param_values,gpointer invocation_hint G_GNUC_UNUSED,gpointer marshal_data)14 void chrome_marshall_BOOLEAN__INT(GClosure* closure,
15                                   GValue* return_value G_GNUC_UNUSED,
16                                   guint n_param_values,
17                                   const GValue* param_values,
18                                   gpointer invocation_hint G_GNUC_UNUSED,
19                                   gpointer marshal_data) {
20   typedef gboolean(*GMarshalFunc_BOOLEAN__INT)(gpointer data1,
21                                                gint arg_1,
22                                                gpointer data2);
23   register GMarshalFunc_BOOLEAN__INT callback;
24   register GCClosure *cc = (GCClosure*)closure;
25   register gpointer data1, data2;
26   gboolean v_return;
27 
28   g_return_if_fail(return_value != NULL);
29   g_return_if_fail(n_param_values == 2);
30 
31   if (G_CCLOSURE_SWAP_DATA(closure)) {
32     data1 = closure->data;
33     // Note: This line (and the line setting data1 in the other if branch)
34     // were macros in the original autogenerated output. This is with the
35     // macro resolved for release mode. In debug mode, it uses an accessor
36     // that asserts saying that the object pointed to by param_values doesn't
37     // hold a pointer. This appears to be the cause of http://crbug.com/58945.
38     //
39     // This is more than a little odd because the gtype on this first param
40     // isn't set correctly by the time we get here, while I watched it
41     // explicitly set upstack. I verified that v_pointer is still set
42     // correctly. I'm not sure what's going on. :(
43     data2 = (param_values + 0)->data[0].v_pointer;
44   } else {
45     data1 = (param_values + 0)->data[0].v_pointer;
46     data2 = closure->data;
47   }
48   callback = (GMarshalFunc_BOOLEAN__INT)(marshal_data ? marshal_data :
49                                          cc->callback);
50 
51   v_return = callback(data1,
52                       g_value_get_int(param_values + 1),
53                       data2);
54 
55   g_value_set_boolean(return_value, v_return);
56 }
57 
58 enum {
59   BUTTON_PUSHED,
60   TRY_BUTTON_PUSHED,
61   LAST_SIGNAL
62 };
63 
64 static guint custom_menu_item_signals[LAST_SIGNAL] = { 0 };
65 
G_DEFINE_TYPE(GtkCustomMenuItem,gtk_custom_menu_item,GTK_TYPE_MENU_ITEM)66 G_DEFINE_TYPE(GtkCustomMenuItem, gtk_custom_menu_item, GTK_TYPE_MENU_ITEM)
67 
68 static void set_selected(GtkCustomMenuItem* item, GtkWidget* selected) {
69   if (selected != item->currently_selected_button) {
70     if (item->currently_selected_button) {
71       gtk_widget_set_state(item->currently_selected_button, GTK_STATE_NORMAL);
72       gtk_widget_set_state(
73           gtk_bin_get_child(GTK_BIN(item->currently_selected_button)),
74           GTK_STATE_NORMAL);
75     }
76 
77     item->currently_selected_button = selected;
78     if (item->currently_selected_button) {
79       gtk_widget_set_state(item->currently_selected_button, GTK_STATE_SELECTED);
80       gtk_widget_set_state(
81           gtk_bin_get_child(GTK_BIN(item->currently_selected_button)),
82           GTK_STATE_PRELIGHT);
83     }
84   }
85 }
86 
87 // When GtkButtons set the label text, they rebuild the widget hierarchy each
88 // and every time. Therefore, we can't just fish out the label from the button
89 // and set some properties; we have to create this callback function that
90 // listens on the button's "notify" signal, which is emitted right after the
91 // label has been (re)created. (Label values can change dynamically.)
on_button_label_set(GObject * object)92 static void on_button_label_set(GObject* object) {
93   GtkButton* button = GTK_BUTTON(object);
94   gtk_widget_set_sensitive(GTK_BIN(button)->child, FALSE);
95   gtk_misc_set_padding(GTK_MISC(GTK_BIN(button)->child), 2, 0);
96 }
97 
98 static void gtk_custom_menu_item_finalize(GObject *object);
99 static gint gtk_custom_menu_item_expose(GtkWidget* widget,
100                                         GdkEventExpose* event);
101 static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget,
102                                                  GdkEventExpose* event,
103                                                  GtkCustomMenuItem* menu_item);
104 static void gtk_custom_menu_item_select(GtkItem *item);
105 static void gtk_custom_menu_item_deselect(GtkItem *item);
106 static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item);
107 
gtk_custom_menu_item_init(GtkCustomMenuItem * item)108 static void gtk_custom_menu_item_init(GtkCustomMenuItem* item) {
109   item->all_widgets = NULL;
110   item->button_widgets = NULL;
111   item->currently_selected_button = NULL;
112   item->previously_selected_button = NULL;
113 
114   GtkWidget* menu_hbox = gtk_hbox_new(FALSE, 0);
115   gtk_container_add(GTK_CONTAINER(item), menu_hbox);
116 
117   item->label = gtk_label_new(NULL);
118   gtk_misc_set_alignment(GTK_MISC(item->label), 0.0, 0.5);
119   gtk_box_pack_start(GTK_BOX(menu_hbox), item->label, TRUE, TRUE, 0);
120 
121   item->hbox = gtk_hbox_new(FALSE, 0);
122   gtk_box_pack_end(GTK_BOX(menu_hbox), item->hbox, FALSE, FALSE, 0);
123 
124   g_signal_connect(item->hbox, "expose-event",
125                    G_CALLBACK(gtk_custom_menu_item_hbox_expose),
126                    item);
127 
128   gtk_widget_show_all(menu_hbox);
129 }
130 
gtk_custom_menu_item_class_init(GtkCustomMenuItemClass * klass)131 static void gtk_custom_menu_item_class_init(GtkCustomMenuItemClass* klass) {
132   GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
133   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
134   GtkItemClass* item_class = GTK_ITEM_CLASS(klass);
135   GtkMenuItemClass* menu_item_class = GTK_MENU_ITEM_CLASS(klass);
136 
137   gobject_class->finalize = gtk_custom_menu_item_finalize;
138 
139   widget_class->expose_event = gtk_custom_menu_item_expose;
140 
141   item_class->select = gtk_custom_menu_item_select;
142   item_class->deselect = gtk_custom_menu_item_deselect;
143 
144   menu_item_class->activate = gtk_custom_menu_item_activate;
145 
146   custom_menu_item_signals[BUTTON_PUSHED] =
147       g_signal_new("button-pushed",
148                    G_TYPE_FROM_CLASS(gobject_class),
149                    G_SIGNAL_RUN_FIRST,
150                    0,
151                    NULL, NULL,
152                    gtk_marshal_NONE__INT,
153                    G_TYPE_NONE, 1, GTK_TYPE_INT);
154   custom_menu_item_signals[TRY_BUTTON_PUSHED] =
155       g_signal_new("try-button-pushed",
156                    G_TYPE_FROM_CLASS(gobject_class),
157                    G_SIGNAL_RUN_LAST,
158                    0,
159                    NULL, NULL,
160                    chrome_marshall_BOOLEAN__INT,
161                    G_TYPE_BOOLEAN, 1, GTK_TYPE_INT);
162 }
163 
gtk_custom_menu_item_finalize(GObject * object)164 static void gtk_custom_menu_item_finalize(GObject *object) {
165   GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM(object);
166   g_list_free(item->all_widgets);
167   g_list_free(item->button_widgets);
168 
169   G_OBJECT_CLASS(gtk_custom_menu_item_parent_class)->finalize(object);
170 }
171 
gtk_custom_menu_item_expose(GtkWidget * widget,GdkEventExpose * event)172 static gint gtk_custom_menu_item_expose(GtkWidget* widget,
173                                         GdkEventExpose* event) {
174   if (GTK_WIDGET_VISIBLE(widget) &&
175       GTK_WIDGET_MAPPED(widget) &&
176       gtk_bin_get_child(GTK_BIN(widget))) {
177     // We skip the drawing in the GtkMenuItem class it draws the highlighted
178     // background and we don't want that.
179     gtk_container_propagate_expose(GTK_CONTAINER(widget),
180                                    gtk_bin_get_child(GTK_BIN(widget)),
181                                    event);
182   }
183 
184   return FALSE;
185 }
186 
gtk_custom_menu_item_expose_button(GtkWidget * hbox,GdkEventExpose * event,GList * button_item)187 static void gtk_custom_menu_item_expose_button(GtkWidget* hbox,
188                                                GdkEventExpose* event,
189                                                GList* button_item) {
190   // We search backwards to find the leftmost and rightmost buttons. The
191   // current button may be that button.
192   GtkWidget* current_button = GTK_WIDGET(button_item->data);
193   GtkWidget* first_button = current_button;
194   for (GList* i = button_item; i && GTK_IS_BUTTON(i->data);
195        i = g_list_previous(i)) {
196     first_button = GTK_WIDGET(i->data);
197   }
198 
199   GtkWidget* last_button = current_button;
200   for (GList* i = button_item; i && GTK_IS_BUTTON(i->data);
201        i = g_list_next(i)) {
202     last_button = GTK_WIDGET(i->data);
203   }
204 
205   if (base::i18n::IsRTL())
206     std::swap(first_button, last_button);
207 
208   int x = first_button->allocation.x;
209   int y = first_button->allocation.y;
210   int width = last_button->allocation.width + last_button->allocation.x -
211               first_button->allocation.x;
212   int height = last_button->allocation.height;
213 
214   gtk_paint_box(hbox->style, hbox->window,
215                 static_cast<GtkStateType>(
216                     GTK_WIDGET_STATE(current_button)),
217                 GTK_SHADOW_OUT,
218                 &current_button->allocation, hbox, "button",
219                 x, y, width, height);
220 
221   // Propagate to the button's children.
222   gtk_container_propagate_expose(
223       GTK_CONTAINER(current_button),
224       gtk_bin_get_child(GTK_BIN(current_button)),
225       event);
226 }
227 
gtk_custom_menu_item_hbox_expose(GtkWidget * widget,GdkEventExpose * event,GtkCustomMenuItem * menu_item)228 static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget,
229                                                  GdkEventExpose* event,
230                                                  GtkCustomMenuItem* menu_item) {
231   // First render all the buttons that aren't the currently selected item.
232   for (GList* current_item = menu_item->all_widgets;
233        current_item != NULL; current_item = g_list_next(current_item)) {
234     if (GTK_IS_BUTTON(current_item->data)) {
235       if (GTK_WIDGET(current_item->data) !=
236           menu_item->currently_selected_button) {
237         gtk_custom_menu_item_expose_button(widget, event, current_item);
238       }
239     }
240   }
241 
242   // As a separate pass, draw the buton separators above. We need to draw the
243   // separators in a separate pass because we are drawing on top of the
244   // buttons. Otherwise, the vlines are overwritten by the next button.
245   for (GList* current_item = menu_item->all_widgets;
246        current_item != NULL; current_item = g_list_next(current_item)) {
247     if (GTK_IS_BUTTON(current_item->data)) {
248       // Check to see if this is the last button in a run.
249       GList* next_item = g_list_next(current_item);
250       if (next_item && GTK_IS_BUTTON(next_item->data)) {
251         GtkWidget* current_button = GTK_WIDGET(current_item->data);
252         GtkAllocation child_alloc =
253             gtk_bin_get_child(GTK_BIN(current_button))->allocation;
254         int half_offset = widget->style->xthickness / 2;
255         gtk_paint_vline(widget->style, widget->window,
256                         static_cast<GtkStateType>(
257                             GTK_WIDGET_STATE(current_button)),
258                         &event->area, widget, "button",
259                         child_alloc.y,
260                         child_alloc.y + child_alloc.height,
261                         current_button->allocation.x +
262                         current_button->allocation.width - half_offset);
263       }
264     }
265   }
266 
267   // Finally, draw the selected item on top of the separators so there are no
268   // artifacts inside the button area.
269   GList* selected = g_list_find(menu_item->all_widgets,
270                                 menu_item->currently_selected_button);
271   if (selected) {
272     gtk_custom_menu_item_expose_button(widget, event, selected);
273   }
274 
275   return TRUE;
276 }
277 
gtk_custom_menu_item_select(GtkItem * item)278 static void gtk_custom_menu_item_select(GtkItem* item) {
279   GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item);
280 
281   // When we are selected, the only thing we do is clear information from
282   // previous selections. Actual selection of a button is done either in the
283   // "mouse-motion-event" or is manually set from GtkCustomMenu's overridden
284   // "move-current" handler.
285   custom_item->previously_selected_button = NULL;
286 
287   gtk_widget_queue_draw(GTK_WIDGET(item));
288 }
289 
gtk_custom_menu_item_deselect(GtkItem * item)290 static void gtk_custom_menu_item_deselect(GtkItem* item) {
291   GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item);
292 
293   // When we are deselected, we store the item that was currently selected so
294   // that it can be acted on. Menu items are first deselected before they are
295   // activated.
296   custom_item->previously_selected_button =
297       custom_item->currently_selected_button;
298   if (custom_item->currently_selected_button)
299     set_selected(custom_item, NULL);
300 
301   gtk_widget_queue_draw(GTK_WIDGET(item));
302 }
303 
gtk_custom_menu_item_activate(GtkMenuItem * menu_item)304 static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item) {
305   GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item);
306 
307   // We look at |previously_selected_button| because by the time we've been
308   // activated, we've already gone through our deselect handler.
309   if (custom_item->previously_selected_button) {
310     gpointer id_ptr = g_object_get_data(
311         G_OBJECT(custom_item->previously_selected_button), "command-id");
312     if (id_ptr != NULL) {
313       int command_id = GPOINTER_TO_INT(id_ptr);
314       g_signal_emit(custom_item, custom_menu_item_signals[BUTTON_PUSHED], 0,
315                     command_id);
316       set_selected(custom_item, NULL);
317     }
318   }
319 }
320 
gtk_custom_menu_item_new(const char * title)321 GtkWidget* gtk_custom_menu_item_new(const char* title) {
322   GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM(
323       g_object_new(GTK_TYPE_CUSTOM_MENU_ITEM, NULL));
324   gtk_label_set_text(GTK_LABEL(item->label), title);
325   return GTK_WIDGET(item);
326 }
327 
gtk_custom_menu_item_add_button(GtkCustomMenuItem * menu_item,int command_id)328 GtkWidget* gtk_custom_menu_item_add_button(GtkCustomMenuItem* menu_item,
329                                            int command_id) {
330   GtkWidget* button = gtk_button_new();
331   g_object_set_data(G_OBJECT(button), "command-id",
332                     GINT_TO_POINTER(command_id));
333   gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0);
334   gtk_widget_show(button);
335 
336   menu_item->all_widgets = g_list_append(menu_item->all_widgets, button);
337   menu_item->button_widgets = g_list_append(menu_item->button_widgets, button);
338 
339   return button;
340 }
341 
gtk_custom_menu_item_add_button_label(GtkCustomMenuItem * menu_item,int command_id)342 GtkWidget* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem* menu_item,
343                                                  int command_id) {
344   GtkWidget* button = gtk_button_new_with_label("");
345   g_object_set_data(G_OBJECT(button), "command-id",
346                     GINT_TO_POINTER(command_id));
347   gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0);
348   g_signal_connect(button, "notify::label",
349                    G_CALLBACK(on_button_label_set), NULL);
350   gtk_widget_show(button);
351 
352   menu_item->all_widgets = g_list_append(menu_item->all_widgets, button);
353 
354   return button;
355 }
356 
gtk_custom_menu_item_add_space(GtkCustomMenuItem * menu_item)357 void gtk_custom_menu_item_add_space(GtkCustomMenuItem* menu_item) {
358   GtkWidget* fixed = gtk_fixed_new();
359   gtk_widget_set_size_request(fixed, 5, -1);
360 
361   gtk_box_pack_start(GTK_BOX(menu_item->hbox), fixed, FALSE, FALSE, 0);
362   gtk_widget_show(fixed);
363 
364   menu_item->all_widgets = g_list_append(menu_item->all_widgets, fixed);
365 }
366 
gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem * menu_item,gdouble x,gdouble y)367 void gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem* menu_item,
368                                                gdouble x, gdouble y) {
369   GtkWidget* new_selected_widget = NULL;
370   GList* current = menu_item->button_widgets;
371   for (; current != NULL; current = current->next) {
372     GtkWidget* current_widget = GTK_WIDGET(current->data);
373     GtkAllocation alloc = current_widget->allocation;
374     int offset_x, offset_y;
375     gtk_widget_translate_coordinates(current_widget, GTK_WIDGET(menu_item),
376                                      0, 0, &offset_x, &offset_y);
377     if (x >= offset_x && x < (offset_x + alloc.width) &&
378         y >= offset_y && y < (offset_y + alloc.height)) {
379       new_selected_widget = current_widget;
380       break;
381     }
382   }
383 
384   set_selected(menu_item, new_selected_widget);
385 }
386 
gtk_custom_menu_item_handle_move(GtkCustomMenuItem * menu_item,GtkMenuDirectionType direction)387 gboolean gtk_custom_menu_item_handle_move(GtkCustomMenuItem* menu_item,
388                                           GtkMenuDirectionType direction) {
389   GtkWidget* current = menu_item->currently_selected_button;
390   if (menu_item->button_widgets && current) {
391     switch (direction) {
392       case GTK_MENU_DIR_PREV: {
393         if (g_list_first(menu_item->button_widgets)->data == current)
394           return FALSE;
395 
396         set_selected(menu_item, GTK_WIDGET(g_list_previous(g_list_find(
397             menu_item->button_widgets, current))->data));
398         break;
399       }
400       case GTK_MENU_DIR_NEXT: {
401         if (g_list_last(menu_item->button_widgets)->data == current)
402           return FALSE;
403 
404         set_selected(menu_item, GTK_WIDGET(g_list_next(g_list_find(
405             menu_item->button_widgets, current))->data));
406         break;
407       }
408       default:
409         break;
410     }
411   }
412 
413   return TRUE;
414 }
415 
gtk_custom_menu_item_select_item_by_direction(GtkCustomMenuItem * menu_item,GtkMenuDirectionType direction)416 void gtk_custom_menu_item_select_item_by_direction(
417     GtkCustomMenuItem* menu_item, GtkMenuDirectionType direction) {
418   menu_item->previously_selected_button = NULL;
419 
420   // If we're just told to be selected by the menu system, select the first
421   // item.
422   if (menu_item->button_widgets) {
423     switch (direction) {
424       case GTK_MENU_DIR_PREV: {
425         GtkWidget* last_button =
426             GTK_WIDGET(g_list_last(menu_item->button_widgets)->data);
427         if (last_button)
428           set_selected(menu_item, last_button);
429         break;
430       }
431       case GTK_MENU_DIR_NEXT: {
432         GtkWidget* first_button =
433             GTK_WIDGET(g_list_first(menu_item->button_widgets)->data);
434         if (first_button)
435           set_selected(menu_item, first_button);
436         break;
437       }
438       default:
439         break;
440     }
441   }
442 
443   gtk_widget_queue_draw(GTK_WIDGET(menu_item));
444 }
445 
gtk_custom_menu_item_is_in_clickable_region(GtkCustomMenuItem * menu_item)446 gboolean gtk_custom_menu_item_is_in_clickable_region(
447     GtkCustomMenuItem* menu_item) {
448   return menu_item->currently_selected_button != NULL;
449 }
450 
gtk_custom_menu_item_try_no_dismiss_command(GtkCustomMenuItem * menu_item)451 gboolean gtk_custom_menu_item_try_no_dismiss_command(
452     GtkCustomMenuItem* menu_item) {
453   GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item);
454   gboolean activated = TRUE;
455 
456   // We work with |currently_selected_button| instead of
457   // |previously_selected_button| since we haven't been "deselect"ed yet.
458   gpointer id_ptr = g_object_get_data(
459       G_OBJECT(custom_item->currently_selected_button), "command-id");
460   if (id_ptr != NULL) {
461     int command_id = GPOINTER_TO_INT(id_ptr);
462     g_signal_emit(custom_item, custom_menu_item_signals[TRY_BUTTON_PUSHED], 0,
463                   command_id, &activated);
464   }
465 
466   return activated;
467 }
468 
gtk_custom_menu_item_foreach_button(GtkCustomMenuItem * menu_item,GtkCallback callback,gpointer callback_data)469 void gtk_custom_menu_item_foreach_button(GtkCustomMenuItem* menu_item,
470                                          GtkCallback callback,
471                                          gpointer callback_data) {
472   // Even though we're filtering |all_widgets| on GTK_IS_BUTTON(), this isn't
473   // equivalent to |button_widgets| because we also want the button-labels.
474   for (GList* i = menu_item->all_widgets; i && GTK_IS_BUTTON(i->data);
475        i = g_list_next(i)) {
476     if (GTK_IS_BUTTON(i->data)) {
477       callback(GTK_WIDGET(i->data), callback_data);
478     }
479   }
480 }
481