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