2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2007-2022 The Claws Mail Team
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/>.
21 #include "claws-features.h"
27 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
34 #include "addrcustomattr.h"
35 #include "manage_window.h"
36 #include "prefs_common.h"
37 #include "alertpanel.h"
39 #include "editaddress.h"
41 static GtkActionGroup
*custom_attr_popup_action
= NULL
;
42 static GtkWidget
*custom_attr_popup_menu
= NULL
;
44 static struct CustomAttrWindow
52 GtkWidget
*cancel_btn
;
63 static gchar
*default_addressbook_attributes_table
[] = {
76 static gboolean dirty
= FALSE
;
78 static void custom_attr_window_create(void);
79 static void custom_attr_selected_attr_edited(GtkCellRendererText
*widget
,
80 gchar
*arg1
, gchar
*arg2
,
81 GtkWidget
*list_view
);
82 static void custom_attr_window_load_list(GList
*list
);
83 static void custom_attr_window_save_list (void);
84 static GList
*custom_attr_default_list(void);
86 void addressbook_custom_attr_edit()
88 if (!custom_attr_window
.window
)
89 custom_attr_window_create();
91 manage_window_set_transient(GTK_WINDOW(custom_attr_window
.window
));
92 gtk_widget_grab_focus(custom_attr_window
.ok_btn
);
94 custom_attr_window_load_list(prefs_common
.addressbook_custom_attributes
);
96 gtk_widget_show(custom_attr_window
.window
);
97 gtk_widget_grab_focus(custom_attr_window
.attr_list
);
98 gtk_window_set_modal(GTK_WINDOW(custom_attr_window
.window
), TRUE
);
101 static gint
custom_attr_cmp_func (GtkTreeModel
*model
, GtkTreeIter
*a
,
102 GtkTreeIter
*b
, gpointer userdata
)
104 gchar
*name1
, *name2
;
107 gtk_tree_model_get(model
, a
, CUSTOM_ATTR_NAME
, &name1
, -1);
108 gtk_tree_model_get(model
, b
, CUSTOM_ATTR_NAME
, &name2
, -1);
111 return name2
== NULL
? 0:1;
116 res
= g_utf8_collate(name1
, name2
);
123 static GtkListStore
* custom_attr_window_create_data_store(void)
125 GtkListStore
*store
= gtk_list_store_new(N_CUSTOM_ATTR
,
128 GtkTreeSortable
*sortable
= GTK_TREE_SORTABLE(store
);
130 gtk_tree_sortable_set_sort_func(sortable
, 0, custom_attr_cmp_func
,
136 static void custom_attr_window_create_list_view_columns(GtkWidget
*list_view
)
138 GtkTreeViewColumn
*column
;
139 GtkCellRenderer
*renderer
;
141 renderer
= gtk_cell_renderer_text_new();
142 g_object_set(G_OBJECT(renderer
), "editable", TRUE
, NULL
);
144 column
= gtk_tree_view_column_new_with_attributes
145 (_("Attribute name"),
147 "text", CUSTOM_ATTR_NAME
,
149 gtk_tree_view_append_column(GTK_TREE_VIEW(list_view
), column
);
150 gtk_tree_view_column_set_resizable(column
, TRUE
);
151 gtk_tree_view_set_search_column(GTK_TREE_VIEW(list_view
),
153 g_signal_connect(G_OBJECT(renderer
), "edited",
154 G_CALLBACK(custom_attr_selected_attr_edited
),
158 static void custom_attr_window_list_view_clear_list(GtkWidget
*list_view
, gboolean warn
)
160 if (!warn
|| alertpanel(_("Delete all attribute names"),
161 _("Do you really want to delete all attribute names?"),
162 NULL
, _("_Cancel"), "edit-delete", _("D_elete"), NULL
, NULL
,
163 ALERTFOCUS_FIRST
) == G_ALERTALTERNATE
) {
164 GtkListStore
*list_store
= GTK_LIST_STORE(gtk_tree_view_get_model
165 (GTK_TREE_VIEW(list_view
)));
166 gtk_list_store_clear(list_store
);
171 static void custom_attr_popup_clear_list (void *obj
, void *data
)
173 custom_attr_window_list_view_clear_list(custom_attr_window
.attr_list
, TRUE
);
176 static void custom_attr_popup_delete (void *obj
, void *data
)
181 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection
182 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)),
186 if (alertpanel(_("Delete attribute name"),
187 _("Do you really want to delete this attribute name?"),
188 NULL
, _("_Cancel"), "edit-delete", _("D_elete"), NULL
, NULL
,
189 ALERTFOCUS_FIRST
) == G_ALERTALTERNATE
) {
190 gtk_list_store_remove(GTK_LIST_STORE(model
), &sel
);
195 static void custom_attr_popup_factory_defaults (void *obj
, void *data
)
197 if (alertpanel(_("Reset to default"),
198 _("Do you really want to replace all attribute names\nwith the default set?"),
199 NULL
, _("_No"), NULL
, _("_Yes"), NULL
, NULL
, ALERTFOCUS_FIRST
) == G_ALERTALTERNATE
) {
200 GList
*tmp
= custom_attr_default_list();
201 custom_attr_window_load_list(tmp
);
215 static GtkActionEntry custom_attr_popup_entries
[] =
217 {"CustomAttrPopup", NULL
, "CustomAttrPopup", NULL
, NULL
, NULL
},
218 {"CustomAttrPopup/Delete", NULL
, N_("_Delete"), NULL
, NULL
, G_CALLBACK(custom_attr_popup_delete
) },
219 {"CustomAttrPopup/DeleteAll", NULL
, N_("Delete _all"), NULL
, NULL
, G_CALLBACK(custom_attr_popup_clear_list
) },
220 {"CustomAttrPopup/Reset", NULL
, N_("_Reset to default"), NULL
, NULL
, G_CALLBACK(custom_attr_popup_factory_defaults
) },
223 static gint
custom_attr_list_btn_pressed(GtkWidget
*widget
, GdkEventButton
*event
,
224 GtkTreeView
*list_view
)
226 if (event
&& event
->button
== 3) {
227 GtkTreeModel
*model
= gtk_tree_view_get_model(list_view
);
231 if (!custom_attr_popup_menu
) {
232 custom_attr_popup_action
= cm_menu_create_action_group("CustomAttrPopup", custom_attr_popup_entries
,
233 G_N_ELEMENTS(custom_attr_popup_entries
), (gpointer
)list_view
);
234 MENUITEM_ADDUI("/Menus", "CustomAttrPopup", "CustomAttrPopup", GTK_UI_MANAGER_MENU
)
235 MENUITEM_ADDUI("/Menus/CustomAttrPopup", "Delete", "CustomAttrPopup/Delete", GTK_UI_MANAGER_MENUITEM
)
236 MENUITEM_ADDUI("/Menus/CustomAttrPopup", "DeleteAll", "CustomAttrPopup/DeleteAll", GTK_UI_MANAGER_MENUITEM
)
237 MENUITEM_ADDUI("/Menus/CustomAttrPopup", "Reset", "CustomAttrPopup/Reset", GTK_UI_MANAGER_MENUITEM
)
238 custom_attr_popup_menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(
239 gtk_ui_manager_get_widget(gtkut_ui_manager(), "/Menus/CustomAttrPopup")) );
242 /* grey out popup menu items if list is empty */
243 non_empty
= gtk_tree_model_get_iter_first(model
, &iter
);
244 cm_menu_set_sensitive("CustomAttrPopup/Delete", non_empty
);
245 cm_menu_set_sensitive("CustomAttrPopup/DeleteAll", non_empty
);
247 gtk_menu_popup_at_widget(GTK_MENU(custom_attr_popup_menu
), widget
, 3, 3, NULL
);
254 static gboolean
custom_attr_list_popup_menu(GtkWidget
*widget
, gpointer data
)
256 GtkTreeView
*list_view
= (GtkTreeView
*)data
;
257 GdkEventButton event
;
260 event
.time
= gtk_get_current_event_time();
262 custom_attr_list_btn_pressed(NULL
, &event
, list_view
);
267 static GtkWidget
*custom_attr_window_list_view_create (void)
269 GtkTreeView
*list_view
;
270 GtkTreeSelection
*selector
;
273 model
= GTK_TREE_MODEL(custom_attr_window_create_data_store());
274 list_view
= GTK_TREE_VIEW(gtk_tree_view_new_with_model(model
));
275 g_object_unref(model
);
276 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model
),
277 CUSTOM_ATTR_NAME
, GTK_SORT_ASCENDING
);
279 gtk_tree_view_set_rules_hint(list_view
, prefs_common
.use_stripes_everywhere
);
281 selector
= gtk_tree_view_get_selection(list_view
);
282 gtk_tree_selection_set_mode(selector
, GTK_SELECTION_BROWSE
);
284 /* create the columns */
285 custom_attr_window_create_list_view_columns(GTK_WIDGET(list_view
));
287 g_signal_connect(G_OBJECT(list_view
), "popup-menu",
288 G_CALLBACK(custom_attr_list_popup_menu
), list_view
);
289 g_signal_connect(G_OBJECT(list_view
), "button-press-event",
290 G_CALLBACK(custom_attr_list_btn_pressed
), list_view
);
291 return GTK_WIDGET(list_view
);
294 static void custom_attr_window_close(void)
297 custom_attr_window_save_list();
298 custom_attr_window_list_view_clear_list(custom_attr_window
.attr_list
, FALSE
);
299 gtk_widget_hide(custom_attr_window
.window
);
300 gtk_window_set_modal(GTK_WINDOW(custom_attr_window
.window
), FALSE
);
301 if (dirty
&& !prefs_common
.addressbook_use_editaddress_dialog
)
302 addressbook_edit_reload_attr_list();
305 static void custom_attr_window_cancel_cb(GtkWidget
*widget
,
309 custom_attr_window_close();
312 static void custom_attr_window_ok_cb(GtkWidget
*widget
,
315 custom_attr_window_close();
318 static void custom_attr_selected_attr_edited(GtkCellRendererText
*widget
,
319 gchar
*path
, gchar
*new_text
,
320 GtkWidget
*list_view
)
323 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list_view
));
325 if (!gtk_tree_model_get_iter_from_string(model
, &iter
, path
))
328 if (!new_text
|| !*new_text
)
331 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
332 CUSTOM_ATTR_NAME
, new_text
,
337 typedef struct FindAttrInStore
{
343 static gboolean
find_attr_in_store(GtkTreeModel
*model
,
346 FindAttrInStore
*data
)
349 gtk_tree_model_get(model
, iter
, CUSTOM_ATTR_NAME
, &attr
, -1);
351 if (g_utf8_collate(data
->attr
, attr
)==0) {
352 data
->path
= path
; /* signal we found it */
361 static void custom_attr_window_add_attr(void)
363 gchar
*new_attr
= gtk_editable_get_chars(GTK_EDITABLE(custom_attr_window
.add_entry
),
366 g_strstrip(new_attr
);
367 if (new_attr
&& *new_attr
) {
368 GtkListStore
*list_store
= GTK_LIST_STORE(gtk_tree_view_get_model
369 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)));
374 gtk_tree_model_foreach(gtk_tree_view_get_model
375 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)),
376 (GtkTreeModelForeachFunc
) find_attr_in_store
,
380 /* activate existing one */
381 GtkTreeSelection
*selection
;
383 GtkTreeModel
*model
= gtk_tree_view_get_model(
384 GTK_TREE_VIEW(custom_attr_window
.attr_list
));
386 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(custom_attr_window
.attr_list
));
387 gtk_tree_selection_select_iter(selection
, &fis
.iter
);
389 path
= gtk_tree_model_get_path(model
, &fis
.iter
);
390 /* XXX returned path may not be valid??? create new one to be sure */
391 gtk_tree_view_set_cursor(GTK_TREE_VIEW(custom_attr_window
.attr_list
),
394 gtk_list_store_set(list_store
, &fis
.iter
,
395 CUSTOM_ATTR_NAME
, new_attr
,
398 gtk_tree_path_free(path
);
403 gtk_list_store_append(list_store
, &iter
);
404 gtk_list_store_set(list_store
, &iter
,
405 CUSTOM_ATTR_NAME
, new_attr
,
410 alertpanel_error(_("Attribute name is not set."));
415 static void custom_attr_window_add_attr_cb(GtkWidget
*widget
,
418 custom_attr_window_add_attr();
419 gtk_entry_set_text(GTK_ENTRY(custom_attr_window
.add_entry
), "");
420 gtk_widget_grab_focus(custom_attr_window
.attr_list
);
423 static void custom_attr_window_del_attr_cb(GtkWidget
*widget
,
426 custom_attr_popup_delete(NULL
, NULL
);
427 gtk_widget_grab_focus(custom_attr_window
.attr_list
);
430 static gboolean
custom_attr_window_key_pressed(GtkWidget
*widget
,
431 GdkEventKey
*event
, gpointer data
)
433 if (event
&& event
->keyval
== GDK_KEY_Escape
)
434 custom_attr_window_close();
435 else if (event
&& event
->keyval
== GDK_KEY_Delete
)
436 custom_attr_popup_delete(NULL
, NULL
);
440 static gboolean
custom_attr_window_add_key_pressed(GtkWidget
*widget
,
441 GdkEventKey
*event
, gpointer data
)
443 if (event
&& (event
->keyval
== GDK_KEY_KP_Enter
|| event
->keyval
== GDK_KEY_Return
)) {
444 custom_attr_window_add_attr();
445 gtk_entry_set_text(GTK_ENTRY(custom_attr_window
.add_entry
), "");
446 gtk_widget_grab_focus(custom_attr_window
.attr_list
);
451 static void custom_attr_window_create(void)
458 GtkWidget
*attr_list
;
459 GtkWidget
*cancel_btn
;
461 GtkWidget
*scrolledwin
;
462 GtkWidget
*new_attr_label
;
463 GtkWidget
*new_attr_entry
;
467 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "custom_attr_edit_window");
468 gtk_window_set_title (GTK_WINDOW(window
),
469 C_("Dialog title", "Edit attribute names"));
471 gtk_container_set_border_width (GTK_CONTAINER (window
), 8);
472 gtk_window_set_position (GTK_WINDOW (window
), GTK_WIN_POS_CENTER
);
473 gtk_window_set_resizable(GTK_WINDOW (window
), TRUE
);
474 g_signal_connect(G_OBJECT(window
), "delete_event",
475 G_CALLBACK(custom_attr_window_cancel_cb
), NULL
);
476 g_signal_connect(G_OBJECT(window
), "key_press_event",
477 G_CALLBACK(custom_attr_window_key_pressed
), NULL
);
478 MANAGE_WINDOW_SIGNALS_CONNECT (window
);
480 vbox1
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
481 hbox1
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
483 new_attr_label
= gtk_label_new(_("New attribute name:"));
484 gtk_label_set_xalign(GTK_LABEL(new_attr_label
), 0.0);
485 gtk_box_pack_start(GTK_BOX(hbox1
), new_attr_label
, FALSE
, FALSE
, 0);
487 new_attr_entry
= gtk_entry_new();
488 gtk_box_pack_start(GTK_BOX(hbox1
), new_attr_entry
, FALSE
, FALSE
, 0);
489 g_signal_connect(G_OBJECT(new_attr_entry
), "key_press_event",
490 G_CALLBACK(custom_attr_window_add_key_pressed
), NULL
);
492 add_btn
= gtkut_stock_button("list-add", _("_Add"));
493 gtk_box_pack_start(GTK_BOX(hbox1
), add_btn
, FALSE
, FALSE
, 0);
495 del_btn
= gtkut_stock_button("edit-delete", _("D_elete"));
496 gtk_box_pack_start(GTK_BOX(hbox1
), del_btn
, FALSE
, FALSE
, 0);
498 gtkut_stock_button_set_create(&hbox2
, &cancel_btn
, NULL
, _("_Cancel"),
499 &ok_btn
, NULL
, _("_OK"),
502 gtk_widget_show(new_attr_label
);
503 gtk_widget_show(new_attr_entry
);
504 gtk_widget_show(add_btn
);
505 gtk_widget_show(del_btn
);
506 gtk_widget_show(cancel_btn
);
507 gtk_widget_show(ok_btn
);
509 g_signal_connect(G_OBJECT(cancel_btn
), "clicked",
510 G_CALLBACK(custom_attr_window_cancel_cb
), NULL
);
511 g_signal_connect(G_OBJECT(ok_btn
), "clicked",
512 G_CALLBACK(custom_attr_window_ok_cb
), NULL
);
513 g_signal_connect(G_OBJECT(add_btn
), "clicked",
514 G_CALLBACK(custom_attr_window_add_attr_cb
), NULL
);
515 g_signal_connect(G_OBJECT(del_btn
), "clicked",
516 G_CALLBACK(custom_attr_window_del_attr_cb
), NULL
);
518 attr_list
= custom_attr_window_list_view_create();
520 label
= gtk_label_new(_("Adding or removing attribute names won't "
521 "affect attributes already set for contacts."));
522 gtk_widget_set_size_request(GTK_WIDGET(label
), 380, -1);
523 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
524 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
525 gtk_box_pack_start(GTK_BOX(vbox1
), label
, FALSE
, TRUE
, 0);
527 scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
528 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin
),
529 GTK_POLICY_AUTOMATIC
, GTK_POLICY_ALWAYS
);
531 gtk_widget_set_size_request(scrolledwin
, 400, 250);
533 gtk_container_add(GTK_CONTAINER(scrolledwin
), attr_list
);
534 gtk_box_pack_start(GTK_BOX(vbox1
), scrolledwin
, TRUE
, TRUE
, 0);
535 gtk_box_pack_start(GTK_BOX(vbox1
), hbox1
, FALSE
, FALSE
, 0);
536 gtk_box_pack_start(GTK_BOX(vbox1
), hbox2
, FALSE
, FALSE
, 0);
538 gtk_widget_show(label
);
539 gtk_widget_show(scrolledwin
);
540 gtk_widget_show(attr_list
);
541 gtk_widget_show(hbox2
);
542 gtk_widget_show(hbox1
);
543 gtk_widget_show(vbox1
);
544 gtk_container_add(GTK_CONTAINER (window
), vbox1
);
546 custom_attr_window
.window
= window
;
547 custom_attr_window
.hbox1
= hbox1
;
548 custom_attr_window
.hbox2
= hbox2
;
549 custom_attr_window
.vbox1
= vbox1
;
550 custom_attr_window
.label
= label
;
551 custom_attr_window
.attr_list
= attr_list
;
552 custom_attr_window
.cancel_btn
= cancel_btn
;
553 custom_attr_window
.ok_btn
= ok_btn
;
554 custom_attr_window
.add_btn
= add_btn
;
555 custom_attr_window
.add_entry
= new_attr_entry
;
558 static void custom_attr_window_load_list (GList
*list
)
560 /* copy attribute names list from prefs to store */
563 GtkListStore
*list_store
= GTK_LIST_STORE(gtk_tree_view_get_model
564 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)));
566 custom_attr_window_list_view_clear_list(custom_attr_window
.attr_list
, FALSE
);
570 gtk_list_store_append(list_store
, &iter
);
571 gtk_list_store_set(list_store
, &iter
,
572 CUSTOM_ATTR_NAME
, cur
->data
,
578 static GList
*store_to_glist
= NULL
;
580 static gboolean
custom_attr_store_to_glist (GtkTreeModel
*model
,
587 gtk_tree_model_get(model
, iter
, CUSTOM_ATTR_NAME
, &attr
, -1);
589 store_to_glist
= g_list_prepend(store_to_glist
, g_strdup(attr
));
595 static void custom_attr_window_save_list (void)
599 /* clear existing attribute names list in prefs */
600 cur
= prefs_common
.addressbook_custom_attributes
;
605 g_list_free(prefs_common
.addressbook_custom_attributes
);
607 /* copy attribute names list from store to prefs */
608 gtk_tree_model_foreach(gtk_tree_view_get_model
609 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)),
610 (GtkTreeModelForeachFunc
) custom_attr_store_to_glist
,
612 prefs_common
.addressbook_custom_attributes
= g_list_reverse(store_to_glist
);
613 store_to_glist
= NULL
;
616 static GList
*custom_attr_default_list(void)
618 /* returned GList must be deallocated by caller */
624 while (default_addressbook_attributes_table
[i
]) {
625 list
= g_list_prepend(
626 list
, g_strdup(gettext(default_addressbook_attributes_table
[i
])));
629 list
= g_list_reverse(list
);
633 GList
*addressbook_update_custom_attr_from_prefs(void)
635 /* load addressbook custom attribute names list from file */
636 /* use a list of default attribute names if storage file doesn't exist */
638 GList
*default_attr_list
;
641 /* load table into glist */
642 default_attr_list
= custom_attr_default_list();
644 list
= prefs_common_read_history_from_dir_with_defaults(ADDRBOOK_DIR
,
645 ADDRESSBOOK_CUSTOM_ATTRIBUTES
,
648 /* free glist if it's the one we return (the default one) */
649 if (list
!= default_attr_list
) {
650 cur
= default_attr_list
;
655 g_list_free(default_attr_list
);