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/>.
21 #include "claws-features.h"
27 #include <glib/gi18n.h>
28 #include <gdk/gdkkeysyms.h>
30 #include "addressbook.h"
31 #include "addressitem.h"
37 #include "prefs_common.h"
39 #include "alertpanel.h"
40 #include "inputdialog.h"
41 #include "manage_window.h"
44 #define ADDRESSBOOK_GUESS_FOLDER_NAME "NewFolder"
45 #define ADDRESSBOOK_GUESS_GROUP_NAME "NewGroup"
51 } GroupEditEMailColumnPos
;
53 #define GROUP_N_COLS 3
54 #define GROUP_COL_WIDTH_NAME 140
55 #define GROUP_COL_WIDTH_EMAIL 120
57 static struct _GroupEdit_dlg
{
60 GtkWidget
*cancel_btn
;
65 GtkWidget
*entry_name
;
66 GtkCMCList
*clist_group
;
67 GtkCMCList
*clist_avail
;
69 GHashTable
*hashEMail
;
73 static void edit_group_status_show( gchar
*msg
) {
74 if( groupeditdlg
.statusbar
!= NULL
) {
75 gtk_statusbar_pop( GTK_STATUSBAR(groupeditdlg
.statusbar
), groupeditdlg
.status_cid
);
77 gtk_statusbar_push( GTK_STATUSBAR(groupeditdlg
.statusbar
), groupeditdlg
.status_cid
, msg
);
82 static void edit_group_ok(GtkWidget
*widget
, gboolean
*cancelled
) {
84 gboolean errFlag
= TRUE
;
86 sName
= gtk_editable_get_chars( GTK_EDITABLE(groupeditdlg
.entry_name
), 0, -1 );
89 if( *sName
!= '\0' ) {
90 gtk_entry_set_text(GTK_ENTRY(groupeditdlg
.entry_name
), sName
);
97 edit_group_status_show( _( "A Group Name must be supplied." ) );
102 static void edit_group_cancel(GtkWidget
*widget
, gboolean
*cancelled
) {
107 static gint
edit_group_delete_event(GtkWidget
*widget
, GdkEventAny
*event
, gboolean
*cancelled
) {
113 static gboolean
edit_group_key_pressed(GtkWidget
*widget
, GdkEventKey
*event
, gboolean
*cancelled
) {
114 if (event
&& event
->keyval
== GDK_KEY_Escape
) {
121 static gchar
*edit_group_format_item_clist( ItemPerson
*person
, ItemEMail
*email
) {
123 gchar
*aName
= ADDRITEM_NAME(email
);
124 if( aName
== NULL
|| *aName
== '\0' ) return str
;
126 str
= g_strdup_printf( "%s - %s", ADDRITEM_NAME(person
), aName
);
129 str
= g_strdup( aName
);
134 static gint
edit_group_clist_add_email( GtkCMCList
*clist
, ItemEMail
*email
) {
135 ItemPerson
*person
= ( ItemPerson
* ) ADDRITEM_PARENT(email
);
136 gchar
*str
= edit_group_format_item_clist( person
, email
);
137 gchar
*text
[ GROUP_N_COLS
];
140 text
[ GROUP_COL_NAME
] = addressbook_set_col_name_guard(str
);
143 text
[ GROUP_COL_NAME
] = addressbook_set_col_name_guard(ADDRITEM_NAME(person
));
145 text
[ GROUP_COL_EMAIL
] = email
->address
;
146 text
[ GROUP_COL_REMARKS
] = email
->remarks
;
148 row
= gtk_cmclist_append( clist
, text
);
149 gtk_cmclist_set_row_data( clist
, row
, email
);
153 static void edit_group_load_clist( GtkCMCList
*clist
, GList
*listEMail
) {
154 GList
*node
= listEMail
;
155 gtk_cmclist_freeze(clist
);
157 ItemEMail
*email
= node
->data
;
158 edit_group_clist_add_email( clist
, email
);
159 node
= g_list_next( node
);
161 gtk_cmclist_thaw(clist
);
165 static void edit_group_move_email( GtkCMCList
*clist_from
, GtkCMCList
*clist_to
, GtkCMCTreeNode
*node
) {
166 ItemEMail
*email
= gtk_cmctree_node_get_row_data( GTK_CMCTREE(clist_from
), node
);
167 GtkCMCTreeNode
*next
= gtkut_ctree_node_next(GTK_CMCTREE(clist_from
), node
);
168 GtkCMCTreeNode
*prev
= gtkut_ctree_node_prev(GTK_CMCTREE(clist_from
), node
);
171 gtk_cmctree_remove_node(GTK_CMCTREE(clist_from
), node
);
172 rrow
= edit_group_clist_add_email( clist_to
, email
);
173 gtk_cmclist_select_row( clist_to
, rrow
, 0 );
175 gtk_cmctree_select(GTK_CMCTREE(clist_from
), next
);
177 gtk_cmctree_select(GTK_CMCTREE(clist_from
), prev
);
181 static void edit_group_to_group( GtkWidget
*widget
, gpointer data
) {
182 GList
*selected
= g_list_copy(GTK_CMCLIST(groupeditdlg
.clist_avail
)->selection
);
183 /* Clear the selected rows on destination clist */
184 gtk_cmclist_freeze(groupeditdlg
.clist_avail
);
185 gtk_cmclist_freeze(groupeditdlg
.clist_group
);
186 gtk_cmclist_unselect_all(groupeditdlg
.clist_group
);
188 edit_group_move_email( groupeditdlg
.clist_avail
,
189 groupeditdlg
.clist_group
, GTK_CMCTREE_NODE(selected
->data
) );
190 selected
= selected
->next
;
192 g_list_free(selected
);
193 gtk_cmclist_thaw(groupeditdlg
.clist_avail
);
194 gtk_cmclist_thaw(groupeditdlg
.clist_group
);
197 static void edit_group_to_avail( GtkWidget
*widget
, gpointer data
) {
198 GList
*selected
= g_list_copy(GTK_CMCLIST(groupeditdlg
.clist_group
)->selection
);
199 gtk_cmclist_freeze(groupeditdlg
.clist_avail
);
200 gtk_cmclist_freeze(groupeditdlg
.clist_group
);
201 gtk_cmclist_unselect_all(groupeditdlg
.clist_avail
);
203 edit_group_move_email( groupeditdlg
.clist_group
,
204 groupeditdlg
.clist_avail
, GTK_CMCTREE_NODE(selected
->data
) );
205 selected
= selected
->next
;
207 g_list_free(selected
);
208 gtk_cmclist_thaw(groupeditdlg
.clist_avail
);
209 gtk_cmclist_thaw(groupeditdlg
.clist_group
);
212 static gboolean
edit_group_list_group_button( GtkCMCList
*clist
, GdkEventButton
*event
, gpointer data
) {
213 if( ! event
) return FALSE
;
214 if( event
->window
!= clist
->clist_window
) return FALSE
;
216 if( event
->button
== 1 ) {
217 if( event
->type
== GDK_2BUTTON_PRESS
) {
218 edit_group_to_avail( NULL
, NULL
);
224 static gboolean
edit_group_list_avail_button( GtkCMCList
*clist
, GdkEventButton
*event
, gpointer data
) {
225 if( ! event
) return FALSE
;
226 if( event
->window
!= clist
->clist_window
) return FALSE
;
228 if( event
->button
== 1 ) {
229 if( event
->type
== GDK_2BUTTON_PRESS
) {
230 edit_group_to_group( NULL
, NULL
);
236 static gint
edit_group_list_compare_func( GtkCMCList
*clist
, gconstpointer ptr1
, gconstpointer ptr2
) {
237 GtkCMCell
*cell1
= ((GtkCMCListRow
*)ptr1
)->cell
;
238 GtkCMCell
*cell2
= ((GtkCMCListRow
*)ptr2
)->cell
;
239 gchar
*name1
= NULL
, *name2
= NULL
;
240 if( cell1
) name1
= cell1
->u
.text
;
241 if( cell2
) name2
= cell2
->u
.text
;
242 if( ! name1
) return ( name2
!= NULL
);
243 if( ! name2
) return -1;
244 return g_utf8_collate( name1
, name2
);
247 static void addressbook_edit_group_size_allocate_cb(GtkWidget
*widget
,
248 GtkAllocation
*allocation
)
250 cm_return_if_fail(allocation
!= NULL
);
252 gtk_window_get_size(GTK_WINDOW(widget
),
253 &prefs_common
.addressbookeditgroupwin_width
, &prefs_common
.addressbookeditgroupwin_height
);
256 static void addressbook_edit_group_create(gboolean
*cancelled
)
262 GtkWidget
*cancel_btn
;
264 GtkWidget
*statusbar
;
269 GtkWidget
*entry_name
;
274 GtkWidget
*clist_swin
;
275 GtkWidget
*clist_group
;
276 GtkWidget
*clist_avail
;
278 GtkWidget
*buttonGroup
;
279 GtkWidget
*buttonAvail
;
281 gchar
*titles
[ GROUP_N_COLS
];
284 static GdkGeometry geometry
;
286 titles
[ GROUP_COL_NAME
] = _( "Name" );
287 titles
[ GROUP_COL_EMAIL
] = _("Email Address");
288 titles
[ GROUP_COL_REMARKS
] = _("Remarks");
290 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "editgroup");
291 gtk_container_set_border_width(GTK_CONTAINER(window
), 0);
292 gtk_window_set_title(GTK_WINDOW(window
), _("Edit group data"));
293 gtk_window_set_position(GTK_WINDOW(window
), GTK_WIN_POS_CENTER
);
294 gtk_window_set_type_hint(GTK_WINDOW(window
), GDK_WINDOW_TYPE_HINT_DIALOG
);
295 g_signal_connect(G_OBJECT(window
), "delete_event",
296 G_CALLBACK(edit_group_delete_event
),
298 g_signal_connect(G_OBJECT(window
), "key_press_event",
299 G_CALLBACK(edit_group_key_pressed
),
301 g_signal_connect(G_OBJECT(window
), "size_allocate",
302 G_CALLBACK(addressbook_edit_group_size_allocate_cb
), NULL
);
304 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6 );
305 gtk_container_set_border_width(GTK_CONTAINER(vbox
), BORDER_WIDTH
);
306 gtk_widget_show( vbox
);
307 gtk_container_add( GTK_CONTAINER( window
), vbox
);
310 hboxg
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0 );
311 gtk_box_pack_start(GTK_BOX(vbox
), hboxg
, FALSE
, FALSE
, 0);
313 /* Data entry area */
314 table
= gtk_grid_new();
315 gtk_box_pack_start(GTK_BOX(hboxg
), table
, TRUE
, TRUE
, 0);
316 gtk_container_set_border_width( GTK_CONTAINER(table
), 4 );
317 gtk_grid_set_row_spacing(GTK_GRID(table
), 0);
318 gtk_grid_set_column_spacing(GTK_GRID(table
), 4);
321 label
= gtk_label_new(_("Group Name"));
322 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
323 gtk_grid_attach(GTK_GRID(table
), label
, 0, 0, 1, 1);
325 entry_name
= gtk_entry_new();
326 gtk_grid_attach(GTK_GRID(table
), entry_name
, 1, 0, 1, 1);
327 gtk_widget_set_hexpand(entry_name
, TRUE
);
328 gtk_widget_set_halign(entry_name
, GTK_ALIGN_FILL
);
331 hboxl
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6 );
332 gtk_container_set_border_width( GTK_CONTAINER(hboxl
), 8 );
333 gtk_box_pack_start(GTK_BOX(vbox
), hboxl
, TRUE
, TRUE
, 0);
336 vboxl
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0 );
337 gtk_box_pack_start(GTK_BOX(hboxl
), vboxl
, TRUE
, TRUE
, 0);
339 hboxh
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0 );
340 gtk_container_set_border_width( GTK_CONTAINER(hboxh
), 4 );
341 gtk_box_pack_start(GTK_BOX(vboxl
), hboxh
, FALSE
, FALSE
, 0);
342 label
= gtk_label_new(_("Available Addresses"));
343 gtk_box_pack_start(GTK_BOX(hboxh
), label
, TRUE
, TRUE
, 0);
344 buttonGroup
= gtkut_stock_button("list-add", _("_Add"));
345 gtk_box_pack_end(GTK_BOX(hboxh
), buttonGroup
, FALSE
, FALSE
, 0);
347 clist_swin
= gtk_scrolled_window_new( NULL
, NULL
);
348 gtk_box_pack_start(GTK_BOX(vboxl
), clist_swin
, TRUE
, TRUE
, 0);
349 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin
),
350 GTK_POLICY_AUTOMATIC
,
351 GTK_POLICY_AUTOMATIC
);
353 clist_avail
= gtk_sctree_new_with_titles( GROUP_N_COLS
, 0, titles
);
354 gtk_container_add( GTK_CONTAINER(clist_swin
), clist_avail
);
355 gtk_cmctree_set_expander_style(GTK_CMCTREE(clist_avail
),
356 GTK_CMCTREE_EXPANDER_TRIANGLE
);
357 gtk_cmclist_set_selection_mode( GTK_CMCLIST(clist_avail
), GTK_SELECTION_MULTIPLE
);
358 gtk_cmclist_set_column_width( GTK_CMCLIST(clist_avail
), GROUP_COL_NAME
, GROUP_COL_WIDTH_NAME
);
359 gtk_cmclist_set_column_width( GTK_CMCLIST(clist_avail
), GROUP_COL_EMAIL
, GROUP_COL_WIDTH_EMAIL
);
360 gtk_cmclist_set_compare_func( GTK_CMCLIST(clist_avail
), edit_group_list_compare_func
);
361 gtk_cmclist_set_auto_sort( GTK_CMCLIST(clist_avail
), TRUE
);
363 for( i
= 0; i
< GROUP_N_COLS
; i
++ )
364 gtk_widget_set_can_focus(GTK_CMCLIST(clist_avail
)->column
[i
].button
, FALSE
);
367 vboxl
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
368 gtk_box_pack_start(GTK_BOX(hboxl
), vboxl
, TRUE
, TRUE
, 0);
370 hboxh
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
371 gtk_container_set_border_width( GTK_CONTAINER(hboxh
), 4);
372 gtk_box_pack_start(GTK_BOX(vboxl
), hboxh
, FALSE
, FALSE
, 0);
373 buttonAvail
= gtkut_stock_button("list-remove", _("_Remove"));
374 gtk_box_pack_start(GTK_BOX(hboxh
), buttonAvail
, FALSE
, FALSE
, 0);
375 label
= gtk_label_new(_("Addresses in Group"));
376 gtk_box_pack_end(GTK_BOX(hboxh
), label
, TRUE
, TRUE
, 0);
378 clist_swin
= gtk_scrolled_window_new( NULL
, NULL
);
379 gtk_box_pack_start(GTK_BOX(vboxl
), clist_swin
, TRUE
, TRUE
, 0);
380 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin
),
381 GTK_POLICY_AUTOMATIC
,
382 GTK_POLICY_AUTOMATIC
);
384 clist_group
= gtk_sctree_new_with_titles(GROUP_N_COLS
, 0, titles
);
385 gtk_container_add(GTK_CONTAINER(clist_swin
), clist_group
);
386 gtk_cmctree_set_expander_style(GTK_CMCTREE(clist_group
),
387 GTK_CMCTREE_EXPANDER_TRIANGLE
);
388 gtk_sctree_set_stripes(GTK_SCTREE(clist_group
), prefs_common
.use_stripes_in_summaries
);
389 gtk_cmclist_set_selection_mode( GTK_CMCLIST(clist_group
), GTK_SELECTION_MULTIPLE
);
390 gtk_cmclist_set_column_width( GTK_CMCLIST(clist_group
), GROUP_COL_NAME
, GROUP_COL_WIDTH_NAME
);
391 gtk_cmclist_set_column_width( GTK_CMCLIST(clist_group
), GROUP_COL_EMAIL
, GROUP_COL_WIDTH_EMAIL
);
392 gtk_cmclist_set_compare_func( GTK_CMCLIST(clist_group
), edit_group_list_compare_func
);
393 gtk_cmclist_set_auto_sort( GTK_CMCLIST(clist_group
), TRUE
);
395 for( i
= 0; i
< GROUP_N_COLS
; i
++ )
396 gtk_widget_set_can_focus(GTK_CMCLIST(clist_group
)->column
[i
].button
, FALSE
);
399 hsbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
400 gtk_box_pack_end(GTK_BOX(vbox
), hsbox
, FALSE
, FALSE
, BORDER_WIDTH
);
401 statusbar
= gtk_statusbar_new();
402 gtk_box_pack_start(GTK_BOX(hsbox
), statusbar
, TRUE
, TRUE
, BORDER_WIDTH
);
405 gtkut_stock_button_set_create(&hbbox
, &cancel_btn
, NULL
, _("_Cancel"),
406 &ok_btn
, NULL
, _("_OK"),
408 gtk_box_pack_end(GTK_BOX(vbox
), hbbox
, FALSE
, FALSE
, 0);
409 gtk_widget_grab_default(ok_btn
);
411 g_signal_connect(G_OBJECT(ok_btn
), "clicked",
412 G_CALLBACK(edit_group_ok
), cancelled
);
413 g_signal_connect(G_OBJECT(cancel_btn
), "clicked",
414 G_CALLBACK(edit_group_cancel
), cancelled
);
416 gtk_widget_show_all(vbox
);
419 g_signal_connect( G_OBJECT(buttonGroup
), "clicked",
420 G_CALLBACK( edit_group_to_group
), NULL
);
421 g_signal_connect( G_OBJECT(buttonAvail
), "clicked",
422 G_CALLBACK( edit_group_to_avail
), NULL
);
423 g_signal_connect(G_OBJECT(clist_avail
), "button_press_event",
424 G_CALLBACK(edit_group_list_avail_button
), NULL
);
425 g_signal_connect(G_OBJECT(clist_group
), "button_press_event",
426 G_CALLBACK(edit_group_list_group_button
), NULL
);
428 if (!geometry
.min_height
) {
429 geometry
.min_width
= 580;
430 geometry
.min_height
= 340;
433 gtk_window_set_geometry_hints(GTK_WINDOW(window
), NULL
, &geometry
,
435 gtk_window_set_default_size(GTK_WINDOW(window
),
436 prefs_common
.addressbookeditgroupwin_width
,
437 prefs_common
.addressbookeditgroupwin_height
);
439 groupeditdlg
.window
= window
;
440 groupeditdlg
.ok_btn
= ok_btn
;
441 groupeditdlg
.cancel_btn
= cancel_btn
;
442 groupeditdlg
.statusbar
= statusbar
;
443 groupeditdlg
.status_cid
= gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar
), "Edit group dialog" );
445 groupeditdlg
.entry_name
= entry_name
;
446 groupeditdlg
.clist_group
= GTK_CMCLIST( clist_group
);
447 groupeditdlg
.clist_avail
= GTK_CMCLIST( clist_avail
);
451 * Return list of email items.
453 static GList
*edit_group_build_email_list() {
454 GtkCMCList
*clist
= GTK_CMCLIST(groupeditdlg
.clist_group
);
455 GList
*listEMail
= NULL
;
458 while( (email
= gtk_cmclist_get_row_data( clist
, row
)) ) {
459 listEMail
= g_list_append( listEMail
, email
);
467 * Enter: abf Address book.
468 * folder Parent folder for group (or NULL if adding to root folder). Argument is
469 * only required for new objects).
470 * group Group to edit, or NULL for a new group object.
471 * Return: Edited object, or NULL if cancelled.
473 ItemGroup
*addressbook_edit_group( AddressBookFile
*abf
, ItemFolder
*parent
, ItemGroup
*group
) {
474 static gboolean cancelled
;
475 GList
*listEMail
= NULL
;
478 if (!groupeditdlg
.window
)
479 addressbook_edit_group_create(&cancelled
);
480 gtk_widget_grab_focus(groupeditdlg
.ok_btn
);
481 gtk_widget_grab_focus(groupeditdlg
.entry_name
);
482 gtk_widget_show(groupeditdlg
.window
);
483 manage_window_set_transient(GTK_WINDOW(groupeditdlg
.window
));
484 gtk_window_set_modal(GTK_WINDOW(groupeditdlg
.window
), TRUE
);
485 /* Clear all fields */
486 edit_group_status_show( "" );
487 gtk_cmclist_clear( GTK_CMCLIST(groupeditdlg
.clist_group
) );
488 gtk_cmclist_clear( GTK_CMCLIST(groupeditdlg
.clist_avail
) );
491 if (ADDRITEM_NAME(group
))
492 gtk_entry_set_text(GTK_ENTRY(groupeditdlg
.entry_name
), ADDRITEM_NAME(group
));
493 edit_group_load_clist(groupeditdlg
.clist_group
, group
->listEMail
);
494 gtk_window_set_title(GTK_WINDOW(groupeditdlg
.window
), _("Edit group details"));
496 gtk_window_set_title(GTK_WINDOW(groupeditdlg
.window
), _("Add new group"));
497 gtk_entry_set_text(GTK_ENTRY(groupeditdlg
.entry_name
), ADDRESSBOOK_GUESS_GROUP_NAME
);
500 listEMail
= addrbook_get_available_email_list( abf
, group
);
501 edit_group_load_clist( groupeditdlg
.clist_avail
, listEMail
);
502 g_list_free( listEMail
);
503 gtk_cmclist_select_row( groupeditdlg
.clist_group
, 0, 0 );
504 gtk_cmclist_select_row( groupeditdlg
.clist_avail
, 0, 0 );
507 gtk_widget_hide( groupeditdlg
.window
);
508 gtk_window_set_modal(GTK_WINDOW(groupeditdlg
.window
), FALSE
);
513 listEMail
= edit_group_build_email_list();
515 /* Update email list */
516 addrbook_update_group_list( abf
, group
, listEMail
);
519 /* Create new person and email list */
520 group
= addrbook_add_group_list( abf
, parent
, listEMail
);
522 name
= gtk_editable_get_chars( GTK_EDITABLE(groupeditdlg
.entry_name
), 0, -1 );
523 addritem_group_set_name( group
, name
);
532 * Enter: abf Address book.
533 * parent Parent folder for folder (or NULL if adding to root folder). Argument is
534 * only required for new objects).
535 * folder Folder to edit, or NULL for a new folder object.
536 * Return: Edited object, or NULL if cancelled.
538 ItemFolder
*addressbook_edit_folder( AddressBookFile
*abf
, ItemFolder
*parent
, ItemFolder
*folder
) {
542 name
= g_strdup( ADDRITEM_NAME(folder
) );
543 name
= input_dialog( _("Edit folder"), _("Input the new name of folder:"), name
);
546 name
= input_dialog( _("New folder"),
547 _("Input the name of new folder:"),
548 _(ADDRESSBOOK_GUESS_FOLDER_NAME
) );
550 if( ! name
) return NULL
;
552 if( *name
== '\0' ) {
557 if( strcmp( name
, ADDRITEM_NAME(folder
) ) == 0 ) {
564 folder
= addrbook_add_new_folder( abf
, parent
);
566 addritem_folder_set_name( folder
, name
);