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.
5 #include "chrome/browser/ui/gtk/gtk_custom_menu_item.h"
7 #include "base/i18n/rtl.h"
8 #include "chrome/browser/ui/gtk/gtk_custom_menu.h"
9 #include "ui/gfx/gtk_compat.h"
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
,
18 const GValue
* param_values
,
19 gpointer invocation_hint G_GNUC_UNUSED
,
20 gpointer marshal_data
) {
21 typedef gboolean(*GMarshalFunc_BOOLEAN__INT
)(gpointer data1
,
24 register GMarshalFunc_BOOLEAN__INT callback
;
25 register GCClosure
*cc
= (GCClosure
*)closure
;
26 register gpointer data1
, data2
;
29 g_return_if_fail(return_value
!= NULL
);
30 g_return_if_fail(n_param_values
== 2);
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.
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
;
46 data1
= (param_values
+ 0)->data
[0].v_pointer
;
47 data2
= closure
->data
;
49 callback
= (GMarshalFunc_BOOLEAN__INT
)(marshal_data
? marshal_data
:
52 v_return
= callback(data1
,
53 g_value_get_int(param_values
+ 1),
56 g_value_set_boolean(return_value
, v_return
);
65 static guint custom_menu_item_signals
[LAST_SIGNAL
] = { 0 };
67 G_DEFINE_TYPE(GtkCustomMenuItem
, gtk_custom_menu_item
, GTK_TYPE_MENU_ITEM
)
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
);
74 gtk_bin_get_child(GTK_BIN(item
->currently_selected_button
)),
78 item
->currently_selected_button
= selected
;
79 if (item
->currently_selected_button
) {
80 gtk_widget_set_state(item
->currently_selected_button
, GTK_STATE_SELECTED
);
82 gtk_bin_get_child(GTK_BIN(item
->currently_selected_button
)),
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);
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
);
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
;
116 GtkWidget
* menu_hbox
= gtk_hbox_new(FALSE
, 0);
117 gtk_container_add(GTK_CONTAINER(item
), menu_hbox
);
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);
123 item
->hbox
= gtk_hbox_new(FALSE
, 0);
124 gtk_box_pack_end(GTK_BOX(menu_hbox
), item
->hbox
, FALSE
, FALSE
, 0);
126 g_signal_connect(item
->hbox
, "expose-event",
127 G_CALLBACK(gtk_custom_menu_item_hbox_expose
),
130 gtk_widget_show_all(menu_hbox
);
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
);
139 gobject_class
->finalize
= gtk_custom_menu_item_finalize
;
141 widget_class
->expose_event
= gtk_custom_menu_item_expose
;
143 item_class
->select
= gtk_custom_menu_item_select
;
144 item_class
->deselect
= gtk_custom_menu_item_deselect
;
146 menu_item_class
->activate
= gtk_custom_menu_item_activate
;
148 custom_menu_item_signals
[BUTTON_PUSHED
] =
149 g_signal_new("button-pushed",
150 G_TYPE_FROM_CLASS(gobject_class
),
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
),
162 chrome_marshall_BOOLEAN__INT
,
163 G_TYPE_BOOLEAN
, 1, G_TYPE_INT
);
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
);
171 G_OBJECT_CLASS(gtk_custom_menu_item_parent_class
)->finalize(object
);
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
)),
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
);
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
);
207 if (base::i18n::IsRTL())
208 std::swap(first_button
, last_button
);
210 GtkAllocation first_allocation
;
211 gtk_widget_get_allocation(first_button
, &first_allocation
);
212 GtkAllocation current_allocation
;
213 gtk_widget_get_allocation(current_button
, ¤t_allocation
);
214 GtkAllocation last_allocation
;
215 gtk_widget_get_allocation(last_button
, &last_allocation
);
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
;
222 gtk_paint_box(gtk_widget_get_style(hbox
),
223 gtk_widget_get_window(hbox
),
224 gtk_widget_get_state(current_button
),
226 ¤t_allocation
, hbox
, "button",
227 x
, y
, width
, height
);
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
)),
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
);
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
)),
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",
272 child_alloc
.y
+ child_alloc
.height
,
273 button_allocation
.x
+
274 button_allocation
.width
- half_offset
);
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
);
284 gtk_custom_menu_item_expose_button(widget
, event
, selected
);
290 static void gtk_custom_menu_item_select(GtkItem
* item
) {
291 GtkCustomMenuItem
* custom_item
= GTK_CUSTOM_MENU_ITEM(item
);
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
;
299 gtk_widget_queue_draw(GTK_WIDGET(item
));
302 static void gtk_custom_menu_item_deselect(GtkItem
* item
) {
303 GtkCustomMenuItem
* custom_item
= GTK_CUSTOM_MENU_ITEM(item
);
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
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
);
313 gtk_widget_queue_draw(GTK_WIDGET(item
));
316 static void gtk_custom_menu_item_activate(GtkMenuItem
* menu_item
) {
317 GtkCustomMenuItem
* custom_item
= GTK_CUSTOM_MENU_ITEM(menu_item
);
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,
328 set_selected(custom_item
, NULL
);
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
);
340 GtkWidget
* gtk_custom_menu_item_add_button(GtkCustomMenuItem
* menu_item
,
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
);
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
);
354 GtkWidget
* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem
* menu_item
,
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
);
364 menu_item
->all_widgets
= g_list_append(menu_item
->all_widgets
, button
);
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);
373 gtk_box_pack_start(GTK_BOX(menu_item
->hbox
), fixed
, FALSE
, FALSE
, 0);
374 gtk_widget_show(fixed
);
376 menu_item
->all_widgets
= g_list_append(menu_item
->all_widgets
, fixed
);
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
);
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
;
397 set_selected(menu_item
, new_selected_widget
);
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
) {
405 case GTK_MENU_DIR_PREV
: {
406 if (g_list_first(menu_item
->button_widgets
)->data
== current
)
409 set_selected(menu_item
, GTK_WIDGET(g_list_previous(g_list_find(
410 menu_item
->button_widgets
, current
))->data
));
413 case GTK_MENU_DIR_NEXT
: {
414 if (g_list_last(menu_item
->button_widgets
)->data
== current
)
417 set_selected(menu_item
, GTK_WIDGET(g_list_next(g_list_find(
418 menu_item
->button_widgets
, current
))->data
));
429 void gtk_custom_menu_item_select_item_by_direction(
430 GtkCustomMenuItem
* menu_item
, GtkMenuDirectionType direction
) {
431 menu_item
->previously_selected_button
= NULL
;
433 // If we're just told to be selected by the menu system, select the first
435 if (menu_item
->button_widgets
) {
437 case GTK_MENU_DIR_PREV
: {
438 GtkWidget
* last_button
=
439 GTK_WIDGET(g_list_last(menu_item
->button_widgets
)->data
);
441 set_selected(menu_item
, last_button
);
444 case GTK_MENU_DIR_NEXT
: {
445 GtkWidget
* first_button
=
446 GTK_WIDGET(g_list_first(menu_item
->button_widgets
)->data
);
448 set_selected(menu_item
, first_button
);
456 gtk_widget_queue_draw(GTK_WIDGET(menu_item
));
459 gboolean
gtk_custom_menu_item_is_in_clickable_region(
460 GtkCustomMenuItem
* menu_item
) {
461 return menu_item
->currently_selected_button
!= NULL
;
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
;
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
);
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
);