2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2007-2024 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 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model
),
276 CUSTOM_ATTR_NAME
, GTK_SORT_ASCENDING
);
277 g_object_unref(model
);
279 selector
= gtk_tree_view_get_selection(list_view
);
280 gtk_tree_selection_set_mode(selector
, GTK_SELECTION_BROWSE
);
282 /* create the columns */
283 custom_attr_window_create_list_view_columns(GTK_WIDGET(list_view
));
285 g_signal_connect(G_OBJECT(list_view
), "popup-menu",
286 G_CALLBACK(custom_attr_list_popup_menu
), list_view
);
287 g_signal_connect(G_OBJECT(list_view
), "button-press-event",
288 G_CALLBACK(custom_attr_list_btn_pressed
), list_view
);
289 return GTK_WIDGET(list_view
);
292 static void custom_attr_window_close(void)
295 custom_attr_window_save_list();
296 custom_attr_window_list_view_clear_list(custom_attr_window
.attr_list
, FALSE
);
297 gtk_widget_hide(custom_attr_window
.window
);
298 gtk_window_set_modal(GTK_WINDOW(custom_attr_window
.window
), FALSE
);
299 if (dirty
&& !prefs_common
.addressbook_use_editaddress_dialog
)
300 addressbook_edit_reload_attr_list();
303 static void custom_attr_window_cancel_cb(GtkWidget
*widget
,
307 custom_attr_window_close();
310 static void custom_attr_window_ok_cb(GtkWidget
*widget
,
313 custom_attr_window_close();
316 static void custom_attr_selected_attr_edited(GtkCellRendererText
*widget
,
317 gchar
*path
, gchar
*new_text
,
318 GtkWidget
*list_view
)
321 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list_view
));
323 if (!gtk_tree_model_get_iter_from_string(model
, &iter
, path
))
326 if (!new_text
|| !*new_text
)
329 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
330 CUSTOM_ATTR_NAME
, new_text
,
335 typedef struct FindAttrInStore
{
341 static gboolean
find_attr_in_store(GtkTreeModel
*model
,
344 FindAttrInStore
*data
)
347 gtk_tree_model_get(model
, iter
, CUSTOM_ATTR_NAME
, &attr
, -1);
349 if (g_utf8_collate(data
->attr
, attr
)==0) {
350 data
->path
= path
; /* signal we found it */
359 static void custom_attr_window_add_attr(void)
361 gchar
*new_attr
= gtk_editable_get_chars(GTK_EDITABLE(custom_attr_window
.add_entry
),
364 g_strstrip(new_attr
);
365 if (new_attr
&& *new_attr
) {
366 GtkListStore
*list_store
= GTK_LIST_STORE(gtk_tree_view_get_model
367 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)));
372 gtk_tree_model_foreach(gtk_tree_view_get_model
373 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)),
374 (GtkTreeModelForeachFunc
) find_attr_in_store
,
378 /* activate existing one */
379 GtkTreeSelection
*selection
;
381 GtkTreeModel
*model
= gtk_tree_view_get_model(
382 GTK_TREE_VIEW(custom_attr_window
.attr_list
));
384 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(custom_attr_window
.attr_list
));
385 gtk_tree_selection_select_iter(selection
, &fis
.iter
);
387 path
= gtk_tree_model_get_path(model
, &fis
.iter
);
388 /* XXX returned path may not be valid??? create new one to be sure */
389 gtk_tree_view_set_cursor(GTK_TREE_VIEW(custom_attr_window
.attr_list
),
392 gtk_list_store_set(list_store
, &fis
.iter
,
393 CUSTOM_ATTR_NAME
, new_attr
,
396 gtk_tree_path_free(path
);
401 gtk_list_store_append(list_store
, &iter
);
402 gtk_list_store_set(list_store
, &iter
,
403 CUSTOM_ATTR_NAME
, new_attr
,
408 alertpanel_error(_("Attribute name is not set."));
413 static void custom_attr_window_add_attr_cb(GtkWidget
*widget
,
416 custom_attr_window_add_attr();
417 gtk_entry_set_text(GTK_ENTRY(custom_attr_window
.add_entry
), "");
418 gtk_widget_grab_focus(custom_attr_window
.attr_list
);
421 static void custom_attr_window_del_attr_cb(GtkWidget
*widget
,
424 custom_attr_popup_delete(NULL
, NULL
);
425 gtk_widget_grab_focus(custom_attr_window
.attr_list
);
428 static gboolean
custom_attr_window_key_pressed(GtkWidget
*widget
,
429 GdkEventKey
*event
, gpointer data
)
431 if (event
&& event
->keyval
== GDK_KEY_Escape
)
432 custom_attr_window_close();
433 else if (event
&& event
->keyval
== GDK_KEY_Delete
)
434 custom_attr_popup_delete(NULL
, NULL
);
438 static gboolean
custom_attr_window_add_key_pressed(GtkWidget
*widget
,
439 GdkEventKey
*event
, gpointer data
)
441 if (event
&& (event
->keyval
== GDK_KEY_KP_Enter
|| event
->keyval
== GDK_KEY_Return
)) {
442 custom_attr_window_add_attr();
443 gtk_entry_set_text(GTK_ENTRY(custom_attr_window
.add_entry
), "");
444 gtk_widget_grab_focus(custom_attr_window
.attr_list
);
449 static void custom_attr_window_create(void)
456 GtkWidget
*attr_list
;
457 GtkWidget
*cancel_btn
;
459 GtkWidget
*scrolledwin
;
460 GtkWidget
*new_attr_label
;
461 GtkWidget
*new_attr_entry
;
465 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "custom_attr_edit_window");
466 gtk_window_set_title (GTK_WINDOW(window
),
467 C_("Dialog title", "Edit attribute names"));
469 gtk_container_set_border_width (GTK_CONTAINER (window
), 8);
470 gtk_window_set_position (GTK_WINDOW (window
), GTK_WIN_POS_CENTER
);
471 gtk_window_set_resizable(GTK_WINDOW (window
), TRUE
);
472 g_signal_connect(G_OBJECT(window
), "delete_event",
473 G_CALLBACK(custom_attr_window_cancel_cb
), NULL
);
474 g_signal_connect(G_OBJECT(window
), "key_press_event",
475 G_CALLBACK(custom_attr_window_key_pressed
), NULL
);
476 MANAGE_WINDOW_SIGNALS_CONNECT (window
);
478 vbox1
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
479 hbox1
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
481 new_attr_label
= gtk_label_new(_("New attribute name:"));
482 gtk_label_set_xalign(GTK_LABEL(new_attr_label
), 0.0);
483 gtk_box_pack_start(GTK_BOX(hbox1
), new_attr_label
, FALSE
, FALSE
, 0);
485 new_attr_entry
= gtk_entry_new();
486 gtk_box_pack_start(GTK_BOX(hbox1
), new_attr_entry
, FALSE
, FALSE
, 0);
487 g_signal_connect(G_OBJECT(new_attr_entry
), "key_press_event",
488 G_CALLBACK(custom_attr_window_add_key_pressed
), NULL
);
490 add_btn
= gtkut_stock_button("list-add", _("_Add"));
491 gtk_box_pack_start(GTK_BOX(hbox1
), add_btn
, FALSE
, FALSE
, 0);
493 del_btn
= gtkut_stock_button("edit-delete", _("D_elete"));
494 gtk_box_pack_start(GTK_BOX(hbox1
), del_btn
, FALSE
, FALSE
, 0);
496 gtkut_stock_button_set_create(&hbox2
, &cancel_btn
, NULL
, _("_Cancel"),
497 &ok_btn
, NULL
, _("_OK"),
500 gtk_widget_show(new_attr_label
);
501 gtk_widget_show(new_attr_entry
);
502 gtk_widget_show(add_btn
);
503 gtk_widget_show(del_btn
);
504 gtk_widget_show(cancel_btn
);
505 gtk_widget_show(ok_btn
);
507 g_signal_connect(G_OBJECT(cancel_btn
), "clicked",
508 G_CALLBACK(custom_attr_window_cancel_cb
), NULL
);
509 g_signal_connect(G_OBJECT(ok_btn
), "clicked",
510 G_CALLBACK(custom_attr_window_ok_cb
), NULL
);
511 g_signal_connect(G_OBJECT(add_btn
), "clicked",
512 G_CALLBACK(custom_attr_window_add_attr_cb
), NULL
);
513 g_signal_connect(G_OBJECT(del_btn
), "clicked",
514 G_CALLBACK(custom_attr_window_del_attr_cb
), NULL
);
516 attr_list
= custom_attr_window_list_view_create();
518 label
= gtk_label_new(_("Adding or removing attribute names won't "
519 "affect attributes already set for contacts."));
520 gtk_widget_set_size_request(GTK_WIDGET(label
), 380, -1);
521 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
522 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
523 gtk_box_pack_start(GTK_BOX(vbox1
), label
, FALSE
, TRUE
, 0);
525 scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
526 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin
),
527 GTK_POLICY_AUTOMATIC
, GTK_POLICY_ALWAYS
);
529 gtk_widget_set_size_request(scrolledwin
, 400, 250);
531 gtk_container_add(GTK_CONTAINER(scrolledwin
), attr_list
);
532 gtk_box_pack_start(GTK_BOX(vbox1
), scrolledwin
, TRUE
, TRUE
, 0);
533 gtk_box_pack_start(GTK_BOX(vbox1
), hbox1
, FALSE
, FALSE
, 0);
534 gtk_box_pack_start(GTK_BOX(vbox1
), hbox2
, FALSE
, FALSE
, 0);
536 gtk_widget_show(label
);
537 gtk_widget_show(scrolledwin
);
538 gtk_widget_show(attr_list
);
539 gtk_widget_show(hbox2
);
540 gtk_widget_show(hbox1
);
541 gtk_widget_show(vbox1
);
542 gtk_container_add(GTK_CONTAINER (window
), vbox1
);
544 custom_attr_window
.window
= window
;
545 custom_attr_window
.hbox1
= hbox1
;
546 custom_attr_window
.hbox2
= hbox2
;
547 custom_attr_window
.vbox1
= vbox1
;
548 custom_attr_window
.label
= label
;
549 custom_attr_window
.attr_list
= attr_list
;
550 custom_attr_window
.cancel_btn
= cancel_btn
;
551 custom_attr_window
.ok_btn
= ok_btn
;
552 custom_attr_window
.add_btn
= add_btn
;
553 custom_attr_window
.add_entry
= new_attr_entry
;
556 static void custom_attr_window_load_list (GList
*list
)
558 /* copy attribute names list from prefs to store */
561 GtkListStore
*list_store
= GTK_LIST_STORE(gtk_tree_view_get_model
562 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)));
564 custom_attr_window_list_view_clear_list(custom_attr_window
.attr_list
, FALSE
);
568 gtk_list_store_append(list_store
, &iter
);
569 gtk_list_store_set(list_store
, &iter
,
570 CUSTOM_ATTR_NAME
, cur
->data
,
576 static GList
*store_to_glist
= NULL
;
578 static gboolean
custom_attr_store_to_glist (GtkTreeModel
*model
,
585 gtk_tree_model_get(model
, iter
, CUSTOM_ATTR_NAME
, &attr
, -1);
587 store_to_glist
= g_list_prepend(store_to_glist
, g_strdup(attr
));
593 static void custom_attr_window_save_list (void)
597 /* clear existing attribute names list in prefs */
598 cur
= prefs_common
.addressbook_custom_attributes
;
603 g_list_free(prefs_common
.addressbook_custom_attributes
);
605 /* copy attribute names list from store to prefs */
606 gtk_tree_model_foreach(gtk_tree_view_get_model
607 (GTK_TREE_VIEW(custom_attr_window
.attr_list
)),
608 (GtkTreeModelForeachFunc
) custom_attr_store_to_glist
,
610 prefs_common
.addressbook_custom_attributes
= g_list_reverse(store_to_glist
);
611 store_to_glist
= NULL
;
614 static GList
*custom_attr_default_list(void)
616 /* returned GList must be deallocated by caller */
622 while (default_addressbook_attributes_table
[i
]) {
623 list
= g_list_prepend(
624 list
, g_strdup(gettext(default_addressbook_attributes_table
[i
])));
627 list
= g_list_reverse(list
);
631 GList
*addressbook_update_custom_attr_from_prefs(void)
633 /* load addressbook custom attribute names list from file */
634 /* use a list of default attribute names if storage file doesn't exist */
636 GList
*default_attr_list
;
639 /* load table into glist */
640 default_attr_list
= custom_attr_default_list();
642 list
= prefs_common_read_history_from_dir_with_defaults(ADDRBOOK_DIR
,
643 ADDRESSBOOK_CUSTOM_ATTRIBUTES
,
646 /* free glist if it's the one we return (the default one) */
647 if (list
!= default_attr_list
) {
648 cur
= default_attr_list
;
653 g_list_free(default_attr_list
);