301b7f6ca95621fbee7d84b13197304e740126f8
[claws.git] / src / gtk / gtkutils.c
1 /*
2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2024 the Claws Mail team and Hiroyuki Yamamoto
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 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <gdk/gdk.h>
28 #include <gtk/gtk.h>
29 #include "gtk/gtksctree.h"
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <sys/stat.h>
33
34 #include "combobox.h"
35
36 #if HAVE_LIBCOMPFACE
37 #  include <compface.h>
38 #endif
39
40 #if HAVE_LIBCOMPFACE
41 #define XPM_XFACE_HEIGHT        (HEIGHT + 3)  /* 3 = 1 header + 2 colors */
42 #endif
43
44 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
45 #  include <wchar.h>
46 #  include <wctype.h>
47 #endif
48
49 #include "defs.h"
50 #include "gtkutils.h"
51 #include "utils.h"
52 #include "gtksctree.h"
53 #include "codeconv.h"
54 #include "stock_pixmap.h"
55 #include "menu.h"
56 #include "prefs_account.h"
57 #include "prefs_common.h"
58 #include "manage_window.h"
59 #include "manual.h"
60
61 gboolean gtkut_get_font_size(GtkWidget *widget,
62                              gint *width, gint *height)
63 {
64         PangoLayout *layout;
65         const gchar *str = "Abcdef";
66
67         cm_return_val_if_fail(GTK_IS_WIDGET(widget), FALSE);
68
69         layout = gtk_widget_create_pango_layout(widget, str);
70         cm_return_val_if_fail(layout, FALSE);
71         pango_layout_get_pixel_size(layout, width, height);
72         if (width)
73                 *width = *width / g_utf8_strlen(str, -1);
74         g_object_unref(layout);
75
76         return TRUE;
77 }
78
79 void gtkut_widget_set_small_font_size(GtkWidget *widget)
80 {
81         PangoFontDescription *font_desc;
82         gint size;
83
84         cm_return_if_fail(widget != NULL);
85         cm_return_if_fail(gtk_widget_get_style(widget) != NULL);
86
87         if (prefs_common.derive_from_normal_font || !SMALL_FONT) {
88                 font_desc = pango_font_description_from_string(NORMAL_FONT);
89                 size = pango_font_description_get_size(font_desc);
90                 pango_font_description_set_size(font_desc, size * PANGO_SCALE_SMALL);
91                 gtk_widget_override_font(widget, font_desc);
92                 pango_font_description_free(font_desc);
93         } else {
94                 font_desc = pango_font_description_from_string(SMALL_FONT);
95                 gtk_widget_override_font(widget, font_desc);
96                 pango_font_description_free(font_desc);
97         }
98 }
99
100 void gtkut_stock_button_add_help(GtkWidget *bbox, GtkWidget **help_btn)
101 {
102         cm_return_if_fail(bbox != NULL);
103
104         *help_btn = gtkut_stock_button("help-browser", "Help");
105
106         gtk_widget_set_can_default(*help_btn, TRUE);
107         gtk_box_pack_end(GTK_BOX (bbox), *help_btn, TRUE, TRUE, 0);
108         gtk_button_box_set_child_secondary(GTK_BUTTON_BOX (bbox),
109                         *help_btn, TRUE);
110         gtk_widget_set_sensitive(*help_btn,
111                         manual_available(MANUAL_MANUAL_CLAWS));
112         gtk_widget_show(*help_btn);
113 }
114
115 void gtkut_stock_button_set_create_with_help(GtkWidget **bbox,
116                 GtkWidget **help_button,
117                 GtkWidget **button1, const gchar *stock_icon1, const gchar *label1,
118                 GtkWidget **button2, const gchar *stock_icon2, const gchar *label2,
119                 GtkWidget **button3, const gchar *stock_icon3, const gchar *label3)
120 {
121         cm_return_if_fail(bbox != NULL);
122         cm_return_if_fail(button1 != NULL);
123
124         gtkut_stock_button_set_create(bbox, button1, stock_icon1, label1,
125                         button2, stock_icon2, label2, button3, stock_icon3, label3);
126
127         gtkut_stock_button_add_help(*bbox, help_button);
128 }
129
130 void gtkut_stock_button_set_create(GtkWidget **bbox,
131                                    GtkWidget **button1, const gchar *stock_icon1, const gchar *label1,
132                                    GtkWidget **button2, const gchar *stock_icon2, const gchar *label2,
133                                    GtkWidget **button3, const gchar *stock_icon3, const gchar *label3)
134 {
135         cm_return_if_fail(bbox != NULL);
136         cm_return_if_fail(button1 != NULL);
137
138         *bbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
139         gtk_button_box_set_layout(GTK_BUTTON_BOX(*bbox), GTK_BUTTONBOX_END);
140         gtk_box_set_spacing(GTK_BOX(*bbox), 5);
141
142         *button1 = gtk_button_new_with_mnemonic(label1);
143         gtk_button_set_image(GTK_BUTTON(*button1),
144                 gtk_image_new_from_icon_name(stock_icon1, GTK_ICON_SIZE_BUTTON));
145         gtk_widget_set_can_default(*button1, TRUE);
146         gtk_box_pack_start(GTK_BOX(*bbox), *button1, TRUE, TRUE, 0);
147         gtk_widget_show(*button1);
148
149         if (button2) {
150                 *button2 = gtk_button_new_with_mnemonic(label2);
151                 gtk_button_set_image(GTK_BUTTON(*button2),
152                         gtk_image_new_from_icon_name(stock_icon2, GTK_ICON_SIZE_BUTTON));
153                 gtk_widget_set_can_default(*button2, TRUE);
154                 gtk_box_pack_start(GTK_BOX(*bbox), *button2, TRUE, TRUE, 0);
155                 gtk_widget_show(*button2);
156         }
157
158         if (button3) {
159                 *button3 = gtk_button_new_with_mnemonic(label3);
160                 gtk_button_set_image(GTK_BUTTON(*button3),
161                         gtk_image_new_from_icon_name(stock_icon3, GTK_ICON_SIZE_BUTTON));
162                 gtk_widget_set_can_default(*button3, TRUE);
163                 gtk_box_pack_start(GTK_BOX(*bbox), *button3, TRUE, TRUE, 0);
164                 gtk_widget_show(*button3);
165         }
166 }
167
168 void gtkut_stock_with_text_button_set_create(GtkWidget **bbox,
169                                    GtkWidget **button1, const gchar *label1, const gchar *text1,
170                                    GtkWidget **button2, const gchar *label2, const gchar *text2,
171                                    GtkWidget **button3, const gchar *label3, const gchar *text3)
172 {
173         cm_return_if_fail(bbox != NULL);
174         cm_return_if_fail(button1 != NULL);
175
176         *bbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
177         gtk_button_box_set_layout(GTK_BUTTON_BOX(*bbox), GTK_BUTTONBOX_END);
178         gtk_box_set_spacing(GTK_BOX(*bbox), 5);
179
180         *button1 = gtk_button_new_with_mnemonic(text1);
181         gtk_button_set_image(GTK_BUTTON(*button1),
182                 gtk_image_new_from_icon_name(label1, GTK_ICON_SIZE_BUTTON));
183         gtk_widget_set_can_default(*button1, TRUE);
184         gtk_box_pack_start(GTK_BOX(*bbox), *button1, TRUE, TRUE, 0);
185         gtk_widget_show(*button1);
186
187         if (button2) {
188                 *button2 = gtk_button_new_with_mnemonic(text2);
189                 gtk_button_set_image(GTK_BUTTON(*button2),
190                         gtk_image_new_from_icon_name(label2, GTK_ICON_SIZE_BUTTON));
191                 gtk_widget_set_can_default(*button2, TRUE);
192                 gtk_box_pack_start(GTK_BOX(*bbox), *button2, TRUE, TRUE, 0);
193                 gtk_widget_show(*button2);
194         }
195
196         if (button3) {
197                 *button3 = gtk_button_new_with_mnemonic(text3);
198                 gtk_button_set_image(GTK_BUTTON(*button3),
199                         gtk_image_new_from_icon_name(label3, GTK_ICON_SIZE_BUTTON));
200                 gtk_widget_set_can_default(*button3, TRUE);
201                 gtk_box_pack_start(GTK_BOX(*bbox), *button3, TRUE, TRUE, 0);
202                 gtk_widget_show(*button3);
203         }
204 }
205
206 #define CELL_SPACING 1
207 #define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
208                                     (((row) + 1) * CELL_SPACING) + \
209                                     (clist)->voffset)
210 #define ROW_FROM_YPIXEL(clist, y) (((y) - (clist)->voffset) / \
211                                    ((clist)->row_height + CELL_SPACING))
212
213 void gtkut_ctree_node_move_if_on_the_edge(GtkCMCTree *ctree, GtkCMCTreeNode *node, gint _row)
214 {
215         GtkCMCList *clist = GTK_CMCLIST(ctree);
216         gint row;
217         GtkVisibility row_visibility, prev_row_visibility, next_row_visibility;
218         gfloat row_align;
219
220         cm_return_if_fail(ctree != NULL);
221         cm_return_if_fail(node != NULL);
222
223         row = (_row != -1 ? _row : g_list_position(clist->row_list, (GList *)node));
224
225         if (row < 0 || row >= clist->rows || clist->row_height == 0) return;
226         row_visibility = gtk_cmclist_row_is_visible(clist, row);
227         prev_row_visibility = gtk_cmclist_row_is_visible(clist, row - 1);
228         next_row_visibility = gtk_cmclist_row_is_visible(clist, row + 1);
229
230         if (row_visibility == GTK_VISIBILITY_NONE) {
231                 row_align = 0.5;
232                 if (gtk_cmclist_row_is_above_viewport(clist, row))
233                         row_align = 0.2;
234                 else if (gtk_cmclist_row_is_below_viewport(clist, row))
235                         row_align = 0.8;
236                 gtk_cmclist_moveto(clist, row, -1, row_align, 0);
237                 return;
238         }
239         if (row_visibility == GTK_VISIBILITY_FULL &&
240             prev_row_visibility == GTK_VISIBILITY_FULL &&
241             next_row_visibility == GTK_VISIBILITY_FULL)
242                 return;
243         if (prev_row_visibility != GTK_VISIBILITY_FULL &&
244             next_row_visibility != GTK_VISIBILITY_FULL)
245                 return;
246
247         if (prev_row_visibility != GTK_VISIBILITY_FULL) {
248                 gtk_cmclist_moveto(clist, row, -1, 0.2, 0);
249                 return;
250         }
251         if (next_row_visibility != GTK_VISIBILITY_FULL) {
252                 gtk_cmclist_moveto(clist, row, -1, 0.8, 0);
253                 return;
254         }
255 }
256
257 #undef CELL_SPACING
258 #undef ROW_TOP_YPIXEL
259 #undef ROW_FROM_YPIXEL
260
261 gint gtkut_ctree_get_nth_from_node(GtkCMCTree *ctree, GtkCMCTreeNode *node)
262 {
263         cm_return_val_if_fail(ctree != NULL, -1);
264         cm_return_val_if_fail(node != NULL, -1);
265
266         return g_list_position(GTK_CMCLIST(ctree)->row_list, (GList *)node);
267 }
268
269 /* get the next node, including the invisible one */
270 GtkCMCTreeNode *gtkut_ctree_node_next(GtkCMCTree *ctree, GtkCMCTreeNode *node)
271 {
272         GtkCMCTreeNode *parent;
273
274         if (!node) return NULL;
275
276         if (GTK_CMCTREE_ROW(node)->children)
277                 return GTK_CMCTREE_ROW(node)->children;
278
279         if (GTK_CMCTREE_ROW(node)->sibling)
280                 return GTK_CMCTREE_ROW(node)->sibling;
281
282         for (parent = GTK_CMCTREE_ROW(node)->parent; parent != NULL;
283              parent = GTK_CMCTREE_ROW(parent)->parent) {
284                 if (GTK_CMCTREE_ROW(parent)->sibling)
285                         return GTK_CMCTREE_ROW(parent)->sibling;
286         }
287
288         return NULL;
289 }
290
291 /* get the previous node, including the invisible one */
292 GtkCMCTreeNode *gtkut_ctree_node_prev(GtkCMCTree *ctree, GtkCMCTreeNode *node)
293 {
294         GtkCMCTreeNode *prev;
295         GtkCMCTreeNode *child;
296
297         if (!node) return NULL;
298
299         prev = GTK_CMCTREE_NODE_PREV(node);
300         if (prev == GTK_CMCTREE_ROW(node)->parent)
301                 return prev;
302
303         child = prev;
304         while (GTK_CMCTREE_ROW(child)->children != NULL) {
305                 child = GTK_CMCTREE_ROW(child)->children;
306                 while (GTK_CMCTREE_ROW(child)->sibling != NULL)
307                         child = GTK_CMCTREE_ROW(child)->sibling;
308         }
309
310         return child;
311 }
312
313 gboolean gtkut_ctree_node_is_selected(GtkCMCTree *ctree, GtkCMCTreeNode *node)
314 {
315         GtkCMCList *clist = GTK_CMCLIST(ctree);
316         GList *cur;
317
318         for (cur = clist->selection; cur != NULL; cur = cur->next) {
319                 if (node == GTK_CMCTREE_NODE(cur->data))
320                         return TRUE;
321         }
322
323         return FALSE;
324 }
325
326 GtkCMCTreeNode *gtkut_ctree_find_collapsed_parent(GtkCMCTree *ctree,
327                                                 GtkCMCTreeNode *node)
328 {
329         if (!node) return NULL;
330
331         while ((node = GTK_CMCTREE_ROW(node)->parent) != NULL) {
332                 if (!GTK_CMCTREE_ROW(node)->expanded)
333                         return node;
334         }
335
336         return NULL;
337 }
338
339 void gtkut_ctree_expand_parent_all(GtkCMCTree *ctree, GtkCMCTreeNode *node)
340 {
341         gtk_cmclist_freeze(GTK_CMCLIST(ctree));
342
343         while ((node = gtkut_ctree_find_collapsed_parent(ctree, node)) != NULL)
344                 gtk_cmctree_expand(ctree, node);
345
346         gtk_cmclist_thaw(GTK_CMCLIST(ctree));
347 }
348
349 gboolean gtkut_ctree_node_is_parent(GtkCMCTreeNode *parent, GtkCMCTreeNode *node)
350 {
351         GtkCMCTreeNode *tmp;
352         cm_return_val_if_fail(node != NULL, FALSE);
353         cm_return_val_if_fail(parent != NULL, FALSE);
354         tmp = node;
355         
356         while (tmp) {
357                 if(GTK_CMCTREE_ROW(tmp)->parent && GTK_CMCTREE_ROW(tmp)->parent == parent)
358                         return TRUE;
359                 tmp = GTK_CMCTREE_ROW(tmp)->parent;
360         }
361         
362         return FALSE;
363 }
364
365 void gtkut_ctree_set_focus_row(GtkCMCTree *ctree, GtkCMCTreeNode *node)
366 {
367         if (node == NULL)
368                 return;
369         gtkut_clist_set_focus_row(GTK_CMCLIST(ctree),
370                                   gtkut_ctree_get_nth_from_node(ctree, node));
371 }
372
373 void gtkut_clist_set_focus_row(GtkCMCList *clist, gint row)
374 {
375         clist->focus_row = row;
376         GTKUT_CTREE_REFRESH(clist);
377 }
378
379 static gboolean gtkut_text_buffer_match_string(GtkTextBuffer *textbuf,
380                                         const GtkTextIter *iter,
381                                         gunichar *wcs, gint len,
382                                         gboolean case_sens)
383 {
384         GtkTextIter start_iter, end_iter;
385         gchar *utf8str, *p;
386         gint match_count;
387
388         start_iter = end_iter = *iter;
389         gtk_text_iter_forward_chars(&end_iter, len);
390
391         utf8str = gtk_text_buffer_get_text(textbuf, &start_iter, &end_iter,
392                                            FALSE);
393         if (!utf8str) return FALSE;
394
395         if ((gint)g_utf8_strlen(utf8str, -1) != len) {
396                 g_free(utf8str);
397                 return FALSE;
398         }
399
400         for (p = utf8str, match_count = 0;
401              *p != '\0' && match_count < len;
402              p = g_utf8_next_char(p), match_count++) {
403                 gunichar wc;
404
405                 wc = g_utf8_get_char(p);
406
407                 if (case_sens) {
408                         if (wc != wcs[match_count])
409                                 break;
410                 } else {
411                         if (g_unichar_tolower(wc) !=
412                             g_unichar_tolower(wcs[match_count]))
413                                 break;
414                 }
415         }
416
417         g_free(utf8str);
418
419         if (match_count == len)
420                 return TRUE;
421         else
422                 return FALSE;
423 }
424
425 static gboolean gtkut_text_buffer_find(GtkTextBuffer *buffer, const GtkTextIter *iter,
426                                 const gchar *str, gboolean case_sens,
427                                 GtkTextIter *match_pos)
428 {
429         gunichar *wcs;
430         gint len;
431         glong items_read = 0, items_written = 0;
432         GError *error = NULL;
433         GtkTextIter iter_;
434         gboolean found = FALSE;
435
436         wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error);
437         if (error != NULL) {
438                 g_warning("an error occurred while converting a string from UTF-8 to UCS-4: %s",
439                           error->message);
440                 g_error_free(error);
441         }
442         if (!wcs || items_written <= 0) return FALSE;
443         len = (gint)items_written;
444
445         iter_ = *iter;
446         do {
447                 found = gtkut_text_buffer_match_string
448                         (buffer, &iter_, wcs, len, case_sens);
449                 if (found) {
450                         *match_pos = iter_;
451                         break;
452                 }
453         } while (gtk_text_iter_forward_char(&iter_));
454
455         g_free(wcs);
456
457         return found;
458 }
459
460 static gboolean gtkut_text_buffer_find_backward(GtkTextBuffer *buffer,
461                                          const GtkTextIter *iter,
462                                          const gchar *str, gboolean case_sens,
463                                          GtkTextIter *match_pos)
464 {
465         gunichar *wcs;
466         gint len;
467         glong items_read = 0, items_written = 0;
468         GError *error = NULL;
469         GtkTextIter iter_;
470         gboolean found = FALSE;
471
472         wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error);
473         if (error != NULL) {
474                 g_warning("an error occurred while converting a string from UTF-8 to UCS-4: %s",
475                           error->message);
476                 g_error_free(error);
477         }
478         if (!wcs || items_written <= 0) return FALSE;
479         len = (gint)items_written;
480
481         iter_ = *iter;
482         while (gtk_text_iter_backward_char(&iter_)) {
483                 found = gtkut_text_buffer_match_string
484                         (buffer, &iter_, wcs, len, case_sens);
485                 if (found) {
486                         *match_pos = iter_;
487                         break;
488                 }
489         }
490
491         g_free(wcs);
492
493         return found;
494 }
495
496 gchar *gtkut_text_view_get_selection(GtkTextView *textview)
497 {
498         GtkTextBuffer *buffer;
499         GtkTextIter start_iter, end_iter;
500         gboolean found;
501
502         cm_return_val_if_fail(GTK_IS_TEXT_VIEW(textview), NULL);
503
504         buffer = gtk_text_view_get_buffer(textview);
505         found = gtk_text_buffer_get_selection_bounds(buffer,
506                                                      &start_iter,
507                                                      &end_iter);
508         if (found)
509                 return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter,
510                                                 FALSE);
511         else
512                 return NULL;
513 }
514
515
516 void gtkut_text_view_set_position(GtkTextView *text, gint pos)
517 {
518         GtkTextBuffer *buffer;
519         GtkTextIter iter;
520         GtkTextMark *mark;
521
522         cm_return_if_fail(text != NULL);
523
524         buffer = gtk_text_view_get_buffer(text);
525
526         gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
527         gtk_text_buffer_place_cursor(buffer, &iter);
528         mark = gtk_text_buffer_create_mark(buffer, NULL, &iter, TRUE);
529         gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
530 }
531
532 gboolean gtkut_text_view_search_string(GtkTextView *text, const gchar *str,
533                                         gboolean case_sens)
534 {
535         GtkTextBuffer *buffer;
536         GtkTextIter iter, match_pos;
537         GtkTextMark *mark;
538         gint len;
539
540         cm_return_val_if_fail(text != NULL, FALSE);
541         cm_return_val_if_fail(str != NULL, FALSE);
542
543         buffer = gtk_text_view_get_buffer(text);
544
545         len = g_utf8_strlen(str, -1);
546         cm_return_val_if_fail(len >= 0, FALSE);
547
548         mark = gtk_text_buffer_get_insert(buffer);
549         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
550
551         if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
552                                    &match_pos)) {
553                 GtkTextIter end = match_pos;
554
555                 gtk_text_iter_forward_chars(&end, len);
556                 /* place "insert" at the last character */
557                 gtk_text_buffer_select_range(buffer, &end, &match_pos);
558                 gtk_text_view_scroll_to_mark(text, mark, 0.0, TRUE, 0.0, 0.5);
559                 return TRUE;
560         }
561
562         return FALSE;
563 }
564
565 gboolean gtkut_text_view_search_string_backward(GtkTextView *text, const gchar *str,
566                                         gboolean case_sens)
567 {
568         GtkTextBuffer *buffer;
569         GtkTextIter iter, match_pos;
570         GtkTextMark *mark;
571         gint len;
572
573         cm_return_val_if_fail(text != NULL, FALSE);
574         cm_return_val_if_fail(str != NULL, FALSE);
575
576         buffer = gtk_text_view_get_buffer(text);
577
578         len = g_utf8_strlen(str, -1);
579         cm_return_val_if_fail(len >= 0, FALSE);
580
581         mark = gtk_text_buffer_get_insert(buffer);
582         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
583
584         if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
585                                             &match_pos)) {
586                 GtkTextIter end = match_pos;
587
588                 gtk_text_iter_forward_chars(&end, len);
589                 gtk_text_buffer_select_range(buffer, &match_pos, &end);
590                 gtk_text_view_scroll_to_mark(text, mark, 0.0, TRUE, 0.0, 0.5);
591                 return TRUE;
592         }
593
594         return FALSE;
595 }
596
597 void gtkut_window_popup(GtkWidget *window)
598 {
599         GdkWindow *gdkwin;
600         gint x, y, sx, sy, new_x, new_y;
601         GdkRectangle workarea = {0};
602
603         gdkwin = gtk_widget_get_window(window);
604
605         cm_return_if_fail(window != NULL);
606         cm_return_if_fail(gdkwin != NULL);
607
608         gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
609                                  &workarea);
610
611         sx = MAX(1, workarea.width);
612         sy = MAX(1, workarea.height);
613
614         gdk_window_get_origin(gdkwin, &x, &y);
615         new_x = x % sx; if (new_x < 0) new_x = 0;
616         new_y = y % sy; if (new_y < 0) new_y = 0;
617         if (new_x != x || new_y != y)
618                 gdk_window_move(gdkwin, new_x, new_y);
619
620         gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window), FALSE);
621         gtk_window_present_with_time(GTK_WINDOW(window), time(NULL));
622 }
623
624 void gtkut_widget_get_uposition(GtkWidget *widget, gint *px, gint *py)
625 {
626         GdkWindow *gdkwin;
627         gint x, y;
628         gint sx, sy;
629         GdkRectangle workarea = {0};
630
631         gdkwin = gtk_widget_get_window(widget);
632
633         cm_return_if_fail(widget != NULL);
634         cm_return_if_fail(gdkwin != NULL);
635
636         gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
637                                  &workarea);
638
639         sx = MAX(1, workarea.width);
640         sy = MAX(1, workarea.height);
641
642         /* gdk_window_get_root_origin ever return *rootwindow*'s position */
643         gdk_window_get_root_origin(gdkwin, &x, &y);
644
645         x %= sx; if (x < 0) x = 0;
646         y %= sy; if (y < 0) y = 0;
647         *px = x;
648         *py = y;
649 }
650
651 static void gtkut_clist_bindings_add(GtkWidget *clist)
652 {
653         GtkBindingSet *binding_set;
654
655         binding_set = gtk_binding_set_by_class
656                 (GTK_CMCLIST_GET_CLASS(clist));
657
658         gtk_binding_entry_add_signal(binding_set, GDK_KEY_n, GDK_CONTROL_MASK,
659                                      "scroll_vertical", 2,
660                                      G_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
661                                      G_TYPE_FLOAT, 0.0);
662         gtk_binding_entry_add_signal(binding_set, GDK_KEY_p, GDK_CONTROL_MASK,
663                                      "scroll_vertical", 2,
664                                      G_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
665                                      G_TYPE_FLOAT, 0.0);
666 }
667
668 void gtkut_widget_init(void)
669 {
670         GtkWidget *clist;
671
672         clist = gtk_cmclist_new(1);
673         g_object_ref(G_OBJECT(clist));
674         g_object_ref_sink (G_OBJECT(clist));
675         gtkut_clist_bindings_add(clist);
676         g_object_unref(G_OBJECT(clist));
677
678         clist = gtk_cmctree_new(1, 0);
679         g_object_ref(G_OBJECT(clist));
680         g_object_ref_sink (G_OBJECT(clist));
681         gtkut_clist_bindings_add(clist);
682         g_object_unref(G_OBJECT(clist));
683
684         clist = gtk_sctree_new_with_titles(1, 0, NULL);
685         g_object_ref(G_OBJECT(clist));
686         g_object_ref_sink (G_OBJECT(clist));
687         gtkut_clist_bindings_add(clist);
688         g_object_unref(G_OBJECT(clist));
689 }
690
691 void gtkut_widget_set_app_icon(GtkWidget *widget)
692 {
693         static GList *icon_list = NULL;
694
695         cm_return_if_fail(widget != NULL);
696         cm_return_if_fail(gtk_widget_get_window(widget) != NULL);
697         if (!icon_list) {
698                 GdkPixbuf *icon = NULL, *big_icon = NULL;
699                 priv_pixbuf_gdk(PRIV_PIXMAP_CLAWS_MAIL_ICON, &icon);
700                 priv_pixbuf_gdk(PRIV_PIXMAP_CLAWS_MAIL_LOGO, &big_icon);
701                 if (icon)
702                         icon_list = g_list_append(icon_list, icon);
703                 if (big_icon)
704                         icon_list = g_list_append(icon_list, big_icon);
705         }
706         if (icon_list)
707                 gtk_window_set_icon_list(GTK_WINDOW(widget), icon_list);
708 }
709
710 void gtkut_widget_set_composer_icon(GtkWidget *widget)
711 {
712         static GList *icon_list = NULL;
713
714         cm_return_if_fail(widget != NULL);
715         cm_return_if_fail(gtk_widget_get_window(widget) != NULL);
716         if (!icon_list) {
717                 GdkPixbuf *icon = NULL, *big_icon = NULL;
718                 stock_pixbuf_gdk(STOCK_PIXMAP_MAIL_COMPOSE, &icon);
719                 stock_pixbuf_gdk(STOCK_PIXMAP_MAIL_COMPOSE_LOGO, &big_icon);
720                 if (icon)
721                         icon_list = g_list_append(icon_list, icon);
722                 if (big_icon)
723                         icon_list = g_list_append(icon_list, big_icon);
724         }
725         if (icon_list)
726                 gtk_window_set_icon_list(GTK_WINDOW(widget), icon_list);
727 }
728
729 static gboolean move_bar = FALSE;
730 static guint move_bar_id;
731
732 static gboolean move_bar_cb(gpointer data)
733 {
734         GtkWidget *w = (GtkWidget *)data;
735         if (!move_bar)
736                 return FALSE;
737
738         if (!GTK_IS_PROGRESS_BAR(w)) {
739                 return FALSE;
740         }
741         gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(w), 0.1);
742         gtk_progress_bar_pulse(GTK_PROGRESS_BAR(w));
743         GTK_EVENTS_FLUSH();
744         return TRUE;
745 }
746
747 GtkWidget *label_window_create(const gchar *str)
748 {
749         GtkWidget *window;
750         GtkWidget *label, *vbox, *hbox;
751         GtkWidget *wait_progress = gtk_progress_bar_new();
752
753         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "gtkutils");
754         gtk_widget_set_size_request(window, 380, 70);
755         gtk_container_set_border_width(GTK_CONTAINER(window), 8);
756         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
757         gtk_window_set_title(GTK_WINDOW(window), str);
758         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
759         gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
760         manage_window_set_transient(GTK_WINDOW(window));
761
762         label = gtk_label_new(str);
763         
764         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
765         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
766         gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, FALSE, 0);
767         gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0);
768         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
769         gtk_box_pack_start(GTK_BOX(hbox), wait_progress, TRUE, FALSE, 0);
770         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
771         
772         gtk_container_add(GTK_CONTAINER(window), vbox);
773         gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
774         gtk_widget_show_all(vbox);
775
776         gtk_widget_show_now(window);
777         
778         if (move_bar_id == 0) {
779                 move_bar_id = g_timeout_add(200, move_bar_cb, wait_progress);
780                 move_bar = TRUE;
781         }
782
783         GTK_EVENTS_FLUSH();
784
785         return window;
786 }
787
788 void label_window_destroy(GtkWidget *window)
789 {
790         move_bar = FALSE;
791         g_source_remove(move_bar_id);
792         move_bar_id = 0;
793         GTK_EVENTS_FLUSH();
794         gtk_widget_destroy(window);     
795 }
796
797 GtkWidget *gtkut_account_menu_new(GList                 *ac_list,
798                                         GCallback               callback,
799                                   gpointer              data)
800 {
801         GList *cur_ac;
802         GtkWidget *optmenu;
803         GtkListStore *menu;
804         GtkTreeIter iter;
805         PrefsAccount *account;
806         gchar *name;
807         
808         cm_return_val_if_fail(ac_list != NULL, NULL);
809
810         optmenu = gtkut_sc_combobox_create(NULL, FALSE);
811         menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
812
813         for (cur_ac = ac_list; cur_ac != NULL; cur_ac = cur_ac->next) {
814                 account = (PrefsAccount *) cur_ac->data;
815                 if (account->name)
816                         name = g_strdup_printf("%s: %s <%s>",
817                                                account->account_name,
818                                                account->name,
819                                                account->address);
820                 else
821                         name = g_strdup_printf("%s: %s",
822                                                account->account_name,
823                                                account->address);
824                 COMBOBOX_ADD_ESCAPED(menu, name, account->account_id);
825                 g_free(name);
826         }
827         gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
828
829         if( callback != NULL )
830                 g_signal_connect(G_OBJECT(optmenu), "changed", callback, data);
831
832         return optmenu;
833 }
834
835 /*!
836  *\brief        Tries to find a focused child using a lame strategy
837  */
838 GtkWidget *gtkut_get_focused_child(GtkContainer *parent)
839 {
840         GtkWidget *result = NULL;
841         GList *child_list = NULL;
842         GList *c;
843
844         cm_return_val_if_fail(parent, NULL);
845
846         /* Get children list and see which has the focus. */
847         child_list = gtk_container_get_children(parent);
848         if (!child_list)
849                 return NULL;
850
851         for (c = child_list; c != NULL; c = g_list_next(c)) {
852                 if (c->data && GTK_IS_WIDGET(c->data)) {
853                         if (gtk_widget_has_focus(GTK_WIDGET(c->data))) {
854                                 result = GTK_WIDGET(c->data);
855                                 break;
856                         }
857                 }
858         }
859         
860         /* See if the returned widget is a container itself; if it is,
861          * see if one of its children is focused. If the focused 
862          * container has no focused child, it is itself a focusable 
863          * child, and has focus. */
864         if (result && GTK_IS_CONTAINER(result)) {
865                 GtkWidget *tmp =  gtkut_get_focused_child(GTK_CONTAINER(result)); 
866                 
867                 if (tmp) 
868                         result = tmp;
869         } else {
870                 /* Try the same for each container in the chain */
871                 for (c = child_list; c != NULL && !result; c = g_list_next(c)) {
872                         if (c->data && GTK_IS_WIDGET(c->data) 
873                         &&  GTK_IS_CONTAINER(c->data)) {
874                                 result = gtkut_get_focused_child
875                                         (GTK_CONTAINER(c->data));
876                         }
877                 }
878         
879         }
880         
881         g_list_free(child_list);
882                 
883         return result;
884 }
885
886 /*!
887  *\brief        Create a Browse (file) button based on GTK stock
888  */
889 GtkWidget *gtkut_get_browse_file_btn(const gchar *button_label)
890 {
891         GtkWidget *button;
892
893         button = gtk_button_new_with_mnemonic(button_label);
894         gtk_button_set_image(GTK_BUTTON(button),
895                 gtk_image_new_from_icon_name("folder", GTK_ICON_SIZE_BUTTON));
896
897         return button;
898 }
899
900 /*!
901  *\brief        Create a Browse (directory) button based on GTK stock
902  */
903 GtkWidget *gtkut_get_browse_directory_btn(const gchar *button_label)
904 {
905         GtkWidget *button;
906
907         button = gtk_button_new_with_mnemonic(button_label);
908         gtk_button_set_image(GTK_BUTTON(button),
909                 gtk_image_new_from_icon_name("folder", GTK_ICON_SIZE_BUTTON));
910
911         return button;
912 }
913
914 GtkWidget *gtkut_get_replace_btn(const gchar *button_label)
915 {
916         GtkWidget *button;
917
918         button = gtk_button_new_with_mnemonic(button_label);
919         gtk_button_set_image(GTK_BUTTON(button),
920                 gtk_image_new_from_icon_name("view-refresh", GTK_ICON_SIZE_BUTTON));
921
922         return button;
923 }
924
925 GtkWidget *gtkut_stock_button(const gchar *stock_image, const gchar *label)
926 {
927         GtkWidget *button;
928         
929         cm_return_val_if_fail(stock_image != NULL, NULL);
930
931         button = gtk_button_new_from_icon_name(stock_image, GTK_ICON_SIZE_BUTTON);
932         if (label != NULL)
933                 gtk_button_set_label(GTK_BUTTON(button), _(label));
934         gtk_button_set_use_underline(GTK_BUTTON(button), TRUE);
935         gtk_button_set_always_show_image(GTK_BUTTON(button), TRUE);
936         
937         return button;
938 };
939
940 /**
941  * merge some part of code into one function : it creates a frame and add
942  *      these into gtk box widget passed in param.
943  * \param box gtk box where adding new created frame.
944  * \param pframe pointer with which to assign the frame. If NULL, no pointer
945  *      is assigned but the frame is anyway created and added to @box.
946  * \param frame_label frame label of new created frame.
947  */
948 GtkWidget *gtkut_get_options_frame(GtkWidget *box, GtkWidget **pframe,
949                 const gchar *frame_label)
950 {
951         GtkWidget *vbox;
952         GtkWidget *frame;
953
954         frame = gtk_frame_new(frame_label);
955         gtk_widget_show(frame);
956         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0);
957         gtk_frame_set_label_align(GTK_FRAME(frame), 0.01, 0.5);
958
959         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
960         gtk_widget_show(vbox);
961         gtk_container_add(GTK_CONTAINER (frame), vbox);
962         gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
963
964         if (pframe != NULL)
965                 *pframe = frame;
966
967         return vbox;
968 }
969
970 #if HAVE_LIBCOMPFACE
971 static gint create_xpm_from_xface(gchar *xpm[], const gchar *xface)
972 {
973         static gchar *bit_pattern[] = {
974                 "....",
975                 "...#",
976                 "..#.",
977                 "..##",
978                 ".#..",
979                 ".#.#",
980                 ".##.",
981                 ".###",
982                 "#...",
983                 "#..#",
984                 "#.#.",
985                 "#.##",
986                 "##..",
987                 "##.#",
988                 "###.",
989                 "####"
990         };
991
992         static gchar *xface_header = "48 48 2 1";
993         static gchar *xface_black  = "# c #000000";
994         static gchar *xface_white  = ". c #ffffff";
995
996         gint i, line = 0;
997         const guchar *p;
998         gchar buf[WIDTH * 4 + 1];  /* 4 = strlen("0x0000") */
999
1000         p = xface;
1001
1002         strcpy(xpm[line++], xface_header);
1003         strcpy(xpm[line++], xface_black);
1004         strcpy(xpm[line++], xface_white);
1005
1006         for (i = 0; i < HEIGHT; i++) {
1007                 gint col;
1008
1009                 buf[0] = '\0';
1010      
1011                 for (col = 0; col < 3; col++) {
1012                         gint figure;
1013
1014                         p += 2;  /* skip '0x' */
1015
1016                         for (figure = 0; figure < 4; figure++) {
1017                                 gint n = 0;
1018
1019                                 if ('0' <= *p && *p <= '9') {
1020                                         n = *p - '0';
1021                                 } else if ('a' <= *p && *p <= 'f') {
1022                                         n = *p - 'a' + 10;
1023                                 } else if ('A' <= *p && *p <= 'F') {
1024                                         n = *p - 'A' + 10;
1025                                 }
1026
1027                                 strcat(buf, bit_pattern[n]);
1028                                 p++;  /* skip ',' */
1029                         }
1030
1031                         p++;  /* skip '\n' */
1032                 }
1033
1034                 strcpy(xpm[line++], buf);
1035                 p++;
1036         }
1037
1038         return 0;
1039 }
1040 #endif
1041
1042 gboolean get_tag_range(GtkTextIter *iter,
1043                                        GtkTextTag *tag,
1044                                        GtkTextIter *start_iter,
1045                                        GtkTextIter *end_iter)
1046 {
1047         GtkTextIter _start_iter, _end_iter;
1048
1049         _end_iter = *iter;
1050         if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1051                 debug_print("Can't find end.\n");
1052                 return FALSE;
1053         }
1054
1055         _start_iter = _end_iter;
1056         if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1057                 debug_print("Can't find start.\n");
1058                 return FALSE;
1059         }
1060
1061         *start_iter = _start_iter;
1062         *end_iter = _end_iter;
1063
1064         return TRUE;
1065 }
1066
1067 #if HAVE_LIBCOMPFACE
1068 GtkWidget *xface_get_from_header(const gchar *o_xface)
1069 {
1070         static gchar *xpm_xface[XPM_XFACE_HEIGHT];
1071         static gboolean xpm_xface_init = TRUE;
1072         GdkPixbuf *pixbuf;
1073         GtkWidget *ret;
1074         gchar xface[2048];
1075         
1076         if (o_xface == NULL)
1077                 return NULL;
1078         
1079         strncpy(xface, o_xface, sizeof(xface) - 1);
1080         xface[sizeof(xface) - 1] = '\0';
1081
1082         if (uncompface(xface) < 0) {
1083                 g_warning("uncompface failed");
1084                 return NULL;
1085         }
1086
1087         if (xpm_xface_init) {
1088                 gint i;
1089
1090                 for (i = 0; i < XPM_XFACE_HEIGHT; i++) {
1091                         xpm_xface[i] = g_malloc(WIDTH + 1);
1092                         *xpm_xface[i] = '\0';
1093                 }
1094                 xpm_xface_init = FALSE;
1095         }
1096
1097         create_xpm_from_xface(xpm_xface, xface);
1098
1099         pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)xpm_xface);
1100         ret = gtk_image_new_from_pixbuf(pixbuf);
1101         g_object_unref(pixbuf);
1102
1103         return ret;
1104 }
1105 #endif
1106
1107 GtkWidget *face_get_from_header(const gchar *o_face)
1108 {
1109         gchar face[2048];
1110         gchar *face_png;
1111         gsize pngsize;
1112         GdkPixbuf *pixbuf;
1113         GError *error = NULL;
1114         GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
1115         GtkWidget *image;
1116         
1117         if (o_face == NULL || strlen(o_face) == 0)
1118                 return NULL;
1119
1120         strncpy2(face, o_face, sizeof(face));
1121
1122         unfold_line(face); /* strip all whitespace and linebreaks */
1123         remove_space(face);
1124
1125         face_png = g_base64_decode(face, &pngsize);
1126         debug_print("---------------------- loaded face png\n");
1127
1128         if (!gdk_pixbuf_loader_write (loader, face_png, pngsize, &error) ||
1129             !gdk_pixbuf_loader_close (loader, &error)) {
1130                 g_warning("loading face failed");
1131                 g_object_unref(loader);
1132                 g_free(face_png);
1133                 return NULL;
1134         }
1135         g_free(face_png);
1136
1137         pixbuf = g_object_ref(gdk_pixbuf_loader_get_pixbuf(loader));
1138
1139         g_object_unref(loader);
1140
1141         if ((gdk_pixbuf_get_width(pixbuf) != 48) || (gdk_pixbuf_get_height(pixbuf) != 48)) {
1142                 g_object_unref(pixbuf);
1143                 g_warning("wrong_size");
1144                 return NULL;
1145         }
1146
1147         image = gtk_image_new_from_pixbuf(pixbuf);
1148         g_object_unref(pixbuf);
1149         return image;
1150 }
1151
1152 static gboolean _combobox_separator_func(GtkTreeModel *model,
1153                 GtkTreeIter *iter, gpointer data)
1154 {
1155         gchar *txt = NULL;
1156
1157         cm_return_val_if_fail(model != NULL, FALSE);
1158
1159         gtk_tree_model_get(model, iter, COMBOBOX_TEXT, &txt, -1);
1160
1161         if( txt == NULL )
1162                 return TRUE;
1163         
1164         g_free(txt);
1165         return FALSE;
1166 }
1167
1168 GtkWidget *gtkut_sc_combobox_create(GtkWidget *eventbox, gboolean focus_on_click)
1169 {
1170         GtkWidget *combobox;
1171         GtkListStore *menu;
1172         GtkCellRenderer *rend;
1173
1174         menu = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
1175
1176         combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(menu));
1177
1178         rend = gtk_cell_renderer_text_new();
1179         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), rend, TRUE);
1180         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), rend,
1181                         "markup", COMBOBOX_TEXT,
1182                         "sensitive", COMBOBOX_SENS,
1183                         NULL);
1184
1185         if( eventbox != NULL )
1186                 gtk_container_add(GTK_CONTAINER(eventbox), combobox);
1187         gtk_widget_set_focus_on_click(GTK_WIDGET(combobox), focus_on_click);
1188
1189         gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(combobox),
1190                         (GtkTreeViewRowSeparatorFunc)_combobox_separator_func, NULL, NULL);
1191
1192         return combobox;
1193 }
1194
1195 static void gtkutils_smooth_scroll_do(GtkWidget *widget, GtkAdjustment *vadj,
1196                                       gfloat old_value, gfloat last_value,
1197                                       gint step)
1198 {
1199         gint change_value;
1200         gboolean up;
1201         gint i;
1202
1203         if (old_value < last_value) {
1204                 change_value = last_value - old_value;
1205                 up = FALSE;
1206         } else {
1207                 change_value = old_value - last_value;
1208                 up = TRUE;
1209         }
1210
1211         for (i = step; i <= change_value; i += step) {
1212                 gtk_adjustment_set_value(vadj, old_value + (up ? -i : i));
1213                 g_signal_emit_by_name(G_OBJECT(vadj),
1214                                       "value_changed", 0);
1215         }
1216
1217         gtk_adjustment_set_value(vadj, last_value);
1218         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1219
1220         gtk_widget_queue_draw(widget);
1221 }
1222
1223 static gboolean gtkutils_smooth_scroll_page(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1224 {
1225         gfloat upper;
1226         gfloat page_incr;
1227         gfloat old_value;
1228         gfloat last_value;
1229
1230         page_incr = gtk_adjustment_get_page_increment(vadj);
1231         if (prefs_common.scroll_halfpage)
1232                 page_incr /= 2;
1233
1234         old_value = gtk_adjustment_get_value(vadj);
1235         if (!up) {
1236                 upper = gtk_adjustment_get_upper(vadj) - gtk_adjustment_get_page_size(vadj);
1237                 if (old_value < upper) {
1238                         last_value = old_value + page_incr;
1239                         last_value = MIN(last_value, upper);
1240
1241                         gtkutils_smooth_scroll_do(widget, vadj, old_value,
1242                                                   last_value,
1243                                                   prefs_common.scroll_step);
1244                 } else
1245                         return FALSE;
1246         } else {
1247                 if (old_value > 0.0) {
1248                         last_value = old_value - page_incr;
1249                         last_value = MAX(last_value, 0.0);
1250
1251                         gtkutils_smooth_scroll_do(widget, vadj, old_value,
1252                                                   last_value,
1253                                                   prefs_common.scroll_step);
1254                 } else
1255                         return FALSE;
1256         }
1257
1258         return TRUE;
1259 }
1260
1261 gboolean gtkutils_scroll_page(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1262 {
1263         gfloat upper;
1264         gfloat page_incr;
1265         gfloat old_value;
1266
1267         if (prefs_common.enable_smooth_scroll)
1268                 return gtkutils_smooth_scroll_page(widget, vadj, up);
1269
1270         page_incr = gtk_adjustment_get_page_increment(vadj);
1271         if (prefs_common.scroll_halfpage)
1272                 page_incr /= 2;
1273
1274         old_value = gtk_adjustment_get_value(vadj);
1275         if (!up) {
1276                 upper = gtk_adjustment_get_upper(vadj) - gtk_adjustment_get_page_size(vadj);
1277                 if (old_value < upper) {
1278                         old_value += page_incr;
1279                         old_value = MIN(old_value, upper);
1280                         gtk_adjustment_set_value(vadj, old_value);
1281                         g_signal_emit_by_name(G_OBJECT(vadj),
1282                                               "value_changed", 0);
1283                 } else
1284                         return FALSE;
1285         } else {
1286                 if (old_value > 0.0) {
1287                         old_value -= page_incr;
1288                         old_value = MAX(old_value, 0.0);
1289                         gtk_adjustment_set_value(vadj, old_value);
1290                         g_signal_emit_by_name(G_OBJECT(vadj),
1291                                               "value_changed", 0);
1292                 } else
1293                         return FALSE;
1294         }
1295         return TRUE;
1296 }
1297
1298 static void gtkutils_smooth_scroll_one_line(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1299 {
1300         gfloat upper;
1301         gfloat old_value;
1302         gfloat last_value;
1303
1304         old_value = gtk_adjustment_get_value(vadj);
1305         if (!up) {
1306                 upper = gtk_adjustment_get_upper(vadj) - gtk_adjustment_get_page_size(vadj);
1307                 if (old_value < upper) {
1308                         last_value = old_value + gtk_adjustment_get_step_increment(vadj);
1309                         last_value = MIN(last_value, upper);
1310
1311                         gtkutils_smooth_scroll_do(widget, vadj, old_value,
1312                                                   last_value,
1313                                                   prefs_common.scroll_step);
1314                 }
1315         } else {
1316                 if (old_value > 0.0) {
1317                         last_value = old_value - gtk_adjustment_get_step_increment(vadj);
1318                         last_value = MAX(last_value, 0.0);
1319
1320                         gtkutils_smooth_scroll_do(widget, vadj, old_value,
1321                                                   last_value,
1322                                                   prefs_common.scroll_step);
1323                 }
1324         }
1325 }
1326
1327 void gtkutils_scroll_one_line(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1328 {
1329         gfloat upper;
1330         gfloat old_value;
1331
1332         if (prefs_common.enable_smooth_scroll) {
1333                 gtkutils_smooth_scroll_one_line(widget, vadj, up);
1334                 return;
1335         }
1336
1337         old_value = gtk_adjustment_get_value(vadj);
1338         if (!up) {
1339                 upper = gtk_adjustment_get_upper(vadj) - gtk_adjustment_get_page_size(vadj);
1340                 if (old_value < upper) {
1341                         old_value += gtk_adjustment_get_step_increment(vadj);
1342                         old_value = MIN(old_value, upper);
1343                         gtk_adjustment_set_value(vadj, old_value);
1344                         g_signal_emit_by_name(G_OBJECT(vadj),
1345                                               "value_changed", 0);
1346                 }
1347         } else {
1348                 if (old_value > 0.0) {
1349                         old_value -= gtk_adjustment_get_step_increment(vadj);
1350                         old_value = MAX(old_value, 0.0);
1351                         gtk_adjustment_set_value(vadj, old_value);
1352                         g_signal_emit_by_name(G_OBJECT(vadj),
1353                                               "value_changed", 0);
1354                 }
1355         }
1356 }
1357
1358 gboolean gtkut_tree_model_text_iter_prev(GtkTreeModel *model,
1359                                  GtkTreeIter *iter,
1360                                  const gchar* text)
1361 /* do the same as gtk_tree_model_iter_next, but _prev instead.
1362    to use with widgets with one text column (gtk_combo_box_text_new()
1363    and with GtkComboBoxEntry's for instance),
1364 */
1365 {
1366         GtkTreeIter cur_iter;
1367         gchar *cur_value;
1368         gboolean valid;
1369         gint count;
1370
1371         cm_return_val_if_fail(model != NULL, FALSE);
1372         cm_return_val_if_fail(iter != NULL, FALSE);
1373
1374         if (text == NULL || *text == '\0')
1375                 return FALSE;
1376
1377         valid = gtk_tree_model_get_iter_first(model, &cur_iter);
1378         count = 0;
1379         while (valid) {
1380                 gtk_tree_model_get(model, &cur_iter, 0, &cur_value, -1);
1381
1382                 if (strcmp(text, cur_value) == 0) {
1383                         g_free(cur_value);
1384                         if (count <= 0)
1385                                 return FALSE;
1386
1387                         return gtk_tree_model_iter_nth_child(model, iter, NULL, count - 1);
1388                 }
1389
1390                 g_free(cur_value);
1391                 valid = gtk_tree_model_iter_next(model, &cur_iter);
1392                 count++;
1393         }
1394         return FALSE;           
1395 }
1396
1397 gboolean gtkut_tree_model_get_iter_last(GtkTreeModel *model,
1398                                  GtkTreeIter *iter)
1399 /* do the same as gtk_tree_model_get_iter_first, but _last instead.
1400 */
1401 {
1402         gint count;
1403
1404         cm_return_val_if_fail(model != NULL, FALSE);
1405         cm_return_val_if_fail(iter != NULL, FALSE);
1406
1407         count = gtk_tree_model_iter_n_children(model, NULL);
1408
1409         if (count <= 0)
1410                 return FALSE;
1411
1412         return gtk_tree_model_iter_nth_child(model, iter, NULL, count - 1);
1413 }
1414
1415 GtkWidget *gtkut_window_new             (GtkWindowType   type,
1416                                          const gchar    *class)
1417 {
1418         GtkWidget *window = gtk_window_new(type);
1419         gtk_window_set_role(GTK_WINDOW(window), class);
1420         gtk_widget_set_name(GTK_WIDGET(window), class);
1421         return window;
1422 }
1423
1424 static gboolean gtkut_tree_iter_comp(GtkTreeModel *model, 
1425                                      GtkTreeIter *iter1, 
1426                                      GtkTreeIter *iter2)
1427 {
1428         GtkTreePath *path1 = gtk_tree_model_get_path(model, iter1);
1429         GtkTreePath *path2 = gtk_tree_model_get_path(model, iter2);
1430         gboolean result;
1431
1432         result = gtk_tree_path_compare(path1, path2) == 0;
1433
1434         gtk_tree_path_free(path1);
1435         gtk_tree_path_free(path2);
1436         
1437         return result;
1438 }
1439
1440 /*!
1441  *\brief        Get selected row number.
1442  */
1443 gint gtkut_list_view_get_selected_row(GtkWidget *list_view)
1444 {
1445         GtkTreeView *view = GTK_TREE_VIEW(list_view);
1446         GtkTreeModel *model = gtk_tree_view_get_model(view);
1447         int n_rows = gtk_tree_model_iter_n_children(model, NULL);
1448         GtkTreeSelection *selection;
1449         GtkTreeIter iter;
1450         int row;
1451
1452         if (n_rows == 0) 
1453                 return -1;
1454         
1455         selection = gtk_tree_view_get_selection(view);
1456         if (!gtk_tree_selection_get_selected(selection, &model, &iter))
1457                 return -1;
1458         
1459         /* get all iterators and compare them... */
1460         for (row = 0; row < n_rows; row++) {
1461                 GtkTreeIter itern;
1462
1463                 if (gtk_tree_model_iter_nth_child(model, &itern, NULL, row)
1464                  && gtkut_tree_iter_comp(model, &iter, &itern))
1465                         return row;
1466         }
1467         
1468         return -1;
1469 }
1470
1471 /*!
1472  *\brief        Select a row by its number.
1473  */
1474 gboolean gtkut_list_view_select_row(GtkWidget *list, gint row)
1475 {
1476         GtkTreeView *list_view = GTK_TREE_VIEW(list);
1477         GtkTreeSelection *selection = gtk_tree_view_get_selection(list_view);
1478         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1479         GtkTreeIter iter;
1480         GtkTreePath *path;
1481
1482         if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1483                 return FALSE;
1484         
1485         gtk_tree_selection_select_iter(selection, &iter);
1486
1487         path = gtk_tree_model_get_path(model, &iter);
1488         gtk_tree_view_set_cursor(list_view, path, NULL, FALSE);
1489         gtk_tree_path_free(path);
1490         
1491         return TRUE;
1492 }
1493
1494 static GtkUIManager *gui_manager = NULL;
1495
1496 GtkUIManager *gtkut_create_ui_manager(void)
1497 {
1498         cm_return_val_if_fail(gui_manager == NULL, gui_manager);
1499         return (gui_manager = gtk_ui_manager_new());
1500 }
1501
1502 GtkUIManager *gtkut_ui_manager(void)
1503 {
1504         return gui_manager;
1505 }
1506
1507 typedef struct _ClawsIOClosure ClawsIOClosure;
1508
1509 struct _ClawsIOClosure
1510 {
1511   ClawsIOFunc function;
1512   GIOCondition condition;
1513   GDestroyNotify notify;
1514   gpointer data;
1515 };
1516
1517 static gboolean  
1518 claws_io_invoke (GIOChannel   *source,
1519                  GIOCondition  condition,
1520                  gpointer      data)
1521 {
1522   ClawsIOClosure *closure = data;
1523   int fd;
1524 #ifndef G_OS_WIN32
1525   fd = g_io_channel_unix_get_fd (source);
1526 #else
1527   fd = g_io_channel_win32_get_fd (source);
1528 #endif
1529   if (closure->condition & condition)
1530     closure->function (closure->data, fd, condition);
1531
1532   return TRUE;
1533 }
1534
1535 static void
1536 claws_io_destroy (gpointer data)
1537 {
1538   ClawsIOClosure *closure = data;
1539
1540   if (closure->notify)
1541     closure->notify (closure->data);
1542
1543   g_free (closure);
1544 }
1545
1546 gint
1547 claws_input_add    (gint              source,
1548                     GIOCondition      condition,
1549                     ClawsIOFunc       function,
1550                     gpointer          data,
1551                     gboolean          is_sock)
1552 {
1553   guint result;
1554   ClawsIOClosure *closure = g_new (ClawsIOClosure, 1);
1555   GIOChannel *channel;
1556
1557   closure->function = function;
1558   closure->condition = condition;
1559   closure->notify = NULL;
1560   closure->data = data;
1561
1562 #ifndef G_OS_WIN32
1563   channel = g_io_channel_unix_new (source);
1564 #else
1565   if (is_sock)
1566     channel = g_io_channel_win32_new_socket(source);
1567   else
1568     channel = g_io_channel_win32_new_fd(source);
1569 #endif
1570   result = g_io_add_watch_full (channel, G_PRIORITY_DEFAULT, condition, 
1571                                 claws_io_invoke,
1572                                 closure, claws_io_destroy);
1573   g_io_channel_unref (channel);
1574
1575   return result;
1576 }
1577
1578 /**
1579  * Load a pixbuf fitting inside the specified size. EXIF orientation is
1580  * respected if available.
1581  *
1582  * @param[in] filename          the file to load
1583  * @param[in] box_width         the max width (-1 for no resize)
1584  * @param[in] box_height        the max height (-1 for no resize)
1585  * @param[out] error            the possible load error
1586  *
1587  * @return a GdkPixbuf
1588  */
1589 GdkPixbuf *claws_load_pixbuf_fitting(GdkPixbuf *src_pixbuf, gboolean inline_img,
1590                                      gboolean fit_img_height, int box_width, int box_height)
1591 {
1592         gint w, h, orientation, angle;
1593         gint avail_width, avail_height;
1594         gboolean flip_horiz, flip_vert;
1595         const gchar *orient_str;
1596         GdkPixbuf *pixbuf, *t_pixbuf;
1597
1598         pixbuf = src_pixbuf;
1599
1600         if (pixbuf == NULL)
1601                 return NULL;
1602
1603         angle = 0;
1604         flip_horiz = flip_vert = FALSE;
1605
1606         /* EXIF orientation */
1607         orient_str = gdk_pixbuf_get_option(pixbuf, "orientation");
1608         if (orient_str != NULL && *orient_str != '\0') {
1609                 orientation = atoi(orient_str);
1610                 switch(orientation) {
1611                         /* See EXIF standard for different values */
1612                         case 1: break;
1613                         case 2: flip_horiz = 1;
1614                                 break;
1615                         case 3: angle = 180;
1616                                 break;
1617                         case 4: flip_vert = 1;
1618                                 break;
1619                         case 5: angle = 90;
1620                                 flip_horiz = 1;
1621                                 break;
1622                         case 6: angle = 270;
1623                                 break;
1624                         case 7: angle = 90;
1625                                 flip_vert = 1;
1626                                 break;
1627                         case 8: angle = 90;
1628                                 break;
1629                 }
1630         }
1631
1632
1633         /* Rotate if needed */
1634         if (angle != 0) {
1635                 t_pixbuf = gdk_pixbuf_rotate_simple(pixbuf, angle);
1636                 g_object_unref(pixbuf);
1637                 pixbuf = t_pixbuf;
1638         }
1639
1640         /* Flip horizontally if needed */
1641         if (flip_horiz) {
1642                 t_pixbuf = gdk_pixbuf_flip(pixbuf, TRUE);
1643                 g_object_unref(pixbuf);
1644                 pixbuf = t_pixbuf;
1645         }
1646
1647         /* Flip vertically if needed */
1648         if (flip_vert) {
1649                 t_pixbuf = gdk_pixbuf_flip(pixbuf, FALSE);
1650                 g_object_unref(pixbuf);
1651                 pixbuf = t_pixbuf;
1652         }
1653
1654         w = gdk_pixbuf_get_width(pixbuf);
1655         h = gdk_pixbuf_get_height(pixbuf);
1656
1657         avail_width = box_width-32;
1658         avail_height = box_height;
1659                 
1660         if (box_width != -1 && box_height != -1 && avail_width - 100 > 0) {
1661                 if (inline_img || fit_img_height) {
1662                         if (w > avail_width) {
1663                                 h = (avail_width * h) / w;
1664                                 w = avail_width;
1665                         }
1666                         if (h > avail_height) {
1667                                 w = (avail_height * w) / h;
1668                                 h = avail_height;
1669                         }
1670                 } else {
1671                         if (w > avail_width || h > avail_height) {
1672                                 h = (avail_width * h) / w;
1673                                 w = avail_width;
1674                         }
1675                 }
1676                 t_pixbuf = gdk_pixbuf_scale_simple(pixbuf, 
1677                         w, h, GDK_INTERP_BILINEAR);
1678                 g_object_unref(pixbuf);
1679                 pixbuf = t_pixbuf;
1680         }
1681
1682         return pixbuf;
1683 }
1684
1685 #if defined USE_GNUTLS
1686 static void auto_configure_done(const gchar *hostname, gint port, gboolean ssl, AutoConfigureData *data)
1687 {
1688         gboolean smtp = strcmp(data->tls_service, "submission") == 0 ? TRUE : FALSE;
1689
1690         if (hostname != NULL) {
1691                 if (data->hostname_entry)
1692                         gtk_entry_set_text(data->hostname_entry, hostname);
1693                 if (data->set_port)
1694                         gtk_toggle_button_set_active(data->set_port,
1695                                 (ssl && port != data->default_ssl_port) || (!ssl && port != data->default_port));
1696                 if (data->port)
1697                         gtk_spin_button_set_value(data->port, port);
1698                 else if (data->hostname_entry) {
1699                         if ((ssl && port != data->default_ssl_port) || (!ssl && port != data->default_port)) {
1700                                 gchar *tmp = g_strdup_printf("%s:%d", hostname, port);
1701                                 gtk_entry_set_text(data->hostname_entry, tmp);
1702                                 g_free(tmp);
1703                         } else
1704                                 gtk_entry_set_text(data->hostname_entry, hostname);
1705                 }
1706
1707                 if (ssl && data->ssl_checkbtn) {
1708                         gtk_toggle_button_set_active(data->ssl_checkbtn, TRUE);
1709                         gtk_toggle_button_set_active(data->tls_checkbtn, FALSE);
1710                 } else if (data->tls_checkbtn) {
1711                         if (!GTK_IS_RADIO_BUTTON(data->ssl_checkbtn)) {
1712                                 /* Wizard where TLS is [x]SSL + [x]TLS */
1713                                 gtk_toggle_button_set_active(data->ssl_checkbtn, TRUE);
1714                         }
1715
1716                         /* Even though technically this is against the RFCs,
1717                          * if a "_submission._tcp" SRV record uses port 465,
1718                          * it is safe to assume TLS-only service, instead of
1719                          * plaintext + STARTTLS one. */
1720                         if (smtp && port == 465)
1721                                 gtk_toggle_button_set_active(data->ssl_checkbtn, TRUE);
1722                         else
1723                                 gtk_toggle_button_set_active(data->tls_checkbtn, TRUE);
1724                 }
1725
1726                 /* Check authentication by default. This is probably required if
1727                  * auto-configuration worked.
1728                  */
1729                 if (data->auth_checkbtn)
1730                         gtk_toggle_button_set_active(data->auth_checkbtn, TRUE);
1731
1732                 /* Set user ID to full email address, which is used by the
1733                  * majority of providers where auto-configuration works.
1734                  */
1735                 if (data->uid_entry)
1736                         gtk_entry_set_text(data->uid_entry, data->address);
1737
1738                 gtk_label_set_text(data->info_label, _("Done."));
1739         } else {
1740                 gchar *msg;
1741                 switch (data->resolver_error) {
1742                 case G_RESOLVER_ERROR_NOT_FOUND:
1743                         msg = g_strdup(_("Failed: no service record found."));
1744                         break;
1745                 case G_RESOLVER_ERROR_TEMPORARY_FAILURE:
1746                         msg = g_strdup(_("Failed: network error."));
1747                         break;
1748                 default:
1749                         msg = g_strdup_printf(_("Failed: unknown error (%d)."), data->resolver_error);
1750                 }
1751                 gtk_label_set_text(data->info_label, msg);
1752                 g_free(msg);
1753         }
1754         gtk_widget_show(GTK_WIDGET(data->configure_button));
1755         gtk_widget_hide(GTK_WIDGET(data->cancel_button));
1756         g_free(data->address);
1757         g_free(data);
1758 }
1759
1760 static void resolve_done(GObject *source, GAsyncResult *result, gpointer user_data)
1761 {
1762         AutoConfigureData *data = (AutoConfigureData *)user_data;
1763         GResolver *resolver = (GResolver *)source;
1764         GError *error = NULL;
1765         gchar *hostname = NULL;
1766         guint16 port;
1767         GList *answers, *cur;
1768         gboolean found = FALSE;
1769         gboolean abort = FALSE;
1770
1771         answers = g_resolver_lookup_service_finish(resolver, result, &error);
1772
1773         if (answers) {
1774                 for (cur = g_srv_target_list_sort(answers); cur; cur = cur->next) {
1775                         GSrvTarget *target = (GSrvTarget *)cur->data;
1776                         const gchar *h = g_srv_target_get_hostname(target);
1777                         port = g_srv_target_get_port(target);
1778                         if (h && strcmp(h,"") && port > 0) {
1779                                 hostname = g_strdup(h);
1780                                 found = TRUE;
1781                                 break;
1782                         }
1783                 }
1784                 g_resolver_free_targets(answers);
1785         } else if (error) {
1786                 if (error->code == G_IO_ERROR_CANCELLED)
1787                         abort = TRUE;
1788                 else
1789                         data->resolver_error = error->code;
1790                 debug_print("error %s\n", error->message);
1791                 g_error_free(error);
1792         }
1793
1794         if (found) {
1795                 auto_configure_done(hostname, port, data->ssl_service != NULL, data);
1796         } else if (data->ssl_service && !abort) {
1797                 /* Fallback to TLS */
1798                 data->ssl_service = NULL;
1799                 auto_configure_service(data);
1800         } else {
1801                 auto_configure_done(NULL, 0, FALSE, data);
1802         }
1803         g_free(hostname);
1804         g_object_unref(resolver);
1805 }
1806
1807 void auto_configure_service(AutoConfigureData *data)
1808 {
1809         GResolver *resolver;
1810         const gchar *cur_service = data->ssl_service != NULL ? data->ssl_service : data->tls_service;
1811
1812         cm_return_if_fail(cur_service != NULL);
1813         cm_return_if_fail(data->address != NULL);
1814
1815         resolver = g_resolver_get_default();
1816         if (resolver != NULL) {
1817                 const gchar *domain = strchr(data->address, '@') + 1;
1818
1819                 gtk_label_set_text(data->info_label, _("Configuring..."));
1820                 gtk_widget_hide(GTK_WIDGET(data->configure_button));
1821                 gtk_widget_show(GTK_WIDGET(data->cancel_button));
1822                 g_resolver_lookup_service_async(resolver, cur_service, "tcp", domain,
1823                                         data->cancel, resolve_done, data);
1824         }
1825 }
1826
1827 gboolean auto_configure_service_sync(const gchar *service, const gchar *domain, gchar **srvhost, guint16 *srvport)
1828 {
1829         GResolver *resolver;
1830         GList *answers, *cur;
1831         GError *error = NULL;
1832         gboolean result = FALSE;
1833
1834         cm_return_val_if_fail(service != NULL, FALSE);
1835         cm_return_val_if_fail(domain != NULL, FALSE);
1836
1837         resolver = g_resolver_get_default();
1838         if (resolver == NULL)
1839                 return FALSE;
1840
1841         answers = g_resolver_lookup_service(resolver, service, "tcp", domain, NULL, &error);
1842
1843         *srvhost = NULL;
1844         *srvport = 0;
1845
1846         if (answers) {
1847                 for (cur = g_srv_target_list_sort(answers); cur; cur = cur->next) {
1848                         GSrvTarget *target = (GSrvTarget *)cur->data;
1849                         const gchar *hostname = g_srv_target_get_hostname(target);
1850                         guint16 port = g_srv_target_get_port(target);
1851                         if (hostname && strcmp(hostname,"") && port > 0) {
1852                                 result = TRUE;
1853                                 *srvhost = g_strdup(hostname);
1854                                 *srvport = port;
1855                                 break;
1856                         }
1857                 }
1858                 g_resolver_free_targets(answers);
1859         } else if (error) {
1860                 g_error_free(error);
1861         }
1862
1863         g_object_unref(resolver);
1864         return result;
1865 }
1866 #endif
1867
1868 gboolean gtkut_pointer_is_grabbed(GtkWidget *widget)
1869 {
1870         GdkDisplay *display;
1871         GdkDevice *pointerdev;
1872
1873         cm_return_val_if_fail(widget != NULL, FALSE);
1874
1875         display = gtk_widget_get_display(widget);
1876         pointerdev = gdk_seat_get_pointer(gdk_display_get_default_seat(display));
1877
1878         return gdk_display_device_is_grabbed(display, pointerdev);
1879 }
1880
1881 gpointer gtkut_tree_view_get_selected_pointer(GtkTreeView *view,
1882                 gint column, GtkTreeModel **_model, GtkTreeSelection **_selection,
1883                 GtkTreeIter *_iter)
1884 {
1885         GtkTreeIter iter;
1886         GtkTreeModel *model;
1887         GtkTreeSelection *sel;
1888         gpointer ptr;
1889         GType type;
1890
1891         cm_return_val_if_fail(view != NULL, NULL);
1892         cm_return_val_if_fail(column >= 0, NULL);
1893
1894         model = gtk_tree_view_get_model(view);
1895         if (_model != NULL)
1896                 *_model = model;
1897
1898         sel = gtk_tree_view_get_selection(view);
1899         if (_selection != NULL)
1900                 *_selection = sel;
1901
1902         if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
1903                 return NULL; /* No row selected */
1904
1905         if (_iter != NULL)
1906                 *_iter = iter;
1907
1908         if (gtk_tree_selection_count_selected_rows(sel) > 1)
1909                 return NULL; /* Can't work with multiselect */
1910
1911         cm_return_val_if_fail(
1912                         gtk_tree_model_get_n_columns(model) > column,
1913                         NULL);
1914
1915         type = gtk_tree_model_get_column_type(model, column);
1916         cm_return_val_if_fail(
1917                         type == G_TYPE_POINTER || type == G_TYPE_STRING,
1918                         NULL);
1919
1920         gtk_tree_model_get(model, &iter, column, &ptr, -1);
1921
1922         return ptr;
1923 }
1924
1925 static GList *get_predefined_times(void)
1926 {
1927         int h,m;
1928         GList *times = NULL;
1929         for (h = 0; h < 24; h++) {
1930                 for (m = 0; m < 60; m += 15) {
1931                         gchar *tmp = g_strdup_printf("%02d:%02d", h, m);
1932                         times = g_list_append(times, tmp);
1933                 }
1934         }
1935         return times;
1936 }
1937
1938 static int get_list_item_num(int h, int m)
1939 {
1940         if (m % 15 != 0)
1941                 return -1;
1942
1943         return (h*4 + m/15);
1944 }
1945
1946 GtkWidget *gtkut_time_select_combo_new()
1947 {
1948         GtkWidget *combo = gtk_combo_box_text_new_with_entry();
1949         GList *times = get_predefined_times();
1950
1951         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), -1);
1952         combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(combo), times);
1953
1954         list_free_strings_full(times);
1955
1956         return combo;
1957 }
1958
1959
1960 void gtkut_time_select_select_by_time(GtkComboBox *combo, int hour, int minute)
1961 {
1962         gchar *time_text = g_strdup_printf("%02d:%02d", hour, minute);
1963         gint num = get_list_item_num(hour, minute);
1964
1965         if (num > -1)
1966                 combobox_select_by_text(combo, time_text);
1967         else
1968                 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), time_text);
1969
1970         g_free(time_text);
1971 }
1972
1973 static void get_time_from_combo(GtkComboBox *combo, int *h, int *m)
1974 {
1975         gchar *tmp;
1976         gchar **parts;
1977
1978         if (!h || !m) 
1979                 return;
1980
1981         tmp = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(combo))), 0, -1);
1982         parts = g_strsplit(tmp, ":", 2);
1983         if (parts[0] && parts[1] && *parts[0] && *parts[1]) {
1984                 *h = atoi(parts[0]);
1985                 *m = atoi(parts[1]);
1986         }
1987         g_strfreev(parts);
1988         g_free(tmp);
1989 }
1990
1991 gboolean gtkut_time_select_get_time(GtkComboBox *combo, int *hour, int *minute)
1992 {
1993         const gchar *value = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))));
1994
1995         if (value == NULL || strlen(value) != 5)
1996                 return FALSE;
1997
1998         if (hour == NULL || minute == NULL)
1999                 return FALSE;
2000
2001         get_time_from_combo(combo, hour, minute);
2002
2003         if (*hour < 0 || *hour > 23)
2004                 return FALSE;
2005         if (*minute < 0 || *minute > 59)
2006                 return FALSE;
2007
2008         return TRUE;
2009 }
2010
2011 void gtk_calendar_select_today(GtkCalendar *calendar)
2012 {
2013         time_t t = time (NULL);
2014         struct tm buft;
2015         struct tm *lt = localtime_r (&t, &buft);
2016
2017         mktime(lt);
2018         gtk_calendar_select_day(calendar, lt->tm_mday);
2019         gtk_calendar_select_month(calendar, lt->tm_mon, lt->tm_year + 1900);
2020 }
2021
2022
2023 #define RGBA_ELEMENT_TO_BYTE(x) (int)((gdouble)x * 255)
2024 gchar *gtkut_gdk_rgba_to_string(GdkRGBA *rgba)
2025 {
2026         gchar *str = g_strdup_printf("#%02x%02x%02x",
2027                         RGBA_ELEMENT_TO_BYTE(rgba->red),
2028                         RGBA_ELEMENT_TO_BYTE(rgba->green),
2029                         RGBA_ELEMENT_TO_BYTE(rgba->blue));
2030
2031         return str;
2032 }
2033 #undef RGBA_ELEMENT_TO_BYTE