libyui-gtk  2.49.0
ygtkmenubutton.c
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 /* YGtkMenuButton widget */
6 // check the header file for information about this widget
7 
8 #include <yui/Libyui_config.h>
9 #include "ygtkmenubutton.h"
10 #include <gtk/gtk.h>
11 #include <gdk/gdkkeysyms.h>
12 #include "YGMacros.h"
13 
14 //** YGtkPopupWindow
15 
16 G_DEFINE_TYPE (YGtkPopupWindow, ygtk_popup_window, GTK_TYPE_WINDOW)
17 
18 static void ygtk_popup_window_init (YGtkPopupWindow *popup)
19 {
20  GtkWindow *window = GTK_WINDOW (popup);
21  gtk_window_set_resizable (window, FALSE);
22 
23  GtkWidget *frame = gtk_frame_new (NULL);
24  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
25  gtk_widget_show (frame);
26  gtk_container_add (GTK_CONTAINER (window), frame);
27 }
28 
29 static void ygtk_popup_window_hide (GtkWidget *widget)
30 {
31  gtk_grab_remove (widget);
32  GTK_WIDGET_CLASS (ygtk_popup_window_parent_class)->hide (widget);
33 }
34 
35 static gboolean ygtk_popup_window_key_press_event (GtkWidget *widget, GdkEventKey *event)
36 {
37  if (event->keyval == GDK_KEY_Escape) {
38  gtk_widget_hide (widget);
39  return TRUE;
40  }
41  return GTK_WIDGET_CLASS (ygtk_popup_window_parent_class)->key_press_event
42  (widget, event);
43 }
44 
45 static gboolean ygtk_popup_window_button_press_event (GtkWidget *widget,
46  GdkEventButton *event)
47 {
48  // NOTE: You can't rely on events x and y since the event may take place
49  // outside of the window.
50  // So, we'll check if this widget (or any of its kids) got the event
51  // If that's not the case, close the menu
52 
53  GtkWidget *child = gtk_get_event_widget ((GdkEvent *) event);
54  if (child != widget)
55  while (child) {
56  if (child == widget)
57  return FALSE;
58  child = gtk_widget_get_parent(child);
59  }
60  gtk_widget_hide (widget);
61  return TRUE;
62 }
63 
64 GtkWidget *ygtk_popup_window_new (GtkWidget *child)
65 {
66  GtkWidget *widget = g_object_new (YGTK_TYPE_POPUP_WINDOW,
67  "type", GTK_WINDOW_POPUP, NULL);
68  GtkWidget *frame = gtk_bin_get_child (GTK_BIN (widget));
69  gtk_container_add (GTK_CONTAINER (frame), child);
70  return widget;
71 }
72 
73 static void ygtk_popup_window_frame_position (GtkWidget *widget, gint *x, gint *y)
74 { // don't let it go outside the screen
75  GtkRequisition req;
76  gtk_widget_get_preferred_size(widget, &req, NULL);
77 
78  GdkScreen *screen = gtk_widget_get_screen (widget);
79  GdkRectangle monitor;
80 
81 # if GTK_CHECK_VERSION (3, 22, 0)
82  GdkMonitor *monitor_num = gdk_display_get_monitor_at_window (
83  gdk_screen_get_display (screen),
84  gdk_screen_get_root_window (screen));
85  gdk_monitor_get_geometry (monitor_num, &monitor);
86 # elif GTK_CHECK_VERSION (3, 12, 0)
87  gint monitor_num = gdk_screen_get_monitor_at_window (screen,
88  gdk_screen_get_root_window (screen));
89  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
90 # else
91  gint monitor_num = gdk_screen_get_monitor_at_window (screen,
92  gtk_widget_get_root_window (widget));
93  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
94 # endif
95 
96  if (*x < monitor.x)
97  *x = monitor.x;
98  else if (*x + req.width > monitor.x + monitor.width)
99  *x = monitor.x + monitor.width - req.width;
100 
101  if (*y < monitor.y)
102  *y = monitor.y;
103  else if (*y + req.height > monitor.y + monitor.height)
104  *y = monitor.y + monitor.height - req.height;
105 }
106 
107 void ygtk_popup_window_popup (GtkWidget *widget, gint x, gint y, guint activate_time)
108 {
109  ygtk_popup_window_frame_position (widget, &x, &y);
110 
111  gtk_grab_add (widget);
112  gtk_window_move (GTK_WINDOW (widget), x, y);
113  gtk_widget_grab_focus (widget);
114  gtk_widget_show (widget);
115 
116  GdkWindow *window = gtk_widget_get_window (widget);
117 
118  GdkDisplay *display = gdk_window_get_display (window);
119 
120 # if GTK_CHECK_VERSION (3, 20, 0)
121  GdkSeat *seat = gdk_display_get_default_seat (display);
122  GdkDevice *pointer = gdk_seat_get_pointer (seat);
123 # else
124  GdkDeviceManager *device_manager = gdk_display_get_device_manager (display);
125  GdkDevice *pointer = gdk_device_manager_get_client_pointer (device_manager);
126 # endif
127 
128  // grab this with your teeth
129  if (gdk_device_grab (pointer, window, GDK_OWNERSHIP_NONE, TRUE,
130  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
131  NULL, activate_time) == 0) {
132  GdkDevice *keyboard;
133  keyboard = gdk_device_get_associated_device (pointer);
134  if (gdk_device_grab (keyboard, window, GDK_OWNERSHIP_NONE, TRUE,
135  GDK_KEY_PRESS | GDK_KEY_RELEASE, NULL, activate_time) != 0)
136  gdk_device_ungrab (pointer, activate_time);
137  }
138 }
139 
140 static void ygtk_popup_window_class_init (YGtkPopupWindowClass *klass)
141 {
142  ygtk_popup_window_parent_class = g_type_class_peek_parent (klass);
143 
144  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
145  widget_class->key_press_event = ygtk_popup_window_key_press_event;
146  widget_class->button_press_event = ygtk_popup_window_button_press_event;
147  widget_class->hide = ygtk_popup_window_hide;
148 }
149 
150 //** YGtkMenuButton
151 
152 G_DEFINE_TYPE (YGtkMenuButton, ygtk_menu_button, GTK_TYPE_TOGGLE_BUTTON)
153 
154 static void ygtk_menu_button_init (YGtkMenuButton *button)
155 {
156 }
157 
158 static void ygtk_menu_button_free_popup (YGtkMenuButton *button)
159 {
160  if (button->popup) {
161  gtk_widget_destroy (GTK_WIDGET (button->popup));
162  g_object_unref (G_OBJECT (button->popup));
163  button->popup = NULL;
164  }
165 }
166 
167 static void ygtk_menu_button_finalize (GObject *object)
168 {
169  ygtk_menu_button_free_popup (YGTK_MENU_BUTTON (object));
170  G_OBJECT_CLASS (ygtk_menu_button_parent_class)->finalize (object);
171 }
172 
173 static void ygtk_menu_button_get_popup_pos (YGtkMenuButton *button, gint *x, gint *y)
174 {
175  GtkWidget *widget = GTK_WIDGET (button);
176  GtkAllocation button_alloc;
177  gtk_widget_get_allocation(widget, &button_alloc);
178 
179  // the popup would look awful if smaller than the button
180  GtkRequisition req;
181  gtk_widget_get_preferred_size (button->popup, &req, NULL);
182  int popup_width = req.width, popup_height = req.height;
183  if (button_alloc.width > req.width) {
184  gtk_widget_set_size_request (button->popup, button_alloc.width, -1);
185  popup_width = button_alloc.width;
186  }
187 
188  gdk_window_get_origin (gtk_widget_get_window(widget), x, y);
189  *x += button_alloc.x - popup_width*button->xalign;
190  *y += (button_alloc.y-popup_height) + (button_alloc.height+popup_height)*button->yalign;
191 
192  // GTK doesn't push up menus if they are near the bottom, but we will...
193 # if GTK_CHECK_VERSION (3, 22, 0)
194  GdkScreen *screen = gtk_widget_get_screen (widget);
195  GdkRectangle monitor;
196 
197  GdkMonitor *monitor_num = gdk_display_get_monitor_at_window (
198  gdk_screen_get_display (screen),
199  gdk_screen_get_root_window (screen));
200  gdk_monitor_get_geometry (monitor_num, &monitor);
201 
202  int screen_height = monitor.y;
203 # else
204  int screen_height = gdk_screen_get_height (gtk_widget_get_screen (widget));
205 # endif
206 
207  if (*y > screen_height - popup_height)
208  *y -= popup_height + button_alloc.height;
209 }
210 
211 #if GTK_CHECK_VERSION (3, 22, 0)
212 #else
213 static void ygtk_menu_button_get_menu_pos (GtkMenu *menu, gint *x, gint *y,
214  gboolean *push_in, gpointer data)
215 {
216  ygtk_menu_button_get_popup_pos (YGTK_MENU_BUTTON (data), x, y);
217  *push_in = TRUE;
218 }
219 #endif
220 
221 static void ygtk_menu_button_show_popup (YGtkMenuButton *button)
222 {
223  GtkWidget *popup = button->popup;
224  if (!popup)
225  return;
226 
227  guint activate_time = gtk_get_current_event_time();
228  if (GTK_IS_MENU (popup))
229 
230 # if GTK_CHECK_VERSION (3, 22, 0)
231  gtk_menu_popup_at_pointer (GTK_MENU (popup), NULL);
232 # else
233  gtk_menu_popup (GTK_MENU (popup), NULL, NULL,
234  ygtk_menu_button_get_menu_pos,
235  button, 0, activate_time);
236 # endif
237 
238  else { // GTK_IS_WINDOW
239  gint x, y;
240  ygtk_menu_button_get_popup_pos (button, &x, &y);
241  ygtk_popup_window_popup (popup, x, y, activate_time);
242  }
243 }
244 
245 static void ygtk_menu_button_hide_popup (YGtkMenuButton *button)
246 {
247  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
248 }
249 
250 static void ygtk_menu_button_button_toggle (GtkToggleButton *button)
251 {
252  if (gtk_toggle_button_get_active (button))
253  ygtk_menu_button_show_popup (YGTK_MENU_BUTTON (button));
254  else
255  ygtk_menu_button_hide_popup (YGTK_MENU_BUTTON (button));
256 }
257 
258 static gint ygtk_menu_button_button_press (GtkWidget *widget, GdkEventButton *event)
259 {
260  if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
261  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
262  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
263  ygtk_menu_button_show_popup (YGTK_MENU_BUTTON (widget));
264  }
265  else
266  ygtk_menu_button_hide_popup (YGTK_MENU_BUTTON (widget));
267  return TRUE;
268  }
269  return FALSE;
270 }
271 
272 GtkWidget *ygtk_menu_button_new (void)
273 {
274  return g_object_new (YGTK_TYPE_MENU_BUTTON, NULL);
275 }
276 
277 GtkWidget *ygtk_menu_button_new_with_label (const gchar *label)
278 {
279  GtkWidget *button = ygtk_menu_button_new();
280  ygtk_menu_button_set_label (YGTK_MENU_BUTTON (button), label);
281  return button;
282 }
283 
284 void ygtk_menu_button_set_label (YGtkMenuButton *button, const gchar *label)
285 {
286  if (!button->label) {
287  GtkWidget *hbox, *arrow;
288  hbox = YGTK_HBOX_NEW(4);
289  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
290 
291 # if GTK_CHECK_VERSION (3, 14, 0)
292  arrow = gtk_image_new_from_icon_name ("pan-down-symbolic",
293  GTK_ICON_SIZE_BUTTON);
294 # else
295  arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN);
296 # endif
297 
298  button->label = gtk_label_new ("");
299  gtk_box_pack_start (GTK_BOX (hbox), button->label, TRUE, TRUE, 0);
300  gtk_box_pack_start (GTK_BOX (hbox), arrow, FALSE, TRUE, 0);
301  gtk_container_add (GTK_CONTAINER (button), hbox);
302  gtk_widget_show_all (hbox);
303  }
304  if (label && *label) {
305  gtk_widget_show (button->label);
306  gtk_label_set_text_with_mnemonic (GTK_LABEL (button->label), label);
307  }
308  else
309  gtk_widget_hide (button->label);
310 }
311 
312 static void menu_button_hide_popup (GtkWidget *widget, YGtkMenuButton *button)
313 { ygtk_menu_button_hide_popup (button); }
314 
315 void ygtk_menu_button_set_popup_align (YGtkMenuButton *button, GtkWidget *popup,
316  gfloat xalign, gfloat yalign)
317 {
318  ygtk_menu_button_free_popup (button);
319  button->xalign = xalign;
320  button->yalign = yalign;
321 
322  if (!GTK_IS_MENU (popup) && !IS_YGTK_POPUP_WINDOW (popup)) {
323  // install widget on a YGtkPopupMenu
324  button->popup = ygtk_popup_window_new (popup);
325  }
326  else
327  button->popup = popup;
328 
329  g_object_ref_sink (G_OBJECT (button->popup));
330  g_signal_connect (G_OBJECT (button->popup), "hide",
331  G_CALLBACK (menu_button_hide_popup), button);
332 }
333 
334 void ygtk_menu_button_set_popup (YGtkMenuButton *button, GtkWidget *popup)
335 {
336  ygtk_menu_button_set_popup_align (button, popup, 0.0, 1.0);
337 }
338 
339 static void ygtk_menu_button_class_init (YGtkMenuButtonClass *klass)
340 {
341  ygtk_menu_button_parent_class = g_type_class_peek_parent (klass);
342 
343  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
344  gobject_class->finalize = ygtk_menu_button_finalize;
345 
346  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
347  widget_class->button_press_event = ygtk_menu_button_button_press;
348 
349  GtkToggleButtonClass *toggle_button_class = GTK_TOGGLE_BUTTON_CLASS (klass);
350  toggle_button_class->toggled = ygtk_menu_button_button_toggle;
351 }
_YGtkMenuButtonClass
Definition: ygtkmenubutton.h:75
_YGtkMenuButton
Definition: ygtkmenubutton.h:66
_YGtkPopupWindow
Definition: ygtkmenubutton.h:30
_YGtkPopupWindowClass
Definition: ygtkmenubutton.h:35