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