fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / gtk / colorlabel.c
1 /*
2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3  * Copyright (C) 2001-2018 Hiroyuki Yamamoto & The Claws Mail Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 /* (alfons) - based on a contribution by Satoshi Nagayasu; revised for colorful 
20  * menu and more Sylpheed integration. The idea to put the code in a separate
21  * file is just that it make it easier to allow "user changeable" label colors.
22  */
23
24 #include "config.h"
25 #include "defs.h"
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31
32 #include "colorlabel.h"
33 #include "utils.h"
34 #include "gtkutils.h"
35 #include "prefs_common.h"
36
37 static gchar *labels[COLORLABELS] = {
38         N_("Orange"),
39         N_("Red") ,
40         N_("Pink"),
41         N_("Sky blue"),
42         N_("Blue"),
43         N_("Green"),
44         N_("Brown"),
45         N_("Grey"),
46         N_("Light brown"),
47         N_("Dark red"),
48         N_("Dark pink"),
49         N_("Steel blue"),
50         N_("Gold"),
51         N_("Bright green"),
52         N_("Magenta")
53 };
54
55 #define CL(x) ((gdouble)x / 65535)
56 static GdkRGBA default_colors[COLORLABELS] = {
57         { CL(65535), CL(39168), CL(0),     1.0 },
58         { CL(65535), CL(0),     CL(0),     1.0 },
59         { CL(65535), CL(26112), CL(65535), 1.0 },
60         { CL(0),     CL(52224), CL(65535), 1.0 },
61         { CL(0),     CL(0),     CL(65535), 1.0 },
62         { CL(0),     CL(39168), CL(0),     1.0 },
63         { CL(26112), CL(13056), CL(13056), 1.0 },
64         { CL(43520), CL(43520), CL(43520), 1.0 },
65         { CL(49152), CL(29184), CL(21504), 1.0 },
66         { CL(49152), CL(0),     CL(0),     1.0 },
67         { CL(52224), CL(4096),  CL(29696), 1.0 },
68         { CL(20480), CL(37888), CL(52480), 1.0 },
69         { CL(65535), CL(54528), CL(0),     1.0 },
70         { CL(0),     CL(55296), CL(0),     1.0 },
71         { CL(49152), CL(24576), CL(49152), 1.0 }
72 };
73
74         
75 typedef enum LabelColorChangeFlags_ {
76         LCCF_COLOR = 1 << 0,
77         LCCF_LABEL = 1 << 1,
78         LCCF_ALL   = LCCF_COLOR | LCCF_LABEL
79 } LabelColorChangeFlags;
80
81 /* XXX: if you add colors, make sure you also check the procmsg.h.
82  * color indices are stored as 3 bits; that explains the max. of 7 colors */
83 static struct 
84 {
85         LabelColorChangeFlags   changed; 
86         /* color here is initialized from default_colors[] at startup */
87         GdkRGBA         color;
88
89         /* XXX: note that the label member is supposed to be dynamically 
90          * allocated and freed */
91         gchar                   *label;
92         GtkWidget               *widget;
93 } label_colors[NUM_MENUS][COLORLABELS] = {
94     {
95         { LCCF_ALL, { 0 }, NULL, NULL },
96         { LCCF_ALL, { 0 }, NULL, NULL },
97         { LCCF_ALL, { 0 }, NULL, NULL },
98         { LCCF_ALL, { 0 }, NULL, NULL },
99         { LCCF_ALL, { 0 }, NULL, NULL },
100         { LCCF_ALL, { 0 }, NULL, NULL },
101         { LCCF_ALL, { 0 }, NULL, NULL },
102         { LCCF_ALL, { 0 }, NULL, NULL },
103         { LCCF_ALL, { 0 }, NULL, NULL },
104         { LCCF_ALL, { 0 }, NULL, NULL },
105         { LCCF_ALL, { 0 }, NULL, NULL },
106         { LCCF_ALL, { 0 }, NULL, NULL },
107         { LCCF_ALL, { 0 }, NULL, NULL },
108         { LCCF_ALL, { 0 }, NULL, NULL },
109         { LCCF_ALL, { 0 }, NULL, NULL }},
110     {
111         { LCCF_ALL, { 0 }, NULL, NULL },
112         { LCCF_ALL, { 0 }, NULL, NULL },
113         { LCCF_ALL, { 0 }, NULL, NULL },
114         { LCCF_ALL, { 0 }, NULL, NULL },
115         { LCCF_ALL, { 0 }, NULL, NULL },
116         { LCCF_ALL, { 0 }, NULL, NULL },
117         { LCCF_ALL, { 0 }, NULL, NULL },
118         { LCCF_ALL, { 0 }, NULL, NULL },
119         { LCCF_ALL, { 0 }, NULL, NULL },
120         { LCCF_ALL, { 0 }, NULL, NULL },
121         { LCCF_ALL, { 0 }, NULL, NULL },
122         { LCCF_ALL, { 0 }, NULL, NULL },
123         { LCCF_ALL, { 0 }, NULL, NULL },
124         { LCCF_ALL, { 0 }, NULL, NULL },
125         { LCCF_ALL, { 0 }, NULL, NULL }}
126 };
127
128 #define LABEL_COLOR_WIDTH       28
129 #define LABEL_COLOR_HEIGHT      16
130
131 #define LABEL_COLORS_ELEMS (sizeof label_colors[0] / sizeof label_colors[0][0])
132
133 #define G_RETURN_VAL_IF_INVALID_COLOR(color, val) \
134         do if ((color) < 0 || (color) >= LABEL_COLORS_ELEMS) {  \
135                 return val;                                     \
136         } while(0)
137
138 static void colorlabel_recreate        (gint);
139 static void colorlabel_recreate_label  (gint);
140
141 void colorlabel_update_colortable_from_prefs(void)
142 {
143         gint i, c;
144
145         for (i = 0; i < NUM_MENUS; i++) {
146                 for (c = 0; c < COLORLABELS; c++) {
147                         label_colors[i][c].color = prefs_common.custom_colorlabel[c].color;
148                         g_free(label_colors[i][c].label);
149                         label_colors[i][c].label =
150                                         g_strdup(prefs_common.custom_colorlabel[c].label);
151                 }
152         }
153 }
154
155
156 gint colorlabel_get_color_count(void)
157 {
158         return LABEL_COLORS_ELEMS;
159 }
160
161 GdkRGBA colorlabel_get_color(gint color_index)
162 {
163         GdkRGBA invalid = { 0 };
164
165         G_RETURN_VAL_IF_INVALID_COLOR(color_index, invalid);
166
167         return label_colors[0][color_index].color;
168 }
169
170 GdkRGBA colorlabel_get_default_color(gint color_index)
171 {
172         GdkRGBA invalid = { 0 };
173
174         G_RETURN_VAL_IF_INVALID_COLOR(color_index, invalid);
175
176         return default_colors[color_index];
177 }
178                 
179 gchar *colorlabel_get_color_default_text(gint color_index)
180 {
181         G_RETURN_VAL_IF_INVALID_COLOR(color_index, NULL);
182
183         return labels[color_index];
184 }
185
186 static gboolean colorlabel_drawing_area_expose_event_cb
187         (GtkWidget *widget, cairo_t *cr, gpointer data)
188 {
189         GdkRGBA *color = (GdkRGBA *)data;
190         GtkAllocation allocation;
191
192         gtk_widget_get_allocation(widget, &allocation);
193
194         cairo_set_source_rgb(cr, 0., 0., 0.);
195         cairo_rectangle(cr, 0, 0,
196             allocation.width - 1,
197             allocation.height - 1);
198         cairo_stroke(cr);
199
200         gdk_cairo_set_source_rgba(cr, color);
201         cairo_rectangle(cr, 1, 1,
202             allocation.width - 2,
203             allocation.height - 2);
204         cairo_fill(cr);
205         
206         return FALSE;
207 }
208
209 static GtkWidget *colorlabel_create_color_widget(GdkRGBA *color)
210 {
211         GtkWidget *widget;
212
213         widget = gtk_drawing_area_new();
214         gtk_widget_set_size_request(widget, LABEL_COLOR_WIDTH - 2, 
215                                     LABEL_COLOR_HEIGHT - 4);
216
217         g_signal_connect(G_OBJECT(widget), "draw", 
218                         G_CALLBACK(colorlabel_drawing_area_expose_event_cb),
219                         color);
220
221         return widget;
222 }
223
224 static GdkPixbuf *colorlabel_create_colormenu_pixbuf(GdkRGBA *color)
225 {
226         GdkPixbuf *pixbuf;
227         guint32 pixel = 0;
228
229         cm_return_val_if_fail(color != NULL, NULL);
230
231         pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
232                         LABEL_COLOR_WIDTH - 2, LABEL_COLOR_HEIGHT - 4);
233
234         /* "pixel" needs to be set to 0xrrggbb00 */
235         pixel += (guint32)(color->red   * 255) << 24;
236         pixel += (guint32)(color->green * 255) << 16;
237         pixel += (guint32)(color->blue  * 255) <<  8;
238         gdk_pixbuf_fill(pixbuf, pixel);
239
240         return pixbuf;
241 }
242
243 /* XXX: colorlabel_recreate_XXX are there to make sure everything
244  * is initialized ok, without having to call a global _xxx_init_
245  * function */
246 static void colorlabel_recreate_color(gint color)
247 {
248         GtkWidget *widget;
249         int i;
250         
251         for (i = 0; i < NUM_MENUS; i++) {
252                 if (!(label_colors[i][color].changed & LCCF_COLOR))
253                         continue;
254
255                 widget = colorlabel_create_color_widget(&label_colors[i][color].color);
256                 cm_return_if_fail(widget);
257
258                 if (label_colors[i][color].widget) 
259                         gtk_widget_destroy(label_colors[i][color].widget);
260
261                 label_colors[i][color].widget = widget;         
262                 label_colors[i][color].changed &= ~LCCF_COLOR;
263         }
264 }
265
266 static void colorlabel_recreate_label(gint color)
267 {
268         int i;
269         
270         for (i = 0; i < NUM_MENUS; i++) {
271                 if (!(label_colors[i][color].changed & LCCF_LABEL))
272                         continue;
273
274                 if (label_colors[i][color].label == NULL) 
275                         label_colors[i][color].label = g_strdup(gettext(labels[color]));
276
277                 label_colors[i][color].changed &= ~LCCF_LABEL;
278         }
279 }
280
281 /* XXX: call this function everytime when you're doing important
282  * stuff with the label_colors[] array */
283 static void colorlabel_recreate(gint color)
284 {
285         colorlabel_recreate_label(color);
286         colorlabel_recreate_color(color);
287 }
288
289 static void colorlabel_recreate_all(void)
290 {
291         gint n;
292
293         for ( n = 0; n < LABEL_COLORS_ELEMS; n++) 
294                 colorlabel_recreate(n);
295 }
296
297 /* colorlabel_create_check_color_menu_item() - creates a color
298  * menu item with a check box */
299 GtkWidget *colorlabel_create_check_color_menu_item(gint color_index, gboolean force, gint menu_index)
300 {
301         GtkWidget *label; 
302         GtkWidget *hbox; 
303         GtkWidget *vbox; 
304         GtkWidget *item;
305         gchar *accel;
306         
307         G_RETURN_VAL_IF_INVALID_COLOR(color_index, NULL);
308
309         item = gtk_check_menu_item_new();
310
311         if (force) {
312                 label_colors[menu_index][color_index].changed |= LCCF_COLOR;
313                 label_colors[menu_index][color_index].changed |= LCCF_LABEL;
314         }
315         colorlabel_recreate(color_index);
316
317         /* XXX: gnome-core::panel::menu.c is a great example of
318          * how to create pixmap menus */
319         label = gtk_label_new(label_colors[menu_index][color_index].label);
320
321         gtk_widget_show(label);
322         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
323         gtk_widget_show(hbox);
324         gtk_container_add(GTK_CONTAINER(item), hbox);
325
326         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
327         gtk_widget_show(vbox);
328         gtk_container_set_border_width(GTK_CONTAINER(vbox), 1);
329
330         gtk_container_add(GTK_CONTAINER(vbox),
331                           label_colors[menu_index][color_index].widget);
332         gtk_widget_show(label_colors[menu_index][color_index].widget);
333
334         gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
335         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 4);
336         if (color_index < 9) {
337                 accel = gtk_accelerator_get_label(GDK_KEY_1+color_index, GDK_CONTROL_MASK);
338                 label = gtk_label_new(accel);
339                 gtk_widget_show(label);
340                 gtk_label_set_xalign(GTK_LABEL(label), 1.0);
341                 g_free(accel);
342                 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
343                 g_object_set_data(G_OBJECT(item), "accel_label", label);
344         } else {
345                 label = gtk_label_new("");
346                 gtk_widget_show(label);
347                 gtk_label_set_xalign(GTK_LABEL(label), 1.0);
348                 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
349                 g_object_set_data(G_OBJECT(item), "accel_label", label);
350         }
351         return item;
352 }
353
354 /* colorlabel_create_color_menu() - creates a color menu without 
355  * checkitems, probably for use in combo items */
356 GtkWidget *colorlabel_create_color_menu(void)
357 {
358         GtkWidget *label; 
359         GtkWidget *item;
360         GtkWidget *menu;
361         gint i;
362
363         colorlabel_recreate_all();
364
365         /* create the menu items. each item has its color code attached */
366         menu = gtk_menu_new();
367         g_object_set_data(G_OBJECT(menu), "label_color_menu", menu);
368
369         item = gtk_menu_item_new_with_label(_("None"));
370         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
371         g_object_set_data(G_OBJECT(item), "color", GUINT_TO_POINTER(0));
372         gtk_widget_show(item);
373
374         item = gtk_separator_menu_item_new();
375         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
376         gtk_widget_show(item);
377
378         /* and the color items */
379         for (i = 0; i < LABEL_COLORS_ELEMS; i++) {
380                 GtkWidget *hbox; 
381                 GtkWidget *vbox;
382                 GtkWidget *widget;
383
384                 item  = gtk_menu_item_new();
385                 g_object_set_data(G_OBJECT(item), "color",
386                                   GUINT_TO_POINTER(i + 1));
387
388                 label = gtk_label_new(label_colors[0][i].label);
389                 
390                 gtk_widget_show(label);
391                 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
392                 gtk_widget_show(hbox);
393                 gtk_container_add(GTK_CONTAINER(item), hbox);
394
395                 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
396                 gtk_widget_show(vbox);
397                 gtk_container_set_border_width(GTK_CONTAINER(vbox), 1);
398
399                 widget = colorlabel_create_color_widget(&label_colors[0][i].color);
400                 gtk_widget_show(widget);
401                 gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);
402
403                 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
404                 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 4);
405                 
406                 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
407                 gtk_widget_show(item);
408         }
409         
410         gtk_widget_show(menu);
411
412         return menu;
413 }
414
415 guint colorlabel_get_color_menu_active_item(GtkWidget *menu)
416 {
417         GtkWidget *menuitem;
418         guint color;
419
420         menuitem = gtk_menu_get_active(GTK_MENU(menu));
421         color = GPOINTER_TO_UINT
422                 (g_object_get_data(G_OBJECT(menuitem), "color"));
423         return color;
424 }
425
426 static gboolean colormenu_separator_func(GtkTreeModel *model,
427                 GtkTreeIter *iter, gpointer data)
428 {
429         gchar *txt;
430
431         gtk_tree_model_get(model, iter, COLORMENU_COL_TEXT, &txt, -1);
432
433         if (txt == NULL)
434                 return TRUE;
435
436         return FALSE;
437 }
438
439 GtkWidget *colorlabel_create_combobox_colormenu()
440 {
441         GtkWidget *combobox;
442         GtkListStore *store;
443         GtkCellRenderer *renderer;
444
445         store = gtk_list_store_new(3,
446                         GDK_TYPE_PIXBUF,
447                         G_TYPE_STRING,
448                         G_TYPE_INT);
449         combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
450
451         gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(combobox),
452                         (GtkTreeViewRowSeparatorFunc)colormenu_separator_func,
453                         NULL, NULL);
454
455         renderer = gtk_cell_renderer_pixbuf_new();
456         gtk_cell_renderer_set_padding(renderer, 2, 0);
457         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox),
458                         renderer, FALSE);
459         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox),
460                         renderer,
461                         "pixbuf", COLORMENU_COL_PIXBUF,
462                         NULL);
463
464         renderer = gtk_cell_renderer_text_new();
465         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox),
466                         renderer, TRUE);
467         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox),
468                         renderer,
469                         "text", COLORMENU_COL_TEXT,
470                         NULL);
471
472         colorlabel_refill_combobox_colormenu(GTK_COMBO_BOX(combobox));
473
474         return combobox;
475 }
476
477 void colorlabel_refill_combobox_colormenu(GtkComboBox *combobox)
478 {
479         GtkListStore *store;
480         GtkTreeIter iter;
481         gint i;
482
483         cm_return_if_fail(combobox != NULL);
484
485         store = GTK_LIST_STORE(gtk_combo_box_get_model(combobox));
486
487         cm_return_if_fail(store != NULL);
488
489         gtk_list_store_clear(store);
490
491         /* "None" */
492         gtk_list_store_append(store, &iter);
493         gtk_list_store_set(store, &iter,
494                         COLORMENU_COL_PIXBUF, NULL,
495                         COLORMENU_COL_TEXT, _("None"),
496                         COLORMENU_COL_ID, -1,
497                         -1);
498         /* Separator */
499         gtk_list_store_append(store, &iter);
500         gtk_list_store_set(store, &iter,
501                         COLORMENU_COL_PIXBUF, NULL,
502                         COLORMENU_COL_TEXT, NULL,
503                         COLORMENU_COL_ID, -1,
504                         -1);
505
506         /* Menu items for individual colors */
507         for (i = 0; i < LABEL_COLORS_ELEMS; i++) {
508                 gtk_list_store_append(store, &iter);
509                 gtk_list_store_set(store, &iter,
510                                 COLORMENU_COL_PIXBUF, colorlabel_create_colormenu_pixbuf(&label_colors[0][i].color),
511                                 COLORMENU_COL_TEXT, label_colors[0][i].label,
512                                 COLORMENU_COL_ID, i,
513                                 -1);
514         }
515 }
516
517 gint colorlabel_get_combobox_colormenu_active(GtkComboBox *combobox)
518 {
519         gint value;
520         GtkTreeIter iter;
521         GtkTreeModel *model;
522
523         cm_return_val_if_fail(combobox != NULL, 0);
524
525         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combobox), &iter))
526                 return 0;
527
528         model = gtk_combo_box_get_model(GTK_COMBO_BOX(combobox));
529         gtk_tree_model_get(model, &iter,
530                         COLORMENU_COL_ID, &value,
531                         -1);
532
533         return value + 1;
534 }
535
536 void colorlabel_set_combobox_colormenu_active(GtkComboBox *combobox,
537                 gint color)
538 {
539         GtkTreeModel *model;
540         GtkTreeIter iter;
541         gint id;
542
543         cm_return_if_fail(combobox != NULL);
544
545         model = gtk_combo_box_get_model(combobox);
546         cm_return_if_fail(model != NULL);
547
548         if (!gtk_tree_model_get_iter_first(model, &iter))
549                 return;
550
551         do {
552                 gtk_tree_model_get(model, &iter,
553                                 COLORMENU_COL_ID, &id,
554                                 -1);
555
556                 if (id == color - 1)
557                         break;
558         } while (gtk_tree_model_iter_next(model, &iter));
559
560         gtk_combo_box_set_active_iter(combobox, &iter);
561 }