Improve some sieve-related translations
[claws.git] / src / gtk / gtkutils.c
blobcf97cfd31482acf2b76bf2a7956acde9a229cd2b
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2022 the Claws Mail team and Hiroyuki Yamamoto
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.
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.
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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
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>
34 #include "combobox.h"
36 #if HAVE_LIBCOMPFACE
37 # include <compface.h>
38 #endif
40 #if HAVE_LIBCOMPFACE
41 #define XPM_XFACE_HEIGHT (HEIGHT + 3) /* 3 = 1 header + 2 colors */
42 #endif
44 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
45 # include <wchar.h>
46 # include <wctype.h>
47 #endif
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"
61 gboolean gtkut_get_font_size(GtkWidget *widget,
62 gint *width, gint *height)
64 PangoLayout *layout;
65 const gchar *str = "Abcdef";
67 cm_return_val_if_fail(GTK_IS_WIDGET(widget), FALSE);
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);
76 return TRUE;
79 void gtkut_widget_set_small_font_size(GtkWidget *widget)
81 PangoFontDescription *font_desc;
82 gint size;
84 cm_return_if_fail(widget != NULL);
85 cm_return_if_fail(gtk_widget_get_style(widget) != NULL);
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);
100 void gtkut_stock_button_add_help(GtkWidget *bbox, GtkWidget **help_btn)
102 cm_return_if_fail(bbox != NULL);
104 *help_btn = gtkut_stock_button("help-browser", "Help");
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);
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)
121 cm_return_if_fail(bbox != NULL);
122 cm_return_if_fail(button1 != NULL);
124 gtkut_stock_button_set_create(bbox, button1, stock_icon1, label1,
125 button2, stock_icon2, label2, button3, stock_icon3, label3);
127 gtkut_stock_button_add_help(*bbox, help_button);
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)
135 cm_return_if_fail(bbox != NULL);
136 cm_return_if_fail(button1 != NULL);
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);
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);
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);
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);
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)
173 cm_return_if_fail(bbox != NULL);
174 cm_return_if_fail(button1 != NULL);
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);
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);
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);
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);
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))
213 void gtkut_ctree_node_move_if_on_the_edge(GtkCMCTree *ctree, GtkCMCTreeNode *node, gint _row)
215 GtkCMCList *clist = GTK_CMCLIST(ctree);
216 gint row;
217 GtkVisibility row_visibility, prev_row_visibility, next_row_visibility;
218 gfloat row_align;
220 cm_return_if_fail(ctree != NULL);
221 cm_return_if_fail(node != NULL);
223 row = (_row != -1 ? _row : g_list_position(clist->row_list, (GList *)node));
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);
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;
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;
247 if (prev_row_visibility != GTK_VISIBILITY_FULL) {
248 gtk_cmclist_moveto(clist, row, -1, 0.2, 0);
249 return;
251 if (next_row_visibility != GTK_VISIBILITY_FULL) {
252 gtk_cmclist_moveto(clist, row, -1, 0.8, 0);
253 return;
257 #undef CELL_SPACING
258 #undef ROW_TOP_YPIXEL
259 #undef ROW_FROM_YPIXEL
261 gint gtkut_ctree_get_nth_from_node(GtkCMCTree *ctree, GtkCMCTreeNode *node)
263 cm_return_val_if_fail(ctree != NULL, -1);
264 cm_return_val_if_fail(node != NULL, -1);
266 return g_list_position(GTK_CMCLIST(ctree)->row_list, (GList *)node);
269 /* get the next node, including the invisible one */
270 GtkCMCTreeNode *gtkut_ctree_node_next(GtkCMCTree *ctree, GtkCMCTreeNode *node)
272 GtkCMCTreeNode *parent;
274 if (!node) return NULL;
276 if (GTK_CMCTREE_ROW(node)->children)
277 return GTK_CMCTREE_ROW(node)->children;
279 if (GTK_CMCTREE_ROW(node)->sibling)
280 return GTK_CMCTREE_ROW(node)->sibling;
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;
288 return NULL;
291 /* get the previous node, including the invisible one */
292 GtkCMCTreeNode *gtkut_ctree_node_prev(GtkCMCTree *ctree, GtkCMCTreeNode *node)
294 GtkCMCTreeNode *prev;
295 GtkCMCTreeNode *child;
297 if (!node) return NULL;
299 prev = GTK_CMCTREE_NODE_PREV(node);
300 if (prev == GTK_CMCTREE_ROW(node)->parent)
301 return prev;
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;
310 return child;
313 gboolean gtkut_ctree_node_is_selected(GtkCMCTree *ctree, GtkCMCTreeNode *node)
315 GtkCMCList *clist = GTK_CMCLIST(ctree);
316 GList *cur;
318 for (cur = clist->selection; cur != NULL; cur = cur->next) {
319 if (node == GTK_CMCTREE_NODE(cur->data))
320 return TRUE;
323 return FALSE;
326 GtkCMCTreeNode *gtkut_ctree_find_collapsed_parent(GtkCMCTree *ctree,
327 GtkCMCTreeNode *node)
329 if (!node) return NULL;
331 while ((node = GTK_CMCTREE_ROW(node)->parent) != NULL) {
332 if (!GTK_CMCTREE_ROW(node)->expanded)
333 return node;
336 return NULL;
339 void gtkut_ctree_expand_parent_all(GtkCMCTree *ctree, GtkCMCTreeNode *node)
341 gtk_cmclist_freeze(GTK_CMCLIST(ctree));
343 while ((node = gtkut_ctree_find_collapsed_parent(ctree, node)) != NULL)
344 gtk_cmctree_expand(ctree, node);
346 gtk_cmclist_thaw(GTK_CMCLIST(ctree));
349 gboolean gtkut_ctree_node_is_parent(GtkCMCTreeNode *parent, GtkCMCTreeNode *node)
351 GtkCMCTreeNode *tmp;
352 cm_return_val_if_fail(node != NULL, FALSE);
353 cm_return_val_if_fail(parent != NULL, FALSE);
354 tmp = node;
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;
362 return FALSE;
365 void gtkut_ctree_set_focus_row(GtkCMCTree *ctree, GtkCMCTreeNode *node)
367 if (node == NULL)
368 return;
369 gtkut_clist_set_focus_row(GTK_CMCLIST(ctree),
370 gtkut_ctree_get_nth_from_node(ctree, node));
373 void gtkut_clist_set_focus_row(GtkCMCList *clist, gint row)
375 clist->focus_row = row;
376 GTKUT_CTREE_REFRESH(clist);
379 static gboolean gtkut_text_buffer_match_string(GtkTextBuffer *textbuf,
380 const GtkTextIter *iter,
381 gunichar *wcs, gint len,
382 gboolean case_sens)
384 GtkTextIter start_iter, end_iter;
385 gchar *utf8str, *p;
386 gint match_count;
388 start_iter = end_iter = *iter;
389 gtk_text_iter_forward_chars(&end_iter, len);
391 utf8str = gtk_text_buffer_get_text(textbuf, &start_iter, &end_iter,
392 FALSE);
393 if (!utf8str) return FALSE;
395 if ((gint)g_utf8_strlen(utf8str, -1) != len) {
396 g_free(utf8str);
397 return FALSE;
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;
405 wc = g_utf8_get_char(p);
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;
417 g_free(utf8str);
419 if (match_count == len)
420 return TRUE;
421 else
422 return FALSE;
425 static gboolean gtkut_text_buffer_find(GtkTextBuffer *buffer, const GtkTextIter *iter,
426 const gchar *str, gboolean case_sens,
427 GtkTextIter *match_pos)
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;
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);
442 if (!wcs || items_written <= 0) return FALSE;
443 len = (gint)items_written;
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;
453 } while (gtk_text_iter_forward_char(&iter_));
455 g_free(wcs);
457 return found;
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)
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;
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);
478 if (!wcs || items_written <= 0) return FALSE;
479 len = (gint)items_written;
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;
491 g_free(wcs);
493 return found;
496 gchar *gtkut_text_view_get_selection(GtkTextView *textview)
498 GtkTextBuffer *buffer;
499 GtkTextIter start_iter, end_iter;
500 gboolean found;
502 cm_return_val_if_fail(GTK_IS_TEXT_VIEW(textview), NULL);
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;
516 void gtkut_text_view_set_position(GtkTextView *text, gint pos)
518 GtkTextBuffer *buffer;
519 GtkTextIter iter;
520 GtkTextMark *mark;
522 cm_return_if_fail(text != NULL);
524 buffer = gtk_text_view_get_buffer(text);
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);
532 gboolean gtkut_text_view_search_string(GtkTextView *text, const gchar *str,
533 gboolean case_sens)
535 GtkTextBuffer *buffer;
536 GtkTextIter iter, match_pos;
537 GtkTextMark *mark;
538 gint len;
540 cm_return_val_if_fail(text != NULL, FALSE);
541 cm_return_val_if_fail(str != NULL, FALSE);
543 buffer = gtk_text_view_get_buffer(text);
545 len = g_utf8_strlen(str, -1);
546 cm_return_val_if_fail(len >= 0, FALSE);
548 mark = gtk_text_buffer_get_insert(buffer);
549 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
551 if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
552 &match_pos)) {
553 GtkTextIter end = match_pos;
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;
562 return FALSE;
565 gboolean gtkut_text_view_search_string_backward(GtkTextView *text, const gchar *str,
566 gboolean case_sens)
568 GtkTextBuffer *buffer;
569 GtkTextIter iter, match_pos;
570 GtkTextMark *mark;
571 gint len;
573 cm_return_val_if_fail(text != NULL, FALSE);
574 cm_return_val_if_fail(str != NULL, FALSE);
576 buffer = gtk_text_view_get_buffer(text);
578 len = g_utf8_strlen(str, -1);
579 cm_return_val_if_fail(len >= 0, FALSE);
581 mark = gtk_text_buffer_get_insert(buffer);
582 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
584 if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
585 &match_pos)) {
586 GtkTextIter end = match_pos;
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;
594 return FALSE;
597 void gtkut_window_popup(GtkWidget *window)
599 GdkWindow *gdkwin;
600 gint x, y, sx, sy, new_x, new_y;
601 GdkRectangle workarea = {0};
603 gdkwin = gtk_widget_get_window(window);
605 cm_return_if_fail(window != NULL);
606 cm_return_if_fail(gdkwin != NULL);
608 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
609 &workarea);
611 sx = MAX(1, workarea.width);
612 sy = MAX(1, workarea.height);
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);
620 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window), FALSE);
621 gtk_window_present_with_time(GTK_WINDOW(window), time(NULL));
624 void gtkut_widget_get_uposition(GtkWidget *widget, gint *px, gint *py)
626 GdkWindow *gdkwin;
627 gint x, y;
628 gint sx, sy;
629 GdkRectangle workarea = {0};
631 gdkwin = gtk_widget_get_window(widget);
633 cm_return_if_fail(widget != NULL);
634 cm_return_if_fail(gdkwin != NULL);
636 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
637 &workarea);
639 sx = MAX(1, workarea.width);
640 sy = MAX(1, workarea.height);
642 /* gdk_window_get_root_origin ever return *rootwindow*'s position */
643 gdk_window_get_root_origin(gdkwin, &x, &y);
645 x %= sx; if (x < 0) x = 0;
646 y %= sy; if (y < 0) y = 0;
647 *px = x;
648 *py = y;
651 static void gtkut_clist_bindings_add(GtkWidget *clist)
653 GtkBindingSet *binding_set;
655 binding_set = gtk_binding_set_by_class
656 (GTK_CMCLIST_GET_CLASS(clist));
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);
668 void gtkut_widget_init(void)
670 GtkWidget *clist;
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));
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));
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));
691 void gtkut_widget_set_app_icon(GtkWidget *widget)
693 static GList *icon_list = NULL;
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);
706 if (icon_list)
707 gtk_window_set_icon_list(GTK_WINDOW(widget), icon_list);
710 void gtkut_widget_set_composer_icon(GtkWidget *widget)
712 static GList *icon_list = NULL;
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);
725 if (icon_list)
726 gtk_window_set_icon_list(GTK_WINDOW(widget), icon_list);
729 static gboolean move_bar = FALSE;
730 static gint move_bar_id = -1;
732 static gboolean move_bar_cb(gpointer data)
734 GtkWidget *w = (GtkWidget *)data;
735 if (!move_bar)
736 return FALSE;
738 if (!GTK_IS_PROGRESS_BAR(w)) {
739 return FALSE;
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;
747 GtkWidget *label_window_create(const gchar *str)
749 GtkWidget *window;
750 GtkWidget *label, *vbox, *hbox;
751 GtkWidget *wait_progress = gtk_progress_bar_new();
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));
762 label = gtk_label_new(str);
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);
772 gtk_container_add(GTK_CONTAINER(window), vbox);
773 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
774 gtk_widget_show_all(vbox);
776 gtk_widget_show_now(window);
778 if (move_bar_id == -1) {
779 move_bar_id = g_timeout_add(200, move_bar_cb, wait_progress);
780 move_bar = TRUE;
783 GTK_EVENTS_FLUSH();
785 return window;
788 void label_window_destroy(GtkWidget *window)
790 move_bar = FALSE;
791 g_source_remove(move_bar_id);
792 move_bar_id = -1;
793 GTK_EVENTS_FLUSH();
794 gtk_widget_destroy(window);
797 GtkWidget *gtkut_account_menu_new(GList *ac_list,
798 GCallback callback,
799 gpointer data)
801 GList *cur_ac;
802 GtkWidget *optmenu;
803 GtkListStore *menu;
804 GtkTreeIter iter;
805 PrefsAccount *account;
806 gchar *name;
808 cm_return_val_if_fail(ac_list != NULL, NULL);
810 optmenu = gtkut_sc_combobox_create(NULL, FALSE);
811 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
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);
827 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
829 if( callback != NULL )
830 g_signal_connect(G_OBJECT(optmenu), "changed", callback, data);
832 return optmenu;
836 *\brief Tries to find a focused child using a lame strategy
838 GtkWidget *gtkut_get_focused_child(GtkContainer *parent)
840 GtkWidget *result = NULL;
841 GList *child_list = NULL;
842 GList *c;
844 cm_return_val_if_fail(parent, NULL);
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;
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;
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));
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));
881 g_list_free(child_list);
883 return result;
887 *\brief Create a Browse (file) button based on GTK stock
889 GtkWidget *gtkut_get_browse_file_btn(const gchar *button_label)
891 GtkWidget *button;
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));
897 return button;
901 *\brief Create a Browse (directory) button based on GTK stock
903 GtkWidget *gtkut_get_browse_directory_btn(const gchar *button_label)
905 GtkWidget *button;
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));
911 return button;
914 GtkWidget *gtkut_get_replace_btn(const gchar *button_label)
916 GtkWidget *button;
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));
922 return button;
925 GtkWidget *gtkut_stock_button(const gchar *stock_image, const gchar *label)
927 GtkWidget *button;
929 cm_return_val_if_fail(stock_image != NULL, NULL);
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);
937 return button;
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.
948 GtkWidget *gtkut_get_options_frame(GtkWidget *box, GtkWidget **pframe,
949 const gchar *frame_label)
951 GtkWidget *vbox;
952 GtkWidget *frame;
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);
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);
964 if (pframe != NULL)
965 *pframe = frame;
967 return vbox;
970 #if HAVE_LIBCOMPFACE
971 static gint create_xpm_from_xface(gchar *xpm[], const gchar *xface)
973 static gchar *bit_pattern[] = {
974 "....",
975 "...#",
976 "..#.",
977 "..##",
978 ".#..",
979 ".#.#",
980 ".##.",
981 ".###",
982 "#...",
983 "#..#",
984 "#.#.",
985 "#.##",
986 "##..",
987 "##.#",
988 "###.",
989 "####"
992 static gchar *xface_header = "48 48 2 1";
993 static gchar *xface_black = "# c #000000";
994 static gchar *xface_white = ". c #ffffff";
996 gint i, line = 0;
997 const guchar *p;
998 gchar buf[WIDTH * 4 + 1]; /* 4 = strlen("0x0000") */
1000 p = xface;
1002 strcpy(xpm[line++], xface_header);
1003 strcpy(xpm[line++], xface_black);
1004 strcpy(xpm[line++], xface_white);
1006 for (i = 0; i < HEIGHT; i++) {
1007 gint col;
1009 buf[0] = '\0';
1011 for (col = 0; col < 3; col++) {
1012 gint figure;
1014 p += 2; /* skip '0x' */
1016 for (figure = 0; figure < 4; figure++) {
1017 gint n = 0;
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;
1027 strcat(buf, bit_pattern[n]);
1028 p++; /* skip ',' */
1031 p++; /* skip '\n' */
1034 strcpy(xpm[line++], buf);
1035 p++;
1038 return 0;
1040 #endif
1042 gboolean get_tag_range(GtkTextIter *iter,
1043 GtkTextTag *tag,
1044 GtkTextIter *start_iter,
1045 GtkTextIter *end_iter)
1047 GtkTextIter _start_iter, _end_iter;
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;
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;
1061 *start_iter = _start_iter;
1062 *end_iter = _end_iter;
1064 return TRUE;
1067 #if HAVE_LIBCOMPFACE
1068 GtkWidget *xface_get_from_header(const gchar *o_xface)
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];
1076 if (o_xface == NULL)
1077 return NULL;
1079 strncpy(xface, o_xface, sizeof(xface) - 1);
1080 xface[sizeof(xface) - 1] = '\0';
1082 if (uncompface(xface) < 0) {
1083 g_warning("uncompface failed");
1084 return NULL;
1087 if (xpm_xface_init) {
1088 gint i;
1090 for (i = 0; i < XPM_XFACE_HEIGHT; i++) {
1091 xpm_xface[i] = g_malloc(WIDTH + 1);
1092 *xpm_xface[i] = '\0';
1094 xpm_xface_init = FALSE;
1097 create_xpm_from_xface(xpm_xface, xface);
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);
1103 return ret;
1105 #endif
1107 GtkWidget *face_get_from_header(const gchar *o_face)
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;
1117 if (o_face == NULL || strlen(o_face) == 0)
1118 return NULL;
1120 strncpy2(face, o_face, sizeof(face));
1122 unfold_line(face); /* strip all whitespace and linebreaks */
1123 remove_space(face);
1125 face_png = g_base64_decode(face, &pngsize);
1126 debug_print("---------------------- loaded face png\n");
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;
1135 g_free(face_png);
1137 pixbuf = g_object_ref(gdk_pixbuf_loader_get_pixbuf(loader));
1139 g_object_unref(loader);
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;
1147 image = gtk_image_new_from_pixbuf(pixbuf);
1148 g_object_unref(pixbuf);
1149 return image;
1152 static gboolean _combobox_separator_func(GtkTreeModel *model,
1153 GtkTreeIter *iter, gpointer data)
1155 gchar *txt = NULL;
1157 cm_return_val_if_fail(model != NULL, FALSE);
1159 gtk_tree_model_get(model, iter, COMBOBOX_TEXT, &txt, -1);
1161 if( txt == NULL )
1162 return TRUE;
1164 g_free(txt);
1165 return FALSE;
1168 GtkWidget *gtkut_sc_combobox_create(GtkWidget *eventbox, gboolean focus_on_click)
1170 GtkWidget *combobox;
1171 GtkListStore *menu;
1172 GtkCellRenderer *rend;
1174 menu = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
1176 combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(menu));
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);
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);
1189 gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(combobox),
1190 (GtkTreeViewRowSeparatorFunc)_combobox_separator_func, NULL, NULL);
1192 return combobox;
1195 static void gtkutils_smooth_scroll_do(GtkWidget *widget, GtkAdjustment *vadj,
1196 gfloat old_value, gfloat last_value,
1197 gint step)
1199 gint change_value;
1200 gboolean up;
1201 gint i;
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;
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);
1217 gtk_adjustment_set_value(vadj, last_value);
1218 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1220 gtk_widget_queue_draw(widget);
1223 static gboolean gtkutils_smooth_scroll_page(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1225 gfloat upper;
1226 gfloat page_incr;
1227 gfloat old_value;
1228 gfloat last_value;
1230 page_incr = gtk_adjustment_get_page_increment(vadj);
1231 if (prefs_common.scroll_halfpage)
1232 page_incr /= 2;
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);
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);
1251 gtkutils_smooth_scroll_do(widget, vadj, old_value,
1252 last_value,
1253 prefs_common.scroll_step);
1254 } else
1255 return FALSE;
1258 return TRUE;
1261 gboolean gtkutils_scroll_page(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1263 gfloat upper;
1264 gfloat page_incr;
1265 gfloat old_value;
1267 if (prefs_common.enable_smooth_scroll)
1268 return gtkutils_smooth_scroll_page(widget, vadj, up);
1270 page_incr = gtk_adjustment_get_page_increment(vadj);
1271 if (prefs_common.scroll_halfpage)
1272 page_incr /= 2;
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;
1295 return TRUE;
1298 static void gtkutils_smooth_scroll_one_line(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1300 gfloat upper;
1301 gfloat old_value;
1302 gfloat last_value;
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);
1311 gtkutils_smooth_scroll_do(widget, vadj, old_value,
1312 last_value,
1313 prefs_common.scroll_step);
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);
1320 gtkutils_smooth_scroll_do(widget, vadj, old_value,
1321 last_value,
1322 prefs_common.scroll_step);
1327 void gtkutils_scroll_one_line(GtkWidget *widget, GtkAdjustment *vadj, gboolean up)
1329 gfloat upper;
1330 gfloat old_value;
1332 if (prefs_common.enable_smooth_scroll) {
1333 gtkutils_smooth_scroll_one_line(widget, vadj, up);
1334 return;
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);
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);
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),
1366 GtkTreeIter cur_iter;
1367 gchar *cur_value;
1368 gboolean valid;
1369 gint count;
1371 cm_return_val_if_fail(model != NULL, FALSE);
1372 cm_return_val_if_fail(iter != NULL, FALSE);
1374 if (text == NULL || *text == '\0')
1375 return FALSE;
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);
1382 if (strcmp(text, cur_value) == 0) {
1383 g_free(cur_value);
1384 if (count <= 0)
1385 return FALSE;
1387 return gtk_tree_model_iter_nth_child(model, iter, NULL, count - 1);
1390 g_free(cur_value);
1391 valid = gtk_tree_model_iter_next(model, &cur_iter);
1392 count++;
1394 return FALSE;
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.
1402 gint count;
1404 cm_return_val_if_fail(model != NULL, FALSE);
1405 cm_return_val_if_fail(iter != NULL, FALSE);
1407 count = gtk_tree_model_iter_n_children(model, NULL);
1409 if (count <= 0)
1410 return FALSE;
1412 return gtk_tree_model_iter_nth_child(model, iter, NULL, count - 1);
1415 GtkWidget *gtkut_window_new (GtkWindowType type,
1416 const gchar *class)
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;
1424 static gboolean gtkut_tree_iter_comp(GtkTreeModel *model,
1425 GtkTreeIter *iter1,
1426 GtkTreeIter *iter2)
1428 GtkTreePath *path1 = gtk_tree_model_get_path(model, iter1);
1429 GtkTreePath *path2 = gtk_tree_model_get_path(model, iter2);
1430 gboolean result;
1432 result = gtk_tree_path_compare(path1, path2) == 0;
1434 gtk_tree_path_free(path1);
1435 gtk_tree_path_free(path2);
1437 return result;
1441 *\brief Get selected row number.
1443 gint gtkut_list_view_get_selected_row(GtkWidget *list_view)
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;
1452 if (n_rows == 0)
1453 return -1;
1455 selection = gtk_tree_view_get_selection(view);
1456 if (!gtk_tree_selection_get_selected(selection, &model, &iter))
1457 return -1;
1459 /* get all iterators and compare them... */
1460 for (row = 0; row < n_rows; row++) {
1461 GtkTreeIter itern;
1463 if (gtk_tree_model_iter_nth_child(model, &itern, NULL, row)
1464 && gtkut_tree_iter_comp(model, &iter, &itern))
1465 return row;
1468 return -1;
1472 *\brief Select a row by its number.
1474 gboolean gtkut_list_view_select_row(GtkWidget *list, gint row)
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;
1482 if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row))
1483 return FALSE;
1485 gtk_tree_selection_select_iter(selection, &iter);
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);
1491 return TRUE;
1494 static GtkUIManager *gui_manager = NULL;
1496 GtkUIManager *gtkut_create_ui_manager(void)
1498 cm_return_val_if_fail(gui_manager == NULL, gui_manager);
1499 return (gui_manager = gtk_ui_manager_new());
1502 GtkUIManager *gtkut_ui_manager(void)
1504 return gui_manager;
1507 typedef struct _ClawsIOClosure ClawsIOClosure;
1509 struct _ClawsIOClosure
1511 ClawsIOFunc function;
1512 GIOCondition condition;
1513 GDestroyNotify notify;
1514 gpointer data;
1517 static gboolean
1518 claws_io_invoke (GIOChannel *source,
1519 GIOCondition condition,
1520 gpointer data)
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);
1532 return TRUE;
1535 static void
1536 claws_io_destroy (gpointer data)
1538 ClawsIOClosure *closure = data;
1540 if (closure->notify)
1541 closure->notify (closure->data);
1543 g_free (closure);
1546 gint
1547 claws_input_add (gint source,
1548 GIOCondition condition,
1549 ClawsIOFunc function,
1550 gpointer data,
1551 gboolean is_sock)
1553 guint result;
1554 ClawsIOClosure *closure = g_new (ClawsIOClosure, 1);
1555 GIOChannel *channel;
1557 closure->function = function;
1558 closure->condition = condition;
1559 closure->notify = NULL;
1560 closure->data = data;
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);
1575 return result;
1579 * Load a pixbuf fitting inside the specified size. EXIF orientation is
1580 * respected if available.
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
1587 * @return a GdkPixbuf
1589 GdkPixbuf *claws_load_pixbuf_fitting(GdkPixbuf *src_pixbuf, gboolean inline_img,
1590 gboolean fit_img_height, int box_width, int box_height)
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;
1598 pixbuf = src_pixbuf;
1600 if (pixbuf == NULL)
1601 return NULL;
1603 angle = 0;
1604 flip_horiz = flip_vert = FALSE;
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;
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;
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;
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;
1654 w = gdk_pixbuf_get_width(pixbuf);
1655 h = gdk_pixbuf_get_height(pixbuf);
1657 avail_width = box_width-32;
1658 avail_height = box_height;
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;
1666 if (h > avail_height) {
1667 w = (avail_height * w) / h;
1668 h = avail_height;
1670 } else {
1671 if (w > avail_width || h > avail_height) {
1672 h = (avail_width * h) / w;
1673 w = avail_width;
1676 t_pixbuf = gdk_pixbuf_scale_simple(pixbuf,
1677 w, h, GDK_INTERP_BILINEAR);
1678 g_object_unref(pixbuf);
1679 pixbuf = t_pixbuf;
1682 return pixbuf;
1685 #if defined USE_GNUTLS
1686 static void auto_configure_done(const gchar *hostname, gint port, gboolean ssl, AutoConfigureData *data)
1688 gboolean smtp = strcmp(data->tls_service, "submission") == 0 ? TRUE : FALSE;
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);
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);
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);
1726 /* Check authentication by default. This is probably required if
1727 * auto-configuration worked.
1729 if (data->auth_checkbtn)
1730 gtk_toggle_button_set_active(data->auth_checkbtn, TRUE);
1732 /* Set user ID to full email address, which is used by the
1733 * majority of providers where auto-configuration works.
1735 if (data->uid_entry)
1736 gtk_entry_set_text(data->uid_entry, data->address);
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);
1751 gtk_label_set_text(data->info_label, msg);
1752 g_free(msg);
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);
1760 static void resolve_done(GObject *source, GAsyncResult *result, gpointer user_data)
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;
1771 answers = g_resolver_lookup_service_finish(resolver, result, &error);
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;
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);
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);
1803 g_free(hostname);
1804 g_object_unref(resolver);
1807 void auto_configure_service(AutoConfigureData *data)
1809 GResolver *resolver;
1810 const gchar *cur_service = data->ssl_service != NULL ? data->ssl_service : data->tls_service;
1812 cm_return_if_fail(cur_service != NULL);
1813 cm_return_if_fail(data->address != NULL);
1815 resolver = g_resolver_get_default();
1816 if (resolver != NULL) {
1817 const gchar *domain = strchr(data->address, '@') + 1;
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);
1827 gboolean auto_configure_service_sync(const gchar *service, const gchar *domain, gchar **srvhost, guint16 *srvport)
1829 GResolver *resolver;
1830 GList *answers, *cur;
1831 GError *error = NULL;
1832 gboolean result = FALSE;
1834 cm_return_val_if_fail(service != NULL, FALSE);
1835 cm_return_val_if_fail(domain != NULL, FALSE);
1837 resolver = g_resolver_get_default();
1838 if (resolver == NULL)
1839 return FALSE;
1841 answers = g_resolver_lookup_service(resolver, service, "tcp", domain, NULL, &error);
1843 *srvhost = NULL;
1844 *srvport = 0;
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;
1858 g_resolver_free_targets(answers);
1859 } else if (error) {
1860 g_error_free(error);
1863 g_object_unref(resolver);
1864 return result;
1866 #endif
1868 gboolean gtkut_pointer_is_grabbed(GtkWidget *widget)
1870 GdkDisplay *display;
1871 GdkDevice *pointerdev;
1873 cm_return_val_if_fail(widget != NULL, FALSE);
1875 display = gtk_widget_get_display(widget);
1876 pointerdev = gdk_seat_get_pointer(gdk_display_get_default_seat(display));
1878 return gdk_display_device_is_grabbed(display, pointerdev);
1881 gpointer gtkut_tree_view_get_selected_pointer(GtkTreeView *view,
1882 gint column, GtkTreeModel **_model, GtkTreeSelection **_selection,
1883 GtkTreeIter *_iter)
1885 GtkTreeIter iter;
1886 GtkTreeModel *model;
1887 GtkTreeSelection *sel;
1888 gpointer ptr;
1889 GType type;
1891 cm_return_val_if_fail(view != NULL, NULL);
1892 cm_return_val_if_fail(column >= 0, NULL);
1894 model = gtk_tree_view_get_model(view);
1895 if (_model != NULL)
1896 *_model = model;
1898 sel = gtk_tree_view_get_selection(view);
1899 if (_selection != NULL)
1900 *_selection = sel;
1902 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
1903 return NULL; /* No row selected */
1905 if (_iter != NULL)
1906 *_iter = iter;
1908 if (gtk_tree_selection_count_selected_rows(sel) > 1)
1909 return NULL; /* Can't work with multiselect */
1911 cm_return_val_if_fail(
1912 gtk_tree_model_get_n_columns(model) > column,
1913 NULL);
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);
1920 gtk_tree_model_get(model, &iter, column, &ptr, -1);
1922 return ptr;
1925 static GList *get_predefined_times(void)
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);
1935 return times;
1938 static int get_list_item_num(int h, int m)
1940 if (m % 15 != 0)
1941 return -1;
1943 return (h*4 + m/15);
1946 GtkWidget *gtkut_time_select_combo_new()
1948 GtkWidget *combo = gtk_combo_box_text_new_with_entry();
1949 GList *times = get_predefined_times();
1951 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), -1);
1952 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(combo), times);
1954 list_free_strings_full(times);
1956 return combo;
1960 void gtkut_time_select_select_by_time(GtkComboBox *combo, int hour, int minute)
1962 gchar *time_text = g_strdup_printf("%02d:%02d", hour, minute);
1963 gint num = get_list_item_num(hour, minute);
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);
1970 g_free(time_text);
1973 static void get_time_from_combo(GtkComboBox *combo, int *h, int *m)
1975 gchar *tmp;
1976 gchar **parts;
1978 if (!h || !m)
1979 return;
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]);
1987 g_strfreev(parts);
1988 g_free(tmp);
1991 gboolean gtkut_time_select_get_time(GtkComboBox *combo, int *hour, int *minute)
1993 const gchar *value = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))));
1995 if (value == NULL || strlen(value) != 5)
1996 return FALSE;
1998 if (hour == NULL || minute == NULL)
1999 return FALSE;
2001 get_time_from_combo(combo, hour, minute);
2003 if (*hour < 0 || *hour > 23)
2004 return FALSE;
2005 if (*minute < 0 || *minute > 59)
2006 return FALSE;
2008 return TRUE;
2011 void gtk_calendar_select_today(GtkCalendar *calendar)
2013 time_t t = time (NULL);
2014 struct tm buft;
2015 struct tm *lt = localtime_r (&t, &buft);
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);
2023 #define RGBA_ELEMENT_TO_BYTE(x) (int)((gdouble)x * 255)
2024 gchar *gtkut_gdk_rgba_to_string(GdkRGBA *rgba)
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));
2031 return str;
2033 #undef RGBA_ELEMENT_TO_BYTE