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