Correcting the color button popup API as suggested by Tommi to follow the GtkComboBox...
[hildon] / src / hildon-color-button.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; version 2.1 of
11  * the License.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-color-button
27  * @short_description: A widget to open HildonColorChooserDialog.
28  * @see_also: #HildonColorChooserDialog, #HildonColorPopup
29  *
30  * HildonColorButton is a widget to open a HildonColorChooserDialog.
31  * The selected color is shown in the button.
32  * The selected color is a property of the button.
33  * The property name is "color" and its type is GtkColor.
34  * 
35  * <example>
36  * <title>HildonColorButton example</title>
37  * <programlisting>
38  * HildonColorButton *cbutton;
39  * GtkColor *color;
40  * <!-- -->
41  * cbutton = hildon_color_button_new();
42  * gtk_object_get( GTK_OBJECT(cbutton), "color", color );
43  * </programlisting>
44  * </example>
45  * 
46  */
47
48 #ifdef                                          HAVE_CONFIG_H
49 #include                                        <config.h>
50 #endif
51
52 #include                                        "hildon-color-button.h"
53 #include                                        <gtk/gtkbutton.h>
54 #include                                        <gtk/gtkalignment.h>
55 #include                                        <gtk/gtkdrawingarea.h>
56 #include                                        <gtk/gtksignal.h>
57 #include                                        <gdk/gdkkeysyms.h>
58 #include                                        "hildon-defines.h"
59 #include                                        "hildon-color-chooser-dialog.h"
60 #include                                        "hildon-color-button-private.h"
61
62 #define                                         COLOR_FILLED_HEIGHT 22
63
64 #define                                         COLOR_FILLED_WIDTH 22
65
66 #define                                         COLOR_BUTTON_WIDTH 52
67
68 #define                                         COLOR_BUTTON_HEIGHT 48
69
70 #define                                         OUTER_BORDER_RED 0
71
72 #define                                         OUTER_BORDER_BLUE 0
73
74 #define                                         OUTER_BORDER_GREEN 0
75
76 #define                                         OUTER_BORDER_THICKNESS 1
77
78 #define                                         INNER_BORDER_RED 65535
79
80 #define                                         INNER_BORDER_BLUE 65535
81
82 #define                                         INNER_BORDER_GREEN 65535
83
84 #define                                         INNER_BORDER_THICKNESS 2
85
86 enum 
87 {
88     PROP_0,
89     PROP_COLOR,
90     PROP_POPUP_SHOWN
91 };
92
93 static void
94 hildon_color_button_class_init                  (HildonColorButtonClass *klass);
95
96 static void
97 hildon_color_button_init                        (HildonColorButton *color_button);
98
99 static void
100 hildon_color_button_finalize                    (GObject *object);
101
102 static void
103 hildon_color_button_set_property                (GObject *object, 
104                                                  guint param_id,
105                                                  const GValue *value, 
106                                                  GParamSpec *pspec);
107
108 static void
109 hildon_color_button_get_property                (GObject *object, 
110                                                  guint param_id,
111                                                  GValue *value, 
112                                                  GParamSpec *pspec);
113
114 static void
115 hildon_color_button_realize                     (GtkWidget *widget);
116
117 static void
118 hildon_color_button_unrealize                   (GtkWidget *widget);
119
120 static void
121 hildon_color_button_clicked                     (GtkButton *button);
122
123 static gboolean
124 hildon_color_button_key_pressed                 (GtkWidget *button, 
125                                                  GdkEventKey *event,
126                                                  gpointer data);
127
128 static gint
129 hildon_color_field_expose_event                 (GtkWidget *widget, 
130                                                  GdkEventExpose *event,
131                                                  HildonColorButton *cb);
132
133 static gboolean
134 hildon_color_button_mnemonic_activate           (GtkWidget *widget,
135                                                  gboolean group_cycling);
136
137 static void
138 draw_grid                                       (GdkDrawable *drawable, 
139                                                  GdkGC *gc, 
140                                                  int x, 
141                                                  int y, 
142                                                  gint w, 
143                                                  gint h);
144
145 static gpointer                                 parent_class = NULL;
146
147 /**
148  * hildon_color_button_get_type:
149  *
150  * Initializes and returns the type of a hildon color button.
151  *
152  * @Returns: GType of #HildonColorButton.
153  */
154 GType G_GNUC_CONST
155 hildon_color_button_get_type                    (void)
156 {
157     static GType color_button_type = 0;
158
159     if (! color_button_type)
160     {
161         static const GTypeInfo color_button_info =
162         {
163             sizeof (HildonColorButtonClass),
164             NULL,           /* base_init */
165             NULL,           /* base_finalize */
166             (GClassInitFunc) hildon_color_button_class_init,
167             NULL,           /* class_finalize */
168             NULL,           /* class_data */
169             sizeof (HildonColorButton),
170             0,              /* n_preallocs */
171             (GInstanceInitFunc) hildon_color_button_init,
172         };
173
174         color_button_type = g_type_register_static (GTK_TYPE_BUTTON, "HildonColorButton",
175                 &color_button_info, 0);
176     }
177
178     return color_button_type;
179 }
180
181 static void
182 hildon_color_button_class_init                  (HildonColorButtonClass *klass)
183 {
184     GObjectClass *gobject_class;
185     GtkButtonClass *button_class;
186     GtkWidgetClass *widget_class;
187
188     gobject_class = G_OBJECT_CLASS (klass);
189     button_class = GTK_BUTTON_CLASS (klass);
190     widget_class = GTK_WIDGET_CLASS (klass);
191     
192     parent_class = g_type_class_peek_parent (klass);
193
194     gobject_class->get_property     = hildon_color_button_get_property;
195     gobject_class->set_property     = hildon_color_button_set_property;
196     gobject_class->finalize         = hildon_color_button_finalize;
197     widget_class->realize           = hildon_color_button_realize;
198     widget_class->unrealize         = hildon_color_button_unrealize;
199     button_class->clicked           = hildon_color_button_clicked;
200     widget_class->mnemonic_activate = hildon_color_button_mnemonic_activate;
201
202     /**
203      * HildonColorButton:color:
204      *
205      * The currently selected color.
206      */
207     g_object_class_install_property (gobject_class, PROP_COLOR,
208             g_param_spec_boxed ("color",
209                 "Current Color",
210                 "The selected color",
211                 GDK_TYPE_COLOR,
212                 G_PARAM_READWRITE));
213
214     /**
215      * HildonColorButton:popup-shown:
216      *
217      * If the color selection dialog is currently popped-up (visible)
218      */
219     g_object_class_install_property (gobject_class, PROP_POPUP_SHOWN,
220             g_param_spec_boolean ("popup-shown",
221                 "IsPopped",
222                 "If the color selection dialog is popped up",
223                 FALSE,
224                 G_PARAM_READABLE));
225
226     g_type_class_add_private (gobject_class, sizeof (HildonColorButtonPrivate));
227 }
228
229 /* FIXME Draw a dotted grid over the specified area to make it look 
230  * insensitive. Actually, we should generate that pixbuf once and 
231  * just render it over later... */
232 static void
233 draw_grid                                       (GdkDrawable *drawable, 
234                                                  GdkGC *gc, 
235                                                  int x, 
236                                                  int y,
237                                                  gint w, 
238                                                  gint h)
239 {
240     int currentx;
241     int currenty;
242     for (currenty = y; currenty <= h; currenty++)
243         for (currentx = ((currenty % 2 == 0) ? x : x + 1); currentx <= w; currentx += 2)
244             gdk_draw_point (drawable, gc, currentx, currenty);
245 }
246
247 /* Handle exposure events for the color picker's drawing area */
248 static gint
249 hildon_color_field_expose_event                 (GtkWidget *widget, 
250                                                  GdkEventExpose *event,
251                                                  HildonColorButton *cb)
252 {
253     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
254     GdkColor outer_border, inner_border;
255
256     g_assert (priv);
257
258     /* Create the outer border color */
259     outer_border.pixel = 0;
260     outer_border.red   = OUTER_BORDER_RED;
261     outer_border.blue  = OUTER_BORDER_BLUE;
262     outer_border.green = OUTER_BORDER_GREEN;
263
264     /* Create the inner border color */
265     inner_border.pixel = 0;
266     inner_border.red   = INNER_BORDER_RED;
267     inner_border.blue  = INNER_BORDER_BLUE;
268     inner_border.green = INNER_BORDER_GREEN;
269
270     /* serve the outer border color to the Graphic Context */
271     gdk_gc_set_rgb_fg_color (priv->gc, &outer_border);
272     /* draw the outer border as a filled rectangle */
273     gdk_draw_rectangle (widget->window,
274             (GTK_WIDGET_IS_SENSITIVE (widget)) ?  priv->gc : widget->style->bg_gc [GTK_STATE_INSENSITIVE],
275             TRUE,
276             0, 
277             0,
278             widget->allocation.width,
279             widget->allocation.height);
280
281     /* serve the inner border color to the Graphic Context */
282     gdk_gc_set_rgb_fg_color (priv->gc, &inner_border);
283
284     /* draw the inner border as a filled rectangle */
285     gdk_draw_rectangle (widget->window,
286             priv->gc,
287             TRUE,
288             OUTER_BORDER_THICKNESS, 
289             OUTER_BORDER_THICKNESS,
290             widget->allocation.width - (OUTER_BORDER_THICKNESS * 2),
291             widget->allocation.height - (OUTER_BORDER_THICKNESS * 2));
292
293     /* serve the actual color to the Graphic Context */
294     gdk_gc_set_rgb_fg_color(priv->gc, &priv->color);
295
296     /* draw the actual rectangle */
297     gdk_draw_rectangle(widget->window,
298             priv->gc,
299             TRUE,
300             INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
301             INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
302             widget->allocation.width - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2),
303             widget->allocation.height - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2));
304
305     if (! GTK_WIDGET_IS_SENSITIVE (widget)) {
306         draw_grid (GDK_DRAWABLE (widget->window), widget->style->bg_gc [GTK_STATE_INSENSITIVE], 
307                 INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
308                 INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
309                 widget->allocation.width  - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2) + 2,
310                 widget->allocation.height - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2) + 2);
311     }
312
313     return FALSE;
314 }
315
316 static void
317 hildon_color_button_init                        (HildonColorButton *cb)
318 {
319     GtkWidget *align;
320     GtkWidget *drawing_area;
321     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
322
323     priv->dialog = NULL;
324     priv->gc = NULL;
325     priv->popped = FALSE;
326
327     gtk_widget_push_composite_child ();
328
329     /* create widgets and pixbuf */
330     align = gtk_alignment_new (0.5, 0.5, 0, 0); /* composite widget */
331
332     drawing_area = gtk_drawing_area_new (); /* composite widget */
333
334     /* setting minimum sizes */
335     gtk_widget_set_size_request (GTK_WIDGET (cb), COLOR_BUTTON_WIDTH,
336             COLOR_BUTTON_HEIGHT);
337
338     gtk_widget_set_size_request (GTK_WIDGET(drawing_area),
339             COLOR_FILLED_WIDTH, COLOR_FILLED_HEIGHT);
340
341     /* Connect the callback function for exposure event */
342     g_signal_connect (drawing_area, "expose-event",
343             G_CALLBACK (hildon_color_field_expose_event), cb);
344
345     /* Connect to callback function for key press event */
346     g_signal_connect (G_OBJECT(cb), "key-press-event",
347             G_CALLBACK(hildon_color_button_key_pressed), cb);
348
349     /* packing */
350     gtk_container_add (GTK_CONTAINER (align), drawing_area);
351     gtk_container_add (GTK_CONTAINER (cb), align);
352
353     gtk_widget_show_all (align);
354
355     gtk_widget_pop_composite_child ();
356 }
357
358 /* Free memory used by HildonColorButton */
359 static void
360 hildon_color_button_finalize                    (GObject *object)
361 {
362     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (object);
363     g_assert (priv);
364
365     if (priv->dialog)
366     {
367         gtk_widget_destroy (priv->dialog);
368         priv->dialog = NULL;
369     }
370
371     if (G_OBJECT_CLASS (parent_class)->finalize)
372         G_OBJECT_CLASS (parent_class)->finalize (object);
373 }
374
375 static void
376 hildon_color_button_realize                     (GtkWidget *widget)
377 {
378     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (widget);
379     g_assert (priv);
380
381     GTK_WIDGET_CLASS (parent_class)->realize (widget);
382
383     priv->gc = gdk_gc_new (widget->window);
384 }
385
386 static void
387 hildon_color_button_unrealize                   (GtkWidget *widget)
388 {
389     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (widget);
390     g_assert (priv);
391
392     if (priv->gc != NULL) { 
393         g_object_unref (priv->gc);
394         priv->gc = NULL;
395     }
396
397     GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
398 }
399
400 /* Make the widget sensitive with the keyboard event */
401 static gboolean
402 hildon_color_button_mnemonic_activate           (GtkWidget *widget,
403                                                  gboolean group_cycling)
404 {
405     gtk_widget_grab_focus (widget);
406     return TRUE;
407 }
408
409 /* Popup a color selector dialog on button click */
410 static void
411 hildon_color_button_clicked                     (GtkButton *button)
412 {
413     HildonColorButton *cb = HILDON_COLOR_BUTTON (button);
414     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
415     HildonColorChooserDialog *cs_dialog;
416     
417     g_assert (priv);
418     
419     cs_dialog = (HildonColorChooserDialog *) priv->dialog;
420
421     /* Popup the color selector dialog */
422     if (! cs_dialog)
423     {
424         /* The dialog hasn't been created yet, do it */
425         GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET(cb));
426         priv->dialog = hildon_color_chooser_dialog_new ();
427         cs_dialog = HILDON_COLOR_CHOOSER_DIALOG (priv->dialog);
428         if (parent)
429             gtk_window_set_transient_for (GTK_WINDOW (cs_dialog), GTK_WINDOW (parent));
430     }
431
432     /* Set the initial color for the color selector dialog */
433     hildon_color_chooser_dialog_set_color (cs_dialog, &priv->color);
434
435     /* Update the color for color button if selection was made */
436     priv->popped = TRUE;
437     if (gtk_dialog_run (GTK_DIALOG (cs_dialog)) == GTK_RESPONSE_OK)
438     {
439         hildon_color_chooser_dialog_get_color (cs_dialog, &priv->color);
440         hildon_color_button_set_color (HILDON_COLOR_BUTTON (button), &priv->color);
441         // FIXME A queue-draw should be enough here (not set needed)
442     } 
443
444     gtk_widget_hide (GTK_WIDGET(cs_dialog));
445     priv->popped = FALSE;
446 }
447
448 /* Popup a color selector dialog on hardkey Select press.
449  * FIXME This is a bit hacky, should work without thi. Check. */
450 static gboolean
451 hildon_color_button_key_pressed                 (GtkWidget *button, 
452                                                  GdkEventKey *event,
453                                                  gpointer data)
454 {
455     g_return_val_if_fail (HILDON_IS_COLOR_BUTTON (button), FALSE);
456
457     if (event->keyval == HILDON_HARDKEY_SELECT)
458     {
459         hildon_color_button_clicked (GTK_BUTTON (button));
460         return TRUE;
461     }
462
463     return FALSE;
464 }
465
466 static void
467 hildon_color_button_set_property                (GObject *object, 
468                                                  guint param_id,
469                                                  const GValue *value, 
470                                                  GParamSpec *pspec)
471 {
472     HildonColorButton *cb = HILDON_COLOR_BUTTON (object);
473     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
474     g_assert (priv);
475
476     switch (param_id) 
477     {
478
479         case PROP_COLOR:
480             priv->color = *(GdkColor *) g_value_get_boxed (value); 
481             gtk_widget_queue_draw (GTK_WIDGET (cb));
482             break;
483
484         default:
485             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
486             break;
487     }
488 }
489
490 static void
491 hildon_color_button_get_property                (GObject *object, 
492                                                  guint param_id,
493                                                  GValue *value, 
494                                                  GParamSpec *pspec)
495 {
496     HildonColorButton *cb = HILDON_COLOR_BUTTON (object);
497     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
498     g_assert (priv);
499
500     switch (param_id) 
501     {
502
503         case PROP_COLOR:
504             g_value_set_boxed (value, &priv->color);
505             break;
506
507         case PROP_POPUP_SHOWN:
508             g_value_set_boolean (value, priv->popped);
509
510         default:
511             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
512             break;
513     }
514 }
515
516 /**
517  * hildon_color_button_new:
518  *
519  * Creates a new color button. This returns a widget in the form of a
520  * small button containing a swatch representing the selected color.
521  * When the button is clicked, a color-selection dialog will open,
522  * allowing the user to select a color. The swatch will be updated to
523  * reflect the new color when the user finishes.
524  *
525  * Returns: a new color button
526  */
527 GtkWidget*
528 hildon_color_button_new                         (void)
529 {
530     return g_object_new (HILDON_TYPE_COLOR_BUTTON, NULL);
531 }
532
533 /**
534  * hildon_color_button_new_with_color:
535  * @color: a #GdkColor for the initial color
536  *
537  * Creates a new color button with @color as the initial color. 
538  *
539  * Returns: a new color button
540  */
541 GtkWidget*
542 hildon_color_button_new_with_color              (const GdkColor *color)
543 {
544     return g_object_new (HILDON_TYPE_COLOR_BUTTON, "color", color, NULL);
545 }
546
547 /**
548  * hildon_color_button_set_color:
549  * @button: a #HildonColorButton
550  * @color: a color to be set
551  *
552  * Sets the color selected by the button.
553  */
554 void
555 hildon_color_button_set_color                   (HildonColorButton *button, 
556                                                  GdkColor *color)
557 {
558     g_return_if_fail (HILDON_IS_COLOR_BUTTON (button));
559
560     g_object_set (G_OBJECT (button), "color", color, NULL);
561 }
562
563 /**
564  * hildon_color_button_get_popup_shown
565  * @button: a #HildonColorButton
566  *
567  * This function checks if the color button has the color 
568  * selection dialog currently popped-up. 
569  * 
570  * Returns: TRUE if the dialog is popped-up (visible to user).
571  *
572  */
573 gboolean
574 hildon_color_button_get_popup_shown             (HildonColorButton *button)
575 {
576     HildonColorButtonPrivate *priv = NULL; 
577     g_return_val_if_fail (HILDON_IS_COLOR_BUTTON (button), FALSE);
578
579     priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
580     g_assert (priv);
581
582     return priv->popped;
583 }
584
585 /**
586  * hildon_color_button_popdown
587  * @button: a #HildonColorButton
588  *
589  * If the color selection dialog is currently popped-up (visible)
590  * it will be popped-down (hidden).
591  *
592  */
593 void
594 hildon_color_button_popdown                     (HildonColorButton *button)
595 {
596     HildonColorButtonPrivate *priv = NULL; 
597     g_return_if_fail (HILDON_IS_COLOR_BUTTON (button));
598
599     priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
600     g_assert (priv);
601
602     if (priv->popped && priv->dialog) {
603         gtk_dialog_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_CANCEL);
604     }
605 }
606
607 /**
608  * hildon_color_button_get_color:
609  * @button: a #HildonColorButton
610  * @color: a color #GdkColor to be fillled with the current color
611  *
612  */
613 void
614 hildon_color_button_get_color                   (HildonColorButton *button, 
615                                                  GdkColor *color)
616 {
617     HildonColorButtonPrivate *priv = NULL; 
618     g_return_if_fail (HILDON_IS_COLOR_BUTTON (button));
619     g_return_if_fail (color != NULL);
620    
621     priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
622     g_assert (priv);
623
624     color->red = priv->color.red;
625     color->green = priv->color.green;
626     color->blue = priv->color.blue;
627     color->pixel = priv->color.pixel;
628 }
629