1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /* eel-gtk-extensions.c - implementation of new functions that operate on
4 gtk classes. Perhaps some of these should be
5 rolled into gtk someday.
6
7 Copyright (C) 1999, 2000, 2001 Eazel, Inc.
8
9 The Mate Library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
13
14 The Mate Library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public
20 License along with the Mate Library; see the file COPYING.LIB. If not,
21 write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23
24 Authors: John Sullivan <sullivan@eazel.com>
25 Ramiro Estrugo <ramiro@eazel.com>
26 Darin Adler <darin@eazel.com>
27 */
28
29 #include <config.h>
30 #include "eel-gtk-extensions.h"
31
32 #include "eel-gdk-pixbuf-extensions.h"
33 #include "eel-glib-extensions.h"
34 #include "eel-mate-extensions.h"
35 #include "eel-marshal.h"
36 #include "eel-string.h"
37
38 #include <X11/Xlib.h>
39 #include <X11/Xatom.h>
40 #include <gdk/gdk.h>
41 #include <gdk/gdkprivate.h>
42 #include <gdk/gdkx.h>
43 #include <gtk/gtk.h>
44 #include <glib/gi18n-lib.h>
45 #include <math.h>
46
47 /* Used for window position & size sanity-checking. The sizes are big enough to prevent
48 * at least normal-sized mate panels from obscuring the window at the screen edges.
49 */
50 #define MINIMUM_ON_SCREEN_WIDTH 100
51 #define MINIMUM_ON_SCREEN_HEIGHT 100
52
53
54 /**
55 * eel_gtk_window_get_geometry_string:
56 * @window: a #GtkWindow
57 *
58 * Obtains the geometry string for this window, suitable for
59 * set_geometry_string(); assumes the window has NorthWest gravity
60 *
61 * Return value: geometry string, must be freed
62 **/
63 char*
eel_gtk_window_get_geometry_string(GtkWindow * window)64 eel_gtk_window_get_geometry_string (GtkWindow *window)
65 {
66 char *str;
67 int w, h, x, y;
68
69 g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
70 g_return_val_if_fail (gtk_window_get_gravity (window) ==
71 GDK_GRAVITY_NORTH_WEST, NULL);
72
73 gtk_window_get_position (window, &x, &y);
74 gtk_window_get_size (window, &w, &h);
75
76 str = g_strdup_printf ("%dx%d+%d+%d", w, h, x, y);
77
78 return str;
79 }
80
81 static void
sanity_check_window_position(int * left,int * top)82 sanity_check_window_position (int *left, int *top)
83 {
84 GdkScreen *screen;
85 gint scale;
86
87 g_assert (left != NULL);
88 g_assert (top != NULL);
89
90 screen = gdk_screen_get_default ();
91 scale = gdk_window_get_scale_factor (gdk_screen_get_root_window (screen));
92
93 /* Make sure the top of the window is on screen, for
94 * draggability (might not be necessary with all window managers,
95 * but seems reasonable anyway). Make sure the top of the window
96 * isn't off the bottom of the screen, or so close to the bottom
97 * that it might be obscured by the panel.
98 */
99 *top = CLAMP (*top, 0, HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale - MINIMUM_ON_SCREEN_HEIGHT);
100
101 /* FIXME bugzilla.eazel.com 669:
102 * If window has negative left coordinate, set_uposition sends it
103 * somewhere else entirely. Not sure what level contains this bug (XWindows?).
104 * Hacked around by pinning the left edge to zero, which just means you
105 * can't set a window to be partly off the left of the screen using
106 * this routine.
107 */
108 /* Make sure the left edge of the window isn't off the right edge of
109 * the screen, or so close to the right edge that it might be
110 * obscured by the panel.
111 */
112 *left = CLAMP (*left, 0, WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale - MINIMUM_ON_SCREEN_WIDTH);
113 }
114
115 static void
sanity_check_window_dimensions(guint * width,guint * height)116 sanity_check_window_dimensions (guint *width, guint *height)
117 {
118 GdkScreen *screen;
119 gint scale;
120
121 g_assert (width != NULL);
122 g_assert (height != NULL);
123
124 screen = gdk_screen_get_default ();
125 scale = gdk_window_get_scale_factor (gdk_screen_get_root_window (screen));
126
127 /* Pin the size of the window to the screen, so we don't end up in
128 * a state where the window is so big essential parts of it can't
129 * be reached (might not be necessary with all window managers,
130 * but seems reasonable anyway).
131 */
132 *width = MIN (*width, WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale);
133 *height = MIN (*height, HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale);
134 }
135
136 /**
137 * eel_gtk_window_set_initial_geometry:
138 *
139 * Sets the position and size of a GtkWindow before the
140 * GtkWindow is shown. It is an error to call this on a window that
141 * is already on-screen. Takes into account screen size, and does
142 * some sanity-checking on the passed-in values.
143 *
144 * @window: A non-visible GtkWindow
145 * @geometry_flags: A EelGdkGeometryFlags value defining which of
146 * the following parameters have defined values
147 * @left: pixel coordinate for left of window
148 * @top: pixel coordinate for top of window
149 * @width: width of window in pixels
150 * @height: height of window in pixels
151 */
152 void
eel_gtk_window_set_initial_geometry(GtkWindow * window,EelGdkGeometryFlags geometry_flags,int left,int top,guint width,guint height)153 eel_gtk_window_set_initial_geometry (GtkWindow *window,
154 EelGdkGeometryFlags geometry_flags,
155 int left,
156 int top,
157 guint width,
158 guint height)
159 {
160 int real_left, real_top;
161
162 g_return_if_fail (GTK_IS_WINDOW (window));
163
164 /* Setting the default size doesn't work when the window is already showing.
165 * Someday we could make this move an already-showing window, but we don't
166 * need that functionality yet.
167 */
168 g_return_if_fail (!gtk_widget_get_visible (GTK_WIDGET (window)));
169
170 if ((geometry_flags & EEL_GDK_X_VALUE) && (geometry_flags & EEL_GDK_Y_VALUE))
171 {
172 GdkScreen *screen;
173 int screen_width, screen_height;
174 int scale;
175
176 real_left = left;
177 real_top = top;
178
179 screen = gtk_window_get_screen (window);
180 scale = gtk_widget_get_scale_factor (GTK_WIDGET (window));
181 screen_width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
182 screen_height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
183
184 /* This is sub-optimal. GDK doesn't allow us to set win_gravity
185 * to South/East types, which should be done if using negative
186 * positions (so that the right or bottom edge of the window
187 * appears at the specified position, not the left or top).
188 * However it does seem to be consistent with other MATE apps.
189 */
190 if (geometry_flags & EEL_GDK_X_NEGATIVE)
191 {
192 real_left = screen_width - real_left;
193 }
194 if (geometry_flags & EEL_GDK_Y_NEGATIVE)
195 {
196 real_top = screen_height - real_top;
197 }
198
199 sanity_check_window_position (&real_left, &real_top);
200 gtk_window_move (window, real_left, real_top);
201 }
202
203 if ((geometry_flags & EEL_GDK_WIDTH_VALUE) && (geometry_flags & EEL_GDK_HEIGHT_VALUE))
204 {
205 sanity_check_window_dimensions (&width, &height);
206 gtk_window_set_default_size (GTK_WINDOW (window), (int)width, (int)height);
207 }
208 }
209
210 /**
211 * eel_gtk_window_set_initial_geometry_from_string:
212 *
213 * Sets the position and size of a GtkWindow before the
214 * GtkWindow is shown. The geometry is passed in as a string.
215 * It is an error to call this on a window that
216 * is already on-screen. Takes into account screen size, and does
217 * some sanity-checking on the passed-in values.
218 *
219 * @window: A non-visible GtkWindow
220 * @geometry_string: A string suitable for use with eel_gdk_parse_geometry
221 * @minimum_width: If the width from the string is smaller than this,
222 * use this for the width.
223 * @minimum_height: If the height from the string is smaller than this,
224 * use this for the height.
225 * @ignore_position: If true position data from string will be ignored.
226 */
227 void
eel_gtk_window_set_initial_geometry_from_string(GtkWindow * window,const char * geometry_string,guint minimum_width,guint minimum_height,gboolean ignore_position)228 eel_gtk_window_set_initial_geometry_from_string (GtkWindow *window,
229 const char *geometry_string,
230 guint minimum_width,
231 guint minimum_height,
232 gboolean ignore_position)
233 {
234 int left, top;
235 guint width, height;
236 EelGdkGeometryFlags geometry_flags;
237
238 g_return_if_fail (GTK_IS_WINDOW (window));
239 g_return_if_fail (geometry_string != NULL);
240
241 /* Setting the default size doesn't work when the window is already showing.
242 * Someday we could make this move an already-showing window, but we don't
243 * need that functionality yet.
244 */
245 g_return_if_fail (!gtk_widget_get_visible (GTK_WIDGET (window)));
246
247 geometry_flags = eel_gdk_parse_geometry (geometry_string, &left, &top, &width, &height);
248
249 /* Make sure the window isn't smaller than makes sense for this window.
250 * Other sanity checks are performed in set_initial_geometry.
251 */
252 if (geometry_flags & EEL_GDK_WIDTH_VALUE)
253 {
254 width = MAX (width, minimum_width);
255 }
256 if (geometry_flags & EEL_GDK_HEIGHT_VALUE)
257 {
258 height = MAX (height, minimum_height);
259 }
260
261 /* Ignore saved window position if requested. */
262 if (ignore_position)
263 {
264 geometry_flags &= ~(EEL_GDK_X_VALUE | EEL_GDK_Y_VALUE);
265 }
266
267 eel_gtk_window_set_initial_geometry (window, geometry_flags, left, top, width, height);
268 }
269
270 /**
271 * eel_pop_up_context_menu:
272 *
273 * Pop up a context menu under the mouse.
274 * The menu is sunk after use, so it will be destroyed unless the
275 * caller first ref'ed it.
276 *
277 * This function is more of a helper function than a gtk extension,
278 * so perhaps it belongs in a different file.
279 *
280 * @menu: The menu to pop up under the mouse.
281 * @offset_x: Number of pixels to displace the popup menu vertically
282 * @offset_y: Number of pixels to displace the popup menu horizontally
283 * @event: The event that invoked this popup menu.
284 **/
285 void
eel_pop_up_context_menu(GtkMenu * menu,GdkEventButton * event)286 eel_pop_up_context_menu (GtkMenu *menu,
287 GdkEventButton *event)
288 {
289 g_return_if_fail (GTK_IS_MENU (menu));
290
291 gtk_menu_popup_at_pointer (menu, (const GdkEvent*) event);
292
293 g_object_ref_sink (menu);
294 g_object_unref (menu);
295 }
296
297 GtkMenuItem *
eel_gtk_menu_append_separator(GtkMenu * menu)298 eel_gtk_menu_append_separator (GtkMenu *menu)
299 {
300 return eel_gtk_menu_insert_separator (menu, -1);
301 }
302
303 GtkMenuItem *
eel_gtk_menu_insert_separator(GtkMenu * menu,int index)304 eel_gtk_menu_insert_separator (GtkMenu *menu, int index)
305 {
306 GtkWidget *menu_item;
307
308 menu_item = gtk_separator_menu_item_new ();
309 gtk_widget_show (menu_item);
310 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menu_item, index);
311
312 return GTK_MENU_ITEM (menu_item);
313 }
314
315 GtkWidget *
eel_gtk_menu_tool_button_get_button(GtkMenuToolButton * tool_button)316 eel_gtk_menu_tool_button_get_button (GtkMenuToolButton *tool_button)
317 {
318 GtkContainer *container;
319 GList *children;
320 GtkWidget *button;
321
322 g_return_val_if_fail (GTK_IS_MENU_TOOL_BUTTON (tool_button), NULL);
323
324 /* The menu tool button's button is the first child
325 * of the child hbox. */
326 container = GTK_CONTAINER (gtk_bin_get_child (GTK_BIN (tool_button)));
327 children = gtk_container_get_children (container);
328 button = GTK_WIDGET (children->data);
329
330 g_list_free (children);
331
332 return button;
333 }
334
335 /**
336 * eel_gtk_label_make_bold.
337 *
338 * Switches the font of label to a bold equivalent.
339 * @label: The label.
340 **/
341 void
eel_gtk_label_make_bold(GtkLabel * label)342 eel_gtk_label_make_bold (GtkLabel *label)
343 {
344 PangoFontDescription *font_desc;
345
346 font_desc = pango_font_description_new ();
347
348 pango_font_description_set_weight (font_desc,
349 PANGO_WEIGHT_BOLD);
350
351 /* This will only affect the weight of the font, the rest is
352 * from the current state of the widget, which comes from the
353 * theme or user prefs, since the font desc only has the
354 * weight flag turned on.
355 */
356 PangoAttrList *attrs = pango_attr_list_new ();
357 PangoAttribute *font_desc_attr = pango_attr_font_desc_new (font_desc);
358 pango_attr_list_insert (attrs, font_desc_attr);
359 gtk_label_set_attributes (label, attrs);
360 pango_attr_list_unref (attrs);
361
362 pango_font_description_free (font_desc);
363 }
364
365 static gboolean
tree_view_button_press_callback(GtkWidget * tree_view,GdkEventButton * event,gpointer data)366 tree_view_button_press_callback (GtkWidget *tree_view,
367 GdkEventButton *event,
368 gpointer data)
369 {
370 GtkTreePath *path;
371 GtkTreeViewColumn *column;
372
373 if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
374 {
375 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (tree_view),
376 event->x, event->y,
377 &path,
378 &column,
379 NULL,
380 NULL))
381 {
382 gtk_tree_view_row_activated
383 (GTK_TREE_VIEW (tree_view), path, column);
384 gtk_tree_path_free (path);
385 }
386 }
387
388 return FALSE;
389 }
390
391 void
eel_gtk_tree_view_set_activate_on_single_click(GtkTreeView * tree_view,gboolean should_activate)392 eel_gtk_tree_view_set_activate_on_single_click (GtkTreeView *tree_view,
393 gboolean should_activate)
394 {
395 guint button_press_id;
396
397 button_press_id = GPOINTER_TO_UINT
398 (g_object_get_data (G_OBJECT (tree_view),
399 "eel-tree-view-activate"));
400
401 if (button_press_id && !should_activate)
402 {
403 g_signal_handler_disconnect (tree_view, button_press_id);
404 g_object_set_data (G_OBJECT (tree_view),
405 "eel-tree-view-activate",
406 NULL);
407 }
408 else if (!button_press_id && should_activate)
409 {
410 button_press_id = g_signal_connect
411 (tree_view,
412 "button_press_event",
413 G_CALLBACK (tree_view_button_press_callback),
414 NULL);
415 g_object_set_data (G_OBJECT (tree_view),
416 "eel-tree-view-activate",
417 GUINT_TO_POINTER (button_press_id));
418 }
419 }
420
421 void
eel_gtk_message_dialog_set_details_label(GtkMessageDialog * dialog,const gchar * details_text)422 eel_gtk_message_dialog_set_details_label (GtkMessageDialog *dialog,
423 const gchar *details_text)
424 {
425 GtkWidget *content_area, *expander, *label;
426
427 content_area = gtk_message_dialog_get_message_area (dialog);
428 expander = gtk_expander_new_with_mnemonic (_("Show more _details"));
429 gtk_expander_set_spacing (GTK_EXPANDER (expander), 6);
430
431 label = gtk_label_new (details_text);
432 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
433 gtk_label_set_selectable (GTK_LABEL (label), TRUE);
434 gtk_label_set_xalign (GTK_LABEL (label), 0);
435
436 gtk_container_add (GTK_CONTAINER (expander), label);
437 gtk_box_pack_start (GTK_BOX (content_area), expander, FALSE, FALSE, 0);
438
439 gtk_widget_show (label);
440 gtk_widget_show (expander);
441 }
442
443 GtkWidget *
eel_image_menu_item_new_from_icon(const gchar * icon_name,const gchar * label_name)444 eel_image_menu_item_new_from_icon (const gchar *icon_name,
445 const gchar *label_name)
446 {
447 gchar *concat;
448 GtkWidget *icon;
449 GSettings *icon_settings;
450 GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
451
452 icon_settings = g_settings_new ("org.mate.interface");
453 if ((icon_name) && (g_settings_get_boolean (icon_settings, "menus-have-icons")))
454 /*Load the icon if user has icons in menus turned on*/
455 icon = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
456 else
457 /*Load an empty icon to hold the space*/
458 icon = gtk_image_new ();
459
460 concat = g_strconcat (label_name, " ", NULL);
461 GtkWidget *label_menu = gtk_label_new_with_mnemonic (concat);
462 GtkWidget *menuitem = gtk_menu_item_new ();
463
464 gtk_container_add (GTK_CONTAINER (box), icon);
465
466 gtk_container_add (GTK_CONTAINER (box), label_menu);
467
468 gtk_container_add (GTK_CONTAINER (menuitem), box);
469 gtk_widget_show_all (menuitem);
470
471 g_object_unref(icon_settings);
472 g_free (concat);
473
474 return menuitem;
475 }
476
477 GtkWidget *
eel_image_menu_item_new_from_surface(cairo_surface_t * icon_surface,const gchar * label_name)478 eel_image_menu_item_new_from_surface (cairo_surface_t *icon_surface,
479 const gchar *label_name)
480 {
481 gchar *concat;
482 GtkWidget *icon;
483 GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
484
485 if (icon_surface)
486 icon = gtk_image_new_from_surface (icon_surface);
487 else
488 icon = gtk_image_new ();
489
490 concat = g_strconcat (label_name, " ", NULL);
491 GtkWidget *label_menu = gtk_label_new (concat);
492 GtkWidget *menuitem = gtk_menu_item_new ();
493
494 gtk_container_add (GTK_CONTAINER (box), icon);
495 gtk_container_add (GTK_CONTAINER (box), label_menu);
496
497 gtk_container_add (GTK_CONTAINER (menuitem), box);
498 gtk_widget_show_all (menuitem);
499
500 g_free (concat);
501
502 return menuitem;
503 }
504
505 gboolean
eel_notebook_scroll_event_cb(GtkWidget * widget,GdkEventScroll * event)506 eel_notebook_scroll_event_cb (GtkWidget *widget,
507 GdkEventScroll *event)
508 {
509 GtkNotebook *notebook = GTK_NOTEBOOK (widget);
510 GtkWidget *child, *event_widget, *action_widget;
511
512 child = gtk_notebook_get_nth_page (notebook, gtk_notebook_get_current_page (notebook));
513 if (child == NULL)
514 return FALSE;
515
516 event_widget = gtk_get_event_widget ((GdkEvent*) event);
517
518 /* Ignore scroll events from the content of the page */
519 if (event_widget == NULL || event_widget == child || gtk_widget_is_ancestor (event_widget, child))
520 return FALSE;
521
522 /* And also from the action widgets */
523 action_widget = gtk_notebook_get_action_widget (notebook, GTK_PACK_START);
524 if (event_widget == action_widget || (action_widget != NULL && gtk_widget_is_ancestor (event_widget, action_widget)))
525 return FALSE;
526
527 action_widget = gtk_notebook_get_action_widget (notebook, GTK_PACK_END);
528 if (event_widget == action_widget || (action_widget != NULL && gtk_widget_is_ancestor (event_widget, action_widget)))
529 return FALSE;
530
531 switch (event->direction) {
532 case GDK_SCROLL_RIGHT:
533 case GDK_SCROLL_DOWN:
534 gtk_notebook_next_page (notebook);
535 break;
536 case GDK_SCROLL_LEFT:
537 case GDK_SCROLL_UP:
538 gtk_notebook_prev_page (notebook);
539 break;
540 case GDK_SCROLL_SMOOTH:
541 switch (gtk_notebook_get_tab_pos (notebook)) {
542 case GTK_POS_LEFT:
543 case GTK_POS_RIGHT:
544 if (event->delta_y > 0)
545 gtk_notebook_next_page (notebook);
546 else if (event->delta_y < 0)
547 gtk_notebook_prev_page (notebook);
548 break;
549 case GTK_POS_TOP:
550 case GTK_POS_BOTTOM:
551 if (event->delta_x > 0)
552 gtk_notebook_next_page (notebook);
553 else if (event->delta_x < 0)
554 gtk_notebook_prev_page (notebook);
555 break;
556 }
557 break;
558 }
559
560 return TRUE;
561 }
562