2008-04-15 13:21:13 <timj@imendio.com>
[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, or (at your option) any later version.
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     SETUP_DIALOG,
89     LAST_SIGNAL
90 };
91
92 enum
93 {
94     PROP_0,
95     PROP_COLOR,
96     PROP_POPUP_SHOWN
97 };
98
99 static void
100 hildon_color_button_class_init                  (HildonColorButtonClass *klass);
101
102 static void
103 hildon_color_button_init                        (HildonColorButton *color_button);
104
105 static void
106 hildon_color_button_finalize                    (GObject *object);
107
108 static void
109 hildon_color_button_set_property                (GObject *object, 
110                                                  guint param_id,
111                                                  const GValue *value, 
112                                                  GParamSpec *pspec);
113
114 static void
115 hildon_color_button_get_property                (GObject *object, 
116                                                  guint param_id,
117                                                  GValue *value, 
118                                                  GParamSpec *pspec);
119
120 static void
121 hildon_color_button_realize                     (GtkWidget *widget);
122
123 static void
124 hildon_color_button_unrealize                   (GtkWidget *widget);
125
126 static void
127 hildon_color_button_clicked                     (GtkButton *button);
128
129 static gboolean
130 hildon_color_button_key_pressed                 (GtkWidget *button, 
131                                                  GdkEventKey *event,
132                                                  gpointer data);
133
134 static gint
135 hildon_color_field_expose_event                 (GtkWidget *widget, 
136                                                  GdkEventExpose *event,
137                                                  HildonColorButton *cb);
138
139 static gboolean
140 hildon_color_button_mnemonic_activate           (GtkWidget *widget,
141                                                  gboolean group_cycling);
142
143 static void
144 draw_grid                                       (GdkDrawable *drawable, 
145                                                  GdkGC *gc, 
146                                                  int x, 
147                                                  int y, 
148                                                  gint w, 
149                                                  gint h);
150
151 static gpointer                                 parent_class = NULL;
152 static guint                                    signals [LAST_SIGNAL] = { 0, };
153
154 /**
155  * hildon_color_button_get_type:
156  *
157  * Initializes and returns the type of a hildon color button.
158  *
159  * @Returns: GType of #HildonColorButton.
160  */
161 GType G_GNUC_CONST
162 hildon_color_button_get_type                    (void)
163 {
164     static GType color_button_type = 0;
165
166     if (! color_button_type)
167     {
168         static const GTypeInfo color_button_info =
169         {
170             sizeof (HildonColorButtonClass),
171             NULL,           /* base_init */
172             NULL,           /* base_finalize */
173             (GClassInitFunc) hildon_color_button_class_init,
174             NULL,           /* class_finalize */
175             NULL,           /* class_data */
176             sizeof (HildonColorButton),
177             0,              /* n_preallocs */
178             (GInstanceInitFunc) hildon_color_button_init,
179         };
180
181         color_button_type = g_type_register_static (GTK_TYPE_BUTTON, "HildonColorButton",
182                 &color_button_info, 0);
183     }
184
185     return color_button_type;
186 }
187
188 static void
189 hildon_color_button_class_init                  (HildonColorButtonClass *klass)
190 {
191     GObjectClass *gobject_class;
192     GtkButtonClass *button_class;
193     GtkWidgetClass *widget_class;
194
195     gobject_class = G_OBJECT_CLASS (klass);
196     button_class = GTK_BUTTON_CLASS (klass);
197     widget_class = GTK_WIDGET_CLASS (klass);
198     
199     parent_class = g_type_class_peek_parent (klass);
200
201     gobject_class->get_property     = hildon_color_button_get_property;
202     gobject_class->set_property     = hildon_color_button_set_property;
203     gobject_class->finalize         = hildon_color_button_finalize;
204     widget_class->realize           = hildon_color_button_realize;
205     widget_class->unrealize         = hildon_color_button_unrealize;
206     button_class->clicked           = hildon_color_button_clicked;
207     widget_class->mnemonic_activate = hildon_color_button_mnemonic_activate;
208
209    signals[SETUP_DIALOG] =
210        g_signal_new ("setup-dialog",
211                      G_TYPE_FROM_CLASS (klass),
212                      G_SIGNAL_RUN_LAST,
213                      0,
214                      NULL, NULL,
215                      gtk_marshal_VOID__OBJECT,
216                      G_TYPE_NONE, 1,
217                      HILDON_TYPE_COLOR_CHOOSER_DIALOG);
218
219     /**
220      * HildonColorButton:color:
221      *
222      * The currently selected color.
223      */
224     g_object_class_install_property (gobject_class, PROP_COLOR,
225             g_param_spec_boxed ("color",
226                 "Current Color",
227                 "The selected color",
228                 GDK_TYPE_COLOR,
229                 G_PARAM_READWRITE));
230
231     /**
232      * HildonColorButton:popup-shown:
233      *
234      * If the color selection dialog is currently popped-up (visible)
235      */
236     g_object_class_install_property (gobject_class, PROP_POPUP_SHOWN,
237             g_param_spec_boolean ("popup-shown",
238                 "IsPopped",
239                 "If the color selection dialog is popped up",
240                 FALSE,
241                 G_PARAM_READABLE));
242
243     g_type_class_add_private (gobject_class, sizeof (HildonColorButtonPrivate));
244 }
245
246 /* FIXME Draw a dotted grid over the specified area to make it look 
247  * insensitive. Actually, we should generate that pixbuf once and 
248  * just render it over later... */
249 static void
250 draw_grid                                       (GdkDrawable *drawable, 
251                                                  GdkGC *gc, 
252                                                  int x, 
253                                                  int y,
254                                                  gint w, 
255                                                  gint h)
256 {
257     int currentx;
258     int currenty;
259     for (currenty = y; currenty <= h; currenty++)
260         for (currentx = ((currenty % 2 == 0) ? x : x + 1); currentx <= w; currentx += 2)
261             gdk_draw_point (drawable, gc, currentx, currenty);
262 }
263
264 /* Handle exposure events for the color picker's drawing area */
265 static gint
266 hildon_color_field_expose_event                 (GtkWidget *widget, 
267                                                  GdkEventExpose *event,
268                                                  HildonColorButton *cb)
269 {
270     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
271     GdkColor outer_border, inner_border;
272
273     g_assert (priv);
274
275     /* Create the outer border color */
276     outer_border.pixel = 0;
277     outer_border.red   = OUTER_BORDER_RED;
278     outer_border.blue  = OUTER_BORDER_BLUE;
279     outer_border.green = OUTER_BORDER_GREEN;
280
281     /* Create the inner border color */
282     inner_border.pixel = 0;
283     inner_border.red   = INNER_BORDER_RED;
284     inner_border.blue  = INNER_BORDER_BLUE;
285     inner_border.green = INNER_BORDER_GREEN;
286
287     /* serve the outer border color to the Graphic Context */
288     gdk_gc_set_rgb_fg_color (priv->gc, &outer_border);
289     /* draw the outer border as a filled rectangle */
290     gdk_draw_rectangle (widget->window,
291             (GTK_WIDGET_IS_SENSITIVE (widget)) ?  priv->gc : widget->style->bg_gc [GTK_STATE_INSENSITIVE],
292             TRUE,
293             0, 
294             0,
295             widget->allocation.width,
296             widget->allocation.height);
297
298     /* serve the inner border color to the Graphic Context */
299     gdk_gc_set_rgb_fg_color (priv->gc, &inner_border);
300
301     /* draw the inner border as a filled rectangle */
302     gdk_draw_rectangle (widget->window,
303             priv->gc,
304             TRUE,
305             OUTER_BORDER_THICKNESS, 
306             OUTER_BORDER_THICKNESS,
307             widget->allocation.width - (OUTER_BORDER_THICKNESS * 2),
308             widget->allocation.height - (OUTER_BORDER_THICKNESS * 2));
309
310     /* serve the actual color to the Graphic Context */
311     gdk_gc_set_rgb_fg_color(priv->gc, &priv->color);
312
313     /* draw the actual rectangle */
314     gdk_draw_rectangle(widget->window,
315             priv->gc,
316             TRUE,
317             INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
318             INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
319             widget->allocation.width - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2),
320             widget->allocation.height - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2));
321
322     if (! GTK_WIDGET_IS_SENSITIVE (widget)) {
323         draw_grid (GDK_DRAWABLE (widget->window), widget->style->bg_gc [GTK_STATE_INSENSITIVE], 
324                 INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
325                 INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS,
326                 widget->allocation.width  - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2) + 2,
327                 widget->allocation.height - ((INNER_BORDER_THICKNESS + OUTER_BORDER_THICKNESS)*2) + 2);
328     }
329
330     return FALSE;
331 }
332
333 static void
334 hildon_color_button_init                        (HildonColorButton *cb)
335 {
336     GtkWidget *align;
337     GtkWidget *drawing_area;
338     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
339
340     priv->dialog = NULL;
341     priv->gc = NULL;
342     priv->popped = FALSE;
343
344     gtk_widget_push_composite_child ();
345
346     /* create widgets and pixbuf */
347     align = gtk_alignment_new (0.5, 0.5, 0, 0); /* composite widget */
348
349     drawing_area = gtk_drawing_area_new (); /* composite widget */
350
351     /* setting minimum sizes */
352     gtk_widget_set_size_request (GTK_WIDGET (cb), COLOR_BUTTON_WIDTH,
353             COLOR_BUTTON_HEIGHT);
354
355     gtk_widget_set_size_request (GTK_WIDGET(drawing_area),
356             COLOR_FILLED_WIDTH, COLOR_FILLED_HEIGHT);
357
358     /* Connect the callback function for exposure event */
359     g_signal_connect (drawing_area, "expose-event",
360             G_CALLBACK (hildon_color_field_expose_event), cb);
361
362     /* Connect to callback function for key press event */
363     g_signal_connect (G_OBJECT(cb), "key-press-event",
364             G_CALLBACK(hildon_color_button_key_pressed), cb);
365
366     /* packing */
367     gtk_container_add (GTK_CONTAINER (align), drawing_area);
368     gtk_container_add (GTK_CONTAINER (cb), align);
369
370     gtk_widget_show_all (align);
371
372     gtk_widget_pop_composite_child ();
373 }
374
375 /* Free memory used by HildonColorButton */
376 static void
377 hildon_color_button_finalize                    (GObject *object)
378 {
379     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (object);
380     g_assert (priv);
381
382     if (priv->dialog)
383     {
384         gtk_widget_destroy (priv->dialog);
385         priv->dialog = NULL;
386     }
387
388     if (G_OBJECT_CLASS (parent_class)->finalize)
389         G_OBJECT_CLASS (parent_class)->finalize (object);
390 }
391
392 static void
393 hildon_color_button_realize                     (GtkWidget *widget)
394 {
395     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (widget);
396     g_assert (priv);
397
398     GTK_WIDGET_CLASS (parent_class)->realize (widget);
399
400     priv->gc = gdk_gc_new (widget->window);
401 }
402
403 static void
404 hildon_color_button_unrealize                   (GtkWidget *widget)
405 {
406     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (widget);
407     g_assert (priv);
408
409     if (priv->gc != NULL) { 
410         g_object_unref (priv->gc);
411         priv->gc = NULL;
412     }
413
414     GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
415 }
416
417 /* Make the widget sensitive with the keyboard event */
418 static gboolean
419 hildon_color_button_mnemonic_activate           (GtkWidget *widget,
420                                                  gboolean group_cycling)
421 {
422     gtk_widget_grab_focus (widget);
423     return TRUE;
424 }
425
426 /* Popup a color selector dialog on button click */
427 static void
428 hildon_color_button_clicked                     (GtkButton *button)
429 {
430     HildonColorButton *cb = HILDON_COLOR_BUTTON (button);
431     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
432     HildonColorChooserDialog *cs_dialog;
433     
434     g_assert (priv);
435     
436     cs_dialog = (HildonColorChooserDialog *) priv->dialog;
437
438     /* Popup the color selector dialog */
439     if (! cs_dialog)
440     {
441         /* The dialog hasn't been created yet, do it */
442         GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET(cb));
443         priv->dialog = hildon_color_chooser_dialog_new ();
444         cs_dialog = HILDON_COLOR_CHOOSER_DIALOG (priv->dialog);
445         if (parent)
446             gtk_window_set_transient_for (GTK_WINDOW (cs_dialog), GTK_WINDOW (parent));
447
448         g_signal_emit (button, signals[SETUP_DIALOG], 0, priv->dialog);
449     }
450
451     /* Set the initial color for the color selector dialog */
452     hildon_color_chooser_dialog_set_color (cs_dialog, &priv->color);
453
454     /* Update the color for color button if selection was made */
455     priv->popped = TRUE;
456     if (gtk_dialog_run (GTK_DIALOG (cs_dialog)) == GTK_RESPONSE_OK)
457     {
458         hildon_color_chooser_dialog_get_color (cs_dialog, &priv->color);
459         hildon_color_button_set_color (HILDON_COLOR_BUTTON (button), &priv->color);
460         // FIXME A queue-draw should be enough here (not set needed)
461     } 
462
463     gtk_widget_hide (GTK_WIDGET(cs_dialog));
464     priv->popped = FALSE;
465 }
466
467 /* Popup a color selector dialog on hardkey Select press.
468  * FIXME This is a bit hacky, should work without thi. Check. */
469 static gboolean
470 hildon_color_button_key_pressed                 (GtkWidget *button, 
471                                                  GdkEventKey *event,
472                                                  gpointer data)
473 {
474     g_return_val_if_fail (HILDON_IS_COLOR_BUTTON (button), FALSE);
475
476     if (event->keyval == HILDON_HARDKEY_SELECT)
477     {
478         hildon_color_button_clicked (GTK_BUTTON (button));
479         return TRUE;
480     }
481
482     return FALSE;
483 }
484
485 static void
486 hildon_color_button_set_property                (GObject *object, 
487                                                  guint param_id,
488                                                  const GValue *value, 
489                                                  GParamSpec *pspec)
490 {
491     HildonColorButton *cb = HILDON_COLOR_BUTTON (object);
492     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
493     g_assert (priv);
494
495     switch (param_id) 
496     {
497
498         case PROP_COLOR:
499             priv->color = *(GdkColor *) g_value_get_boxed (value); 
500             gtk_widget_queue_draw (GTK_WIDGET (cb));
501             break;
502
503         default:
504             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
505             break;
506     }
507 }
508
509 static void
510 hildon_color_button_get_property                (GObject *object, 
511                                                  guint param_id,
512                                                  GValue *value, 
513                                                  GParamSpec *pspec)
514 {
515     HildonColorButton *cb = HILDON_COLOR_BUTTON (object);
516     HildonColorButtonPrivate *priv = HILDON_COLOR_BUTTON_GET_PRIVATE (cb);
517     g_assert (priv);
518
519     switch (param_id) 
520     {
521
522         case PROP_COLOR:
523             g_value_set_boxed (value, &priv->color);
524             break;
525
526         case PROP_POPUP_SHOWN:
527             g_value_set_boolean (value, priv->popped);
528
529         default:
530             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
531             break;
532     }
533 }
534
535 /**
536  * hildon_color_button_new:
537  *
538  * Creates a new color button. This returns a widget in the form of a
539  * small button containing a swatch representing the selected color.
540  * When the button is clicked, a color-selection dialog will open,
541  * allowing the user to select a color. The swatch will be updated to
542  * reflect the new color when the user finishes.
543  *
544  * Returns: a new color button
545  */
546 GtkWidget*
547 hildon_color_button_new                         (void)
548 {
549     return g_object_new (HILDON_TYPE_COLOR_BUTTON, NULL);
550 }
551
552 /**
553  * hildon_color_button_new_with_color:
554  * @color: a #GdkColor for the initial color
555  *
556  * Creates a new color button with @color as the initial color. 
557  *
558  * Returns: a new color button
559  */
560 GtkWidget*
561 hildon_color_button_new_with_color              (const GdkColor *color)
562 {
563     return g_object_new (HILDON_TYPE_COLOR_BUTTON, "color", color, NULL);
564 }
565
566 /**
567  * hildon_color_button_set_color:
568  * @button: a #HildonColorButton
569  * @color: a color to be set
570  *
571  * Sets the color selected by the button.
572  */
573 void
574 hildon_color_button_set_color                   (HildonColorButton *button, 
575                                                  GdkColor *color)
576 {
577     g_return_if_fail (HILDON_IS_COLOR_BUTTON (button));
578
579     g_object_set (G_OBJECT (button), "color", color, NULL);
580 }
581
582 /**
583  * hildon_color_button_get_popup_shown
584  * @button: a #HildonColorButton
585  *
586  * This function checks if the color button has the color 
587  * selection dialog currently popped-up. 
588  * 
589  * Returns: TRUE if the dialog is popped-up (visible to user).
590  *
591  */
592 gboolean
593 hildon_color_button_get_popup_shown             (HildonColorButton *button)
594 {
595     HildonColorButtonPrivate *priv = NULL; 
596     g_return_val_if_fail (HILDON_IS_COLOR_BUTTON (button), FALSE);
597
598     priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
599     g_assert (priv);
600
601     return priv->popped;
602 }
603
604 /**
605  * hildon_color_button_popdown
606  * @button: a #HildonColorButton
607  *
608  * If the color selection dialog is currently popped-up (visible)
609  * it will be popped-down (hidden).
610  *
611  */
612 void
613 hildon_color_button_popdown                     (HildonColorButton *button)
614 {
615     HildonColorButtonPrivate *priv = NULL; 
616     g_return_if_fail (HILDON_IS_COLOR_BUTTON (button));
617
618     priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
619     g_assert (priv);
620
621     if (priv->popped && priv->dialog) {
622         gtk_dialog_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_CANCEL);
623     }
624 }
625
626 /**
627  * hildon_color_button_get_color:
628  * @button: a #HildonColorButton
629  * @color: a color #GdkColor to be fillled with the current color
630  *
631  */
632 void
633 hildon_color_button_get_color                   (HildonColorButton *button, 
634                                                  GdkColor *color)
635 {
636     HildonColorButtonPrivate *priv = NULL; 
637     g_return_if_fail (HILDON_IS_COLOR_BUTTON (button));
638     g_return_if_fail (color != NULL);
639    
640     priv = HILDON_COLOR_BUTTON_GET_PRIVATE (button);
641     g_assert (priv);
642
643     color->red = priv->color.red;
644     color->green = priv->color.green;
645     color->blue = priv->color.blue;
646     color->pixel = priv->color.pixel;
647 }
648