]> Pileus Git - ~andy/gtk/blob - gtk/gtkcellrendereraccel.c
Move GtkSizeRequest into GtkWidget
[~andy/gtk] / gtk / gtkcellrendereraccel.c
1 /* gtkcellrendereraccel.h
2  * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb@redhat.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include "gtkcellrendereraccel.h"
23
24 #include "gdk/gdkkeysyms.h"
25
26 #include "gtkintl.h"
27 #include "gtkaccelgroup.h"
28 #include "gtkmarshalers.h"
29 #include "gtklabel.h"
30 #include "gtkeventbox.h"
31 #include "gtkmain.h"
32 #include "gtksizerequest.h"
33 #include "gtkprivate.h"
34
35
36 static void gtk_cell_renderer_accel_get_property (GObject         *object,
37                                                   guint            param_id,
38                                                   GValue          *value,
39                                                   GParamSpec      *pspec);
40 static void gtk_cell_renderer_accel_set_property (GObject         *object,
41                                                   guint            param_id,
42                                                   const GValue    *value,
43                                                   GParamSpec      *pspec);
44 static void gtk_cell_renderer_accel_get_size     (GtkCellRenderer *cell,
45                                                   GtkWidget       *widget,
46                                                   GdkRectangle    *cell_area,
47                                                   gint            *x_offset,
48                                                   gint            *y_offset,
49                                                   gint            *width,
50                                                   gint            *height);
51 static GtkCellEditable *
52            gtk_cell_renderer_accel_start_editing (GtkCellRenderer *cell,
53                                                   GdkEvent        *event,
54                                                   GtkWidget       *widget,
55                                                   const gchar     *path,
56                                                   GdkRectangle    *background_area,
57                                                   GdkRectangle    *cell_area,
58                                                   GtkCellRendererState flags);
59 static gchar *convert_keysym_state_to_string     (GtkCellRendererAccel *accel,
60                                                   guint                 keysym,
61                                                   GdkModifierType       mask,
62                                                   guint                 keycode);
63
64 enum {
65   ACCEL_EDITED,
66   ACCEL_CLEARED,
67   LAST_SIGNAL
68 };
69
70 enum {
71   PROP_0,
72   PROP_ACCEL_KEY,
73   PROP_ACCEL_MODS,
74   PROP_KEYCODE,
75   PROP_ACCEL_MODE
76 };
77
78 struct _GtkCellRendererAccelPrivate
79 {
80   GtkCellRendererAccelMode accel_mode;
81
82   GtkWidget *edit_widget;
83   GtkWidget *grab_widget;
84   GtkWidget *sizing_label;
85
86   GdkDevice *grab_keyboard;
87   GdkDevice *grab_pointer;
88
89   GdkModifierType accel_mods;
90
91   guint accel_key;
92   guint keycode;
93 };
94
95 static guint signals[LAST_SIGNAL] = { 0 };
96
97 G_DEFINE_TYPE (GtkCellRendererAccel, gtk_cell_renderer_accel, GTK_TYPE_CELL_RENDERER_TEXT)
98
99 static void
100 gtk_cell_renderer_accel_init (GtkCellRendererAccel *cell_accel)
101 {
102   gchar *text;
103
104   cell_accel->priv = G_TYPE_INSTANCE_GET_PRIVATE (cell_accel,
105                                                   GTK_TYPE_CELL_RENDERER_ACCEL,
106                                                   GtkCellRendererAccelPrivate);
107
108   text = convert_keysym_state_to_string (cell_accel, 0, 0, 0);
109   g_object_set (cell_accel, "text", text, NULL);
110   g_free (text);
111 }
112
113 static void
114 gtk_cell_renderer_accel_class_init (GtkCellRendererAccelClass *cell_accel_class)
115 {
116   GObjectClass *object_class;
117   GtkCellRendererClass *cell_renderer_class;
118
119   object_class = G_OBJECT_CLASS (cell_accel_class);
120   cell_renderer_class = GTK_CELL_RENDERER_CLASS (cell_accel_class);
121
122   object_class->set_property = gtk_cell_renderer_accel_set_property;
123   object_class->get_property = gtk_cell_renderer_accel_get_property;
124
125   cell_renderer_class->get_size      = gtk_cell_renderer_accel_get_size;
126   cell_renderer_class->start_editing = gtk_cell_renderer_accel_start_editing;
127
128   /**
129    * GtkCellRendererAccel:accel-key:
130    *
131    * The keyval of the accelerator.
132    *
133    * Since: 2.10
134    */
135   g_object_class_install_property (object_class,
136                                    PROP_ACCEL_KEY,
137                                    g_param_spec_uint ("accel-key",
138                                                      P_("Accelerator key"),
139                                                      P_("The keyval of the accelerator"),
140                                                       0,
141                                                       G_MAXINT,
142                                                       0,
143                                                       GTK_PARAM_READWRITE));
144   
145   /**
146    * GtkCellRendererAccel:accel-mods:
147    *
148    * The modifier mask of the accelerator.
149    *
150    * Since: 2.10
151    */
152   g_object_class_install_property (object_class,
153                                    PROP_ACCEL_MODS,
154                                    g_param_spec_flags ("accel-mods",
155                                                        P_("Accelerator modifiers"),
156                                                        P_("The modifier mask of the accelerator"),
157                                                        GDK_TYPE_MODIFIER_TYPE,
158                                                        0,
159                                                        GTK_PARAM_READWRITE));
160
161   /**
162    * GtkCellRendererAccel:keycode:
163    *
164    * The hardware keycode of the accelerator. Note that the hardware keycode is
165    * only relevant if the key does not have a keyval. Normally, the keyboard
166    * configuration should assign keyvals to all keys.
167    *
168    * Since: 2.10
169    */ 
170   g_object_class_install_property (object_class,
171                                    PROP_KEYCODE,
172                                    g_param_spec_uint ("keycode",
173                                                       P_("Accelerator keycode"),
174                                                       P_("The hardware keycode of the accelerator"),
175                                                       0,
176                                                       G_MAXINT,
177                                                       0,
178                                                       GTK_PARAM_READWRITE));
179
180   /**
181    * GtkCellRendererAccel:accel-mode:
182    *
183    * Determines if the edited accelerators are GTK+ accelerators. If
184    * they are, consumed modifiers are suppressed, only accelerators
185    * accepted by GTK+ are allowed, and the accelerators are rendered
186    * in the same way as they are in menus.
187    *
188    * Since: 2.10
189    */
190   g_object_class_install_property (object_class,
191                                    PROP_ACCEL_MODE,
192                                    g_param_spec_enum ("accel-mode",
193                                                       P_("Accelerator Mode"),
194                                                       P_("The type of accelerators"),
195                                                       GTK_TYPE_CELL_RENDERER_ACCEL_MODE,
196                                                       GTK_CELL_RENDERER_ACCEL_MODE_GTK,
197                                                       GTK_PARAM_READWRITE));
198   
199   /**
200    * GtkCellRendererAccel::accel-edited:
201    * @accel: the object reveiving the signal
202    * @path_string: the path identifying the row of the edited cell
203    * @accel_key: the new accelerator keyval
204    * @accel_mods: the new acclerator modifier mask
205    * @hardware_keycode: the keycode of the new accelerator
206    *
207    * Gets emitted when the user has selected a new accelerator.
208    *
209    * Since: 2.10
210    */
211   signals[ACCEL_EDITED] = g_signal_new (I_("accel-edited"),
212                                         GTK_TYPE_CELL_RENDERER_ACCEL,
213                                         G_SIGNAL_RUN_LAST,
214                                         G_STRUCT_OFFSET (GtkCellRendererAccelClass, accel_edited),
215                                         NULL, NULL,
216                                         _gtk_marshal_VOID__STRING_UINT_FLAGS_UINT,
217                                         G_TYPE_NONE, 4,
218                                         G_TYPE_STRING,
219                                         G_TYPE_UINT,
220                                         GDK_TYPE_MODIFIER_TYPE,
221                                         G_TYPE_UINT);
222
223   /**
224    * GtkCellRendererAccel::accel-cleared:
225    * @accel: the object reveiving the signal
226    * @path_string: the path identifying the row of the edited cell
227    *
228    * Gets emitted when the user has removed the accelerator.
229    *
230    * Since: 2.10
231    */
232   signals[ACCEL_CLEARED] = g_signal_new (I_("accel-cleared"),
233                                          GTK_TYPE_CELL_RENDERER_ACCEL,
234                                          G_SIGNAL_RUN_LAST,
235                                          G_STRUCT_OFFSET (GtkCellRendererAccelClass, accel_cleared),
236                                          NULL, NULL,
237                                          g_cclosure_marshal_VOID__STRING,
238                                          G_TYPE_NONE, 1,
239                                          G_TYPE_STRING);
240
241   g_type_class_add_private (cell_accel_class, sizeof (GtkCellRendererAccelPrivate));
242 }
243
244
245 /**
246  * gtk_cell_renderer_accel_new:
247  *
248  * Creates a new #GtkCellRendererAccel.
249  * 
250  * Returns: the new cell renderer
251  *
252  * Since: 2.10
253  */
254 GtkCellRenderer *
255 gtk_cell_renderer_accel_new (void)
256 {
257   return g_object_new (GTK_TYPE_CELL_RENDERER_ACCEL, NULL);
258 }
259
260 static gchar *
261 convert_keysym_state_to_string (GtkCellRendererAccel *accel,
262                                 guint                 keysym,
263                                 GdkModifierType       mask,
264                                 guint                 keycode)
265 {
266   GtkCellRendererAccelPrivate *priv = accel->priv;
267
268   if (keysym == 0 && keycode == 0)
269     /* This label is displayed in a treeview cell displaying
270      * a disabled accelerator key combination.
271      */
272     return g_strdup (C_("Accelerator", "Disabled"));
273   else 
274     {
275       if (priv->accel_mode == GTK_CELL_RENDERER_ACCEL_MODE_GTK)
276         {
277           if (!gtk_accelerator_valid (keysym, mask))
278             /* This label is displayed in a treeview cell displaying
279              * an accelerator key combination that is not valid according
280              * to gtk_accelerator_valid().
281              */
282             return g_strdup (C_("Accelerator", "Invalid"));
283
284           return gtk_accelerator_get_label (keysym, mask);
285         }
286       else 
287         {
288           gchar *name;
289
290           name = gtk_accelerator_get_label (keysym, mask);
291           if (name == NULL)
292             name = gtk_accelerator_name (keysym, mask);
293
294           if (keysym == 0)
295             {
296               gchar *tmp;
297
298               tmp = name;
299               name = g_strdup_printf ("%s0x%02x", tmp, keycode);
300               g_free (tmp);
301             }
302
303           return name;
304         }
305     }
306 }
307
308 static void
309 gtk_cell_renderer_accel_get_property  (GObject    *object,
310                                        guint       param_id,
311                                        GValue     *value,
312                                        GParamSpec *pspec)
313 {
314   GtkCellRendererAccelPrivate *priv = GTK_CELL_RENDERER_ACCEL (object)->priv;
315
316   switch (param_id)
317     {
318     case PROP_ACCEL_KEY:
319       g_value_set_uint (value, priv->accel_key);
320       break;
321
322     case PROP_ACCEL_MODS:
323       g_value_set_flags (value, priv->accel_mods);
324       break;
325
326     case PROP_KEYCODE:
327       g_value_set_uint (value, priv->keycode);
328       break;
329
330     case PROP_ACCEL_MODE:
331       g_value_set_enum (value, priv->accel_mode);
332       break;
333
334     default:
335       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
336     }
337 }
338
339 static void
340 gtk_cell_renderer_accel_set_property  (GObject      *object,
341                                        guint         param_id,
342                                        const GValue *value,
343                                        GParamSpec   *pspec)
344 {
345   GtkCellRendererAccel *accel = GTK_CELL_RENDERER_ACCEL (object);
346   GtkCellRendererAccelPrivate *priv = accel->priv;
347   gboolean changed = FALSE;
348
349   switch (param_id)
350     {
351     case PROP_ACCEL_KEY:
352       {
353         guint accel_key = g_value_get_uint (value);
354
355         if (priv->accel_key != accel_key)
356           {
357             priv->accel_key = accel_key;
358             changed = TRUE;
359           }
360       }
361       break;
362
363     case PROP_ACCEL_MODS:
364       {
365         guint accel_mods = g_value_get_flags (value);
366
367         if (priv->accel_mods != accel_mods)
368           {
369             priv->accel_mods = accel_mods;
370             changed = TRUE;
371           }
372       }
373       break;
374     case PROP_KEYCODE:
375       {
376         guint keycode = g_value_get_uint (value);
377
378         if (priv->keycode != keycode)
379           {
380             priv->keycode = keycode;
381             changed = TRUE;
382           }
383       }
384       break;
385
386     case PROP_ACCEL_MODE:
387       priv->accel_mode = g_value_get_enum (value);
388       break;
389       
390     default:
391       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
392     }
393
394   if (changed)
395     {
396       gchar *text;
397
398       text = convert_keysym_state_to_string (accel, priv->accel_key, priv->accel_mods, priv->keycode);
399       g_object_set (accel, "text", text, NULL);
400       g_free (text);
401     }
402 }
403
404 static void
405 gtk_cell_renderer_accel_get_size (GtkCellRenderer *cell,
406                                   GtkWidget       *widget,
407                                   GdkRectangle    *cell_area,
408                                   gint            *x_offset,
409                                   gint            *y_offset,
410                                   gint            *width,
411                                   gint            *height)
412
413 {
414   GtkCellRendererAccelPrivate *priv = GTK_CELL_RENDERER_ACCEL (cell)->priv;
415   GtkRequisition requisition;
416
417   if (priv->sizing_label == NULL)
418     priv->sizing_label = gtk_label_new (_("New accelerator..."));
419
420   gtk_widget_get_preferred_size (priv->sizing_label, &requisition, NULL);
421
422   GTK_CELL_RENDERER_CLASS (gtk_cell_renderer_accel_parent_class)->get_size (cell, widget, cell_area,
423                                                                             x_offset, y_offset, width, height);
424
425   /* FIXME: need to take the cell_area et al. into account */
426   if (width)
427     *width = MAX (*width, requisition.width);
428   if (height)
429     *height = MAX (*height, requisition.height);
430 }
431
432 static gboolean
433 grab_key_callback (GtkWidget            *widget,
434                    GdkEventKey          *event,
435                    GtkCellRendererAccel *accel)
436 {
437   GtkCellRendererAccelPrivate *priv = accel->priv;
438   GdkModifierType accel_mods = 0;
439   guint accel_key;
440   gchar *path;
441   gboolean edited;
442   gboolean cleared;
443   GdkModifierType consumed_modifiers;
444   GdkDisplay *display;
445
446   display = gtk_widget_get_display (widget);
447
448   if (event->is_modifier)
449     return TRUE;
450
451   edited = FALSE;
452   cleared = FALSE;
453
454   gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display),
455                                        event->hardware_keycode,
456                                        event->state,
457                                        event->group,
458                                        NULL, NULL, NULL, &consumed_modifiers);
459
460   accel_key = gdk_keyval_to_lower (event->keyval);
461   if (accel_key == GDK_KEY_ISO_Left_Tab) 
462     accel_key = GDK_KEY_Tab;
463
464   accel_mods = event->state & gtk_accelerator_get_default_mod_mask ();
465
466   /* Filter consumed modifiers 
467    */
468   if (priv->accel_mode == GTK_CELL_RENDERER_ACCEL_MODE_GTK)
469     accel_mods &= ~consumed_modifiers;
470   
471   /* Put shift back if it changed the case of the key, not otherwise.
472    */
473   if (accel_key != event->keyval)
474     accel_mods |= GDK_SHIFT_MASK;
475     
476   if (accel_mods == 0)
477     {
478       switch (event->keyval)
479         {
480         case GDK_KEY_Escape:
481           goto out; /* cancel */
482         case GDK_KEY_BackSpace:
483           /* clear the accelerator on Backspace */
484           cleared = TRUE;
485           goto out;
486         default:
487           break;
488         }
489     }
490
491   if (priv->accel_mode == GTK_CELL_RENDERER_ACCEL_MODE_GTK)
492     {
493       if (!gtk_accelerator_valid (accel_key, accel_mods))
494         {
495           gtk_widget_error_bell (widget);
496
497           return TRUE;
498         }
499     }
500
501   edited = TRUE;
502
503  out:
504   gtk_device_grab_remove (priv->grab_widget, priv->grab_pointer);
505   gdk_device_ungrab (priv->grab_keyboard, event->time);
506   gdk_device_ungrab (priv->grab_pointer, event->time);
507
508   path = g_strdup (g_object_get_data (G_OBJECT (priv->edit_widget), "gtk-cell-renderer-text"));
509
510   gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (priv->edit_widget));
511   gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (priv->edit_widget));
512   priv->edit_widget = NULL;
513   priv->grab_widget = NULL;
514   priv->grab_keyboard = NULL;
515   priv->grab_pointer = NULL;
516
517   if (edited)
518     g_signal_emit (accel, signals[ACCEL_EDITED], 0, path, 
519                    accel_key, accel_mods, event->hardware_keycode);
520   else if (cleared)
521     g_signal_emit (accel, signals[ACCEL_CLEARED], 0, path);
522
523   g_free (path);
524
525   return TRUE;
526 }
527
528 static void
529 ungrab_stuff (GtkWidget            *widget,
530               GtkCellRendererAccel *accel)
531 {
532   GtkCellRendererAccelPrivate *priv = accel->priv;
533
534   gtk_device_grab_remove (priv->grab_widget, priv->grab_pointer);
535   gdk_device_ungrab (priv->grab_keyboard, GDK_CURRENT_TIME);
536   gdk_device_ungrab (priv->grab_pointer, GDK_CURRENT_TIME);
537
538   priv->grab_keyboard = NULL;
539   priv->grab_pointer = NULL;
540
541   g_signal_handlers_disconnect_by_func (priv->grab_widget,
542                                         G_CALLBACK (grab_key_callback),
543                                         accel);
544 }
545
546 static void
547 _gtk_cell_editable_event_box_start_editing (GtkCellEditable *cell_editable,
548                                             GdkEvent        *event)
549 {
550   /* do nothing, because we are pointless */
551 }
552
553 static void
554 _gtk_cell_editable_event_box_cell_editable_init (GtkCellEditableIface *iface)
555 {
556   iface->start_editing = _gtk_cell_editable_event_box_start_editing;
557 }
558
559 typedef GtkEventBox      GtkCellEditableEventBox;
560 typedef GtkEventBoxClass GtkCellEditableEventBoxClass;
561
562 G_DEFINE_TYPE_WITH_CODE (GtkCellEditableEventBox, _gtk_cell_editable_event_box, GTK_TYPE_EVENT_BOX, { \
563     G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, _gtk_cell_editable_event_box_cell_editable_init)   \
564       })
565
566
567 static void
568 _gtk_cell_editable_event_box_class_init (GtkCellEditableEventBoxClass *class)
569 {
570 }
571
572 static void
573 _gtk_cell_editable_event_box_init (GtkCellEditableEventBox *box)
574 {
575 }
576
577 static GtkCellEditable *
578 gtk_cell_renderer_accel_start_editing (GtkCellRenderer      *cell,
579                                        GdkEvent             *event,
580                                        GtkWidget            *widget,
581                                        const gchar          *path,
582                                        GdkRectangle         *background_area,
583                                        GdkRectangle         *cell_area,
584                                        GtkCellRendererState  flags)
585 {
586   GtkCellRendererAccelPrivate *priv;
587   GtkCellRendererText *celltext;
588   GtkCellRendererAccel *accel;
589   GtkStyle *style;
590   GtkWidget *label;
591   GtkWidget *eventbox;
592   GdkDevice *device, *keyb, *pointer;
593   GdkWindow *window;
594   gboolean editable;
595   guint32 time;
596
597   celltext = GTK_CELL_RENDERER_TEXT (cell);
598   accel = GTK_CELL_RENDERER_ACCEL (cell);
599   priv = accel->priv;
600
601   /* If the cell isn't editable we return NULL. */
602   g_object_get (celltext, "editable", &editable, NULL);
603   if (editable == FALSE)
604     return NULL;
605
606   window = gtk_widget_get_window (widget);
607   style = gtk_widget_get_style (widget);
608
609   g_return_val_if_fail (window != NULL, NULL);
610
611   if (event)
612     device = gdk_event_get_device (event);
613   else
614     device = gtk_get_current_event_device ();
615
616   if (!device)
617     return NULL;
618
619   if (device->source == GDK_SOURCE_KEYBOARD)
620     {
621       keyb = device;
622       pointer = gdk_device_get_associated_device (device);
623     }
624   else
625     {
626       pointer = device;
627       keyb = gdk_device_get_associated_device (device);
628     }
629
630   time = gdk_event_get_time (event);
631
632   if (gdk_device_grab (keyb, window,
633                        GDK_OWNERSHIP_WINDOW, FALSE,
634                        GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
635                        NULL, time) != GDK_GRAB_SUCCESS)
636     return NULL;
637
638   if (gdk_device_grab (pointer, window,
639                        GDK_OWNERSHIP_WINDOW, FALSE,
640                        GDK_BUTTON_PRESS_MASK,
641                        NULL, time) != GDK_GRAB_SUCCESS)
642     {
643       gdk_device_ungrab (keyb, time);
644       return NULL;
645     }
646
647   priv->grab_keyboard = keyb;
648   priv->grab_pointer = pointer;
649   priv->grab_widget = widget;
650
651   g_signal_connect (G_OBJECT (widget), "key-press-event",
652                     G_CALLBACK (grab_key_callback),
653                     accel);
654
655   eventbox = g_object_new (_gtk_cell_editable_event_box_get_type (), NULL);
656   priv->edit_widget = eventbox;
657   g_object_add_weak_pointer (G_OBJECT (priv->edit_widget),
658                              (gpointer) &priv->edit_widget);
659   
660   label = gtk_label_new (NULL);
661   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
662
663   
664
665   gtk_widget_modify_bg (eventbox, GTK_STATE_NORMAL,
666                         &style->bg[GTK_STATE_SELECTED]);
667
668   gtk_widget_modify_fg (label, GTK_STATE_NORMAL,
669                         &style->fg[GTK_STATE_SELECTED]);
670
671   /* This label is displayed in a treeview cell displaying
672    * an accelerator when the cell is clicked to change the 
673    * acelerator.
674    */
675   gtk_label_set_text (GTK_LABEL (label), _("New accelerator..."));
676
677   gtk_container_add (GTK_CONTAINER (eventbox), label);
678   
679   g_object_set_data_full (G_OBJECT (priv->edit_widget), "gtk-cell-renderer-text",
680                           g_strdup (path), g_free);
681   
682   gtk_widget_show_all (priv->edit_widget);
683
684   gtk_device_grab_add (priv->grab_widget, pointer, TRUE);
685
686   g_signal_connect (priv->edit_widget, "unrealize",
687                     G_CALLBACK (ungrab_stuff), accel);
688   
689   return GTK_CELL_EDITABLE (priv->edit_widget);
690 }