libyui-gtk  2.44.9
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  gint monitor_num = gdk_screen_get_monitor_at_window (screen, gdk_screen_get_root_window (gtk_widget_get_screen (widget)));
80  GdkRectangle monitor;
81  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
82 
83  if (*x < monitor.x)
84  *x = monitor.x;
85  else if (*x + req.width > monitor.x + monitor.width)
86  *x = monitor.x + monitor.width - req.width;
87 
88  if (*y < monitor.y)
89  *y = monitor.y;
90  else if (*y + req.height > monitor.y + monitor.height)
91  *y = monitor.y + monitor.height - req.height;
92 }
93 
94 void ygtk_popup_window_popup (GtkWidget *widget, gint x, gint y, guint activate_time)
95 {
96  ygtk_popup_window_frame_position (widget, &x, &y);
97 
98  gtk_grab_add (widget);
99  gtk_window_move (GTK_WINDOW (widget), x, y);
100  gtk_widget_grab_focus (widget);
101  gtk_widget_show (widget);
102 
103  GdkWindow *window = gtk_widget_get_window (widget);
104 
105  GdkDisplay *display = gdk_window_get_display (window);
106 
107  // GdkDeviceManager *device_manager = gdk_display_get_device_manager (display);
108  // GdkDevice *pointer = gdk_device_manager_get_client_pointer (device_manager);
109  GdkSeat *seat = gdk_display_get_default_seat (display);
110  GdkDevice *pointer = gdk_seat_get_pointer (seat);
111 
112  // grab this with your teeth
113  if (gdk_device_grab (pointer, window, GDK_OWNERSHIP_NONE, TRUE,
114  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
115  NULL, activate_time) == 0) {
116  GdkDevice *keyboard;
117  keyboard = gdk_device_get_associated_device (pointer);
118  if (gdk_device_grab (keyboard, window, GDK_OWNERSHIP_NONE, TRUE,
119  GDK_KEY_PRESS | GDK_KEY_RELEASE, NULL, activate_time) != 0)
120  gdk_device_ungrab (pointer, activate_time);
121  }
122 }
123 
124 static void ygtk_popup_window_class_init (YGtkPopupWindowClass *klass)
125 {
126  ygtk_popup_window_parent_class = g_type_class_peek_parent (klass);
127 
128  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
129  widget_class->key_press_event = ygtk_popup_window_key_press_event;
130  widget_class->button_press_event = ygtk_popup_window_button_press_event;
131  widget_class->hide = ygtk_popup_window_hide;
132 }
133 
134 //** YGtkMenuButton
135 
136 G_DEFINE_TYPE (YGtkMenuButton, ygtk_menu_button, GTK_TYPE_TOGGLE_BUTTON)
137 
138 static void ygtk_menu_button_init (YGtkMenuButton *button)
139 {
140 }
141 
142 static void ygtk_menu_button_free_popup (YGtkMenuButton *button)
143 {
144  if (button->popup) {
145  gtk_widget_destroy (GTK_WIDGET (button->popup));
146  g_object_unref (G_OBJECT (button->popup));
147  button->popup = NULL;
148  }
149 }
150 
151 static void ygtk_menu_button_finalize (GObject *object)
152 {
153  ygtk_menu_button_free_popup (YGTK_MENU_BUTTON (object));
154  G_OBJECT_CLASS (ygtk_menu_button_parent_class)->finalize (object);
155 }
156 
157 static void ygtk_menu_button_get_popup_pos (YGtkMenuButton *button, gint *x, gint *y)
158 {
159  GtkWidget *widget = GTK_WIDGET (button);
160  GtkAllocation button_alloc;
161  gtk_widget_get_allocation(widget, &button_alloc);
162 
163  // the popup would look awful if smaller than the button
164  GtkRequisition req;
165  gtk_widget_get_preferred_size (button->popup, &req, NULL);
166  int popup_width = req.width, popup_height = req.height;
167  if (button_alloc.width > req.width) {
168  gtk_widget_set_size_request (button->popup, button_alloc.width, -1);
169  popup_width = button_alloc.width;
170  }
171 
172  gdk_window_get_origin (gtk_widget_get_window(widget), x, y);
173  *x += button_alloc.x - popup_width*button->xalign;
174  *y += (button_alloc.y-popup_height) + (button_alloc.height+popup_height)*button->yalign;
175 
176  // GTK doesn't push up menus if they are near the bottom, but we will...
177  int screen_height;
178  screen_height = gdk_screen_get_height (gtk_widget_get_screen (widget));
179  if (*y > screen_height - popup_height)
180  *y -= popup_height + button_alloc.height;
181 }
182 
183 static void ygtk_menu_button_get_menu_pos (GtkMenu *menu, gint *x, gint *y,
184  gboolean *push_in, gpointer data)
185 {
186  ygtk_menu_button_get_popup_pos (YGTK_MENU_BUTTON (data), x, y);
187  *push_in = TRUE;
188 }
189 
190 static void ygtk_menu_button_show_popup (YGtkMenuButton *button)
191 {
192  GtkWidget *popup = button->popup;
193  if (!popup)
194  return;
195 
196  guint activate_time = gtk_get_current_event_time();
197  if (GTK_IS_MENU (popup))
198  // gtk_menu_popup (GTK_MENU (popup), NULL, NULL, ygtk_menu_button_get_menu_pos,
199  // button, 0, activate_time);
200  gtk_menu_popup_at_pointer (GTK_MENU (popup), NULL);
201 
202  else { // GTK_IS_WINDOW
203  gint x, y;
204  ygtk_menu_button_get_popup_pos (button, &x, &y);
205  ygtk_popup_window_popup (popup, x, y, activate_time);
206  }
207 }
208 
209 static void ygtk_menu_button_hide_popup (YGtkMenuButton *button)
210 {
211  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
212 }
213 
214 static void ygtk_menu_button_button_toggle (GtkToggleButton *button)
215 {
216  if (gtk_toggle_button_get_active (button))
217  ygtk_menu_button_show_popup (YGTK_MENU_BUTTON (button));
218  else
219  ygtk_menu_button_hide_popup (YGTK_MENU_BUTTON (button));
220 }
221 
222 static gint ygtk_menu_button_button_press (GtkWidget *widget, GdkEventButton *event)
223 {
224  if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
225  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
226  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
227  ygtk_menu_button_show_popup (YGTK_MENU_BUTTON (widget));
228  }
229  else
230  ygtk_menu_button_hide_popup (YGTK_MENU_BUTTON (widget));
231  return TRUE;
232  }
233  return FALSE;
234 }
235 
236 GtkWidget *ygtk_menu_button_new (void)
237 {
238  return g_object_new (YGTK_TYPE_MENU_BUTTON, NULL);
239 }
240 
241 GtkWidget *ygtk_menu_button_new_with_label (const gchar *label)
242 {
243  GtkWidget *button = ygtk_menu_button_new();
244  ygtk_menu_button_set_label (YGTK_MENU_BUTTON (button), label);
245  return button;
246 }
247 
248 void ygtk_menu_button_set_label (YGtkMenuButton *button, const gchar *label)
249 {
250  if (!button->label) {
251  GtkWidget *hbox, *arrow;
252  hbox = YGTK_HBOX_NEW(4);
253  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
254  // arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN);
255  arrow = gtk_image_new_from_icon_name ("pan-down-symbolic",
256  GTK_ICON_SIZE_BUTTON);
257  button->label = gtk_label_new ("");
258  gtk_box_pack_start (GTK_BOX (hbox), button->label, TRUE, TRUE, 0);
259  gtk_box_pack_start (GTK_BOX (hbox), arrow, FALSE, TRUE, 0);
260  gtk_container_add (GTK_CONTAINER (button), hbox);
261  gtk_widget_show_all (hbox);
262  }
263  if (label && *label) {
264  gtk_widget_show (button->label);
265  gtk_label_set_text_with_mnemonic (GTK_LABEL (button->label), label);
266  }
267  else
268  gtk_widget_hide (button->label);
269 }
270 
271 static void menu_button_hide_popup (GtkWidget *widget, YGtkMenuButton *button)
272 { ygtk_menu_button_hide_popup (button); }
273 
274 void ygtk_menu_button_set_popup_align (YGtkMenuButton *button, GtkWidget *popup,
275  gfloat xalign, gfloat yalign)
276 {
277  ygtk_menu_button_free_popup (button);
278  button->xalign = xalign;
279  button->yalign = yalign;
280 
281  if (!GTK_IS_MENU (popup) && !IS_YGTK_POPUP_WINDOW (popup)) {
282  // install widget on a YGtkPopupMenu
283  button->popup = ygtk_popup_window_new (popup);
284  }
285  else
286  button->popup = popup;
287 
288  g_object_ref_sink (G_OBJECT (button->popup));
289  g_signal_connect (G_OBJECT (button->popup), "hide",
290  G_CALLBACK (menu_button_hide_popup), button);
291 }
292 
293 void ygtk_menu_button_set_popup (YGtkMenuButton *button, GtkWidget *popup)
294 {
295  ygtk_menu_button_set_popup_align (button, popup, 0.0, 1.0);
296 }
297 
298 static void ygtk_menu_button_class_init (YGtkMenuButtonClass *klass)
299 {
300  ygtk_menu_button_parent_class = g_type_class_peek_parent (klass);
301 
302  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
303  gobject_class->finalize = ygtk_menu_button_finalize;
304 
305  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
306  widget_class->button_press_event = ygtk_menu_button_button_press;
307 
308  GtkToggleButtonClass *toggle_button_class = GTK_TOGGLE_BUTTON_CLASS (klass);
309  toggle_button_class->toggled = ygtk_menu_button_button_toggle;
310 }
_YGtkMenuButtonClass
Definition: ygtkmenubutton.h:74
_YGtkMenuButton
Definition: ygtkmenubutton.h:65
_YGtkPopupWindow
Definition: ygtkmenubutton.h:29
_YGtkPopupWindowClass
Definition: ygtkmenubutton.h:34