2 * att_remover -- for Claws Mail
4 * Copyright (C) 2005 Colin Leroy <colin@colino.net>
6 * Sylpheed is a GTK+ based, lightweight, and fast e-mail client
7 * Copyright (C) 1999-2005 Hiroyuki Yamamoto and the Claws Mail Team
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 #include "claws-features.h"
30 #include <glib/gi18n.h>
34 #include <gdk/gdkkeysyms.h>
37 #include "mainwindow.h"
38 #include "summaryview.h"
41 #include "summaryview.h"
43 #include "alertpanel.h"
48 #include "prefs_common.h"
50 #include "prefs_gtk.h"
52 #define PREFS_BLOCK_NAME "AttRemover"
54 static struct _AttRemover {
62 typedef struct _AttRemover AttRemover;
64 static PrefParam prefs[] = {
65 {"win_width", "-1", &AttRemoverData.win_width, P_INT, NULL,
67 {"win_height", "-1", &AttRemoverData.win_height, P_INT, NULL,
78 static MimeInfo *find_first_text_part(MimeInfo *partinfo)
80 while (partinfo && partinfo->type != MIMETYPE_TEXT) {
81 partinfo = procmime_mimeinfo_next(partinfo);
87 static gboolean key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
88 AttRemover *attremover)
90 if (event && event->keyval == GDK_Escape)
91 gtk_widget_destroy(attremover->window);
96 static void cancelled_event_cb(GtkWidget *widget, AttRemover *attremover)
98 gtk_widget_destroy(attremover->window);
101 static void size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation)
103 cm_return_if_fail(allocation != NULL);
105 AttRemoverData.win_width = allocation->width;
106 AttRemoverData.win_height = allocation->height;
109 static gint save_new_message(MsgInfo *oldmsg, MsgInfo *newmsg, MimeInfo *info,
110 gboolean has_attachment)
113 FolderItem *item = oldmsg->folder;
114 MsgFlags flags = {0, 0};
117 finalmsg = procmsg_msginfo_new_from_mimeinfo(newmsg, info);
119 procmsg_msginfo_free(newmsg);
123 debug_print("finalmsg %s\n", finalmsg->plaintext_file);
125 flags.tmp_flags = oldmsg->flags.tmp_flags;
126 flags.perm_flags = oldmsg->flags.perm_flags;
129 flags.tmp_flags &= ~MSG_HAS_ATTACHMENT;
131 oldmsg->flags.perm_flags &= ~MSG_LOCKED;
132 folder_item_remove_msg(item, oldmsg->msgnum);
133 msgnum = folder_item_add_msg(item, finalmsg->plaintext_file,
135 finalmsg->msgnum = msgnum;
136 procmsg_msginfo_free(newmsg);
137 procmsg_msginfo_free(finalmsg);
139 newmsg = folder_item_get_msginfo(item, msgnum);
140 if (newmsg && item) {
141 procmsg_msginfo_unset_flags(newmsg, ~0, ~0);
142 procmsg_msginfo_set_flags(newmsg, flags.perm_flags, flags.tmp_flags);
143 procmsg_msginfo_free(newmsg);
149 #define MIMEINFO_NOT_ATTACHMENT(m) \
150 ((m)->type == MIMETYPE_MESSAGE || (m)->type == MIMETYPE_MULTIPART)
152 static void remove_attachments_cb(GtkWidget *widget, AttRemover *attremover)
154 MainWindow *mainwin = mainwindow_get_mainwindow();
155 SummaryView *summaryview = mainwin->summaryview;
156 GtkTreeModel *model = attremover->model;
159 MimeInfo *info, *parent, *last, *partinfo;
161 gint att_all = 0, att_removed = 0, msgnum;
162 gboolean to_removal, iter_valid=TRUE;
164 newmsg = procmsg_msginfo_copy(attremover->msginfo);
165 info = procmime_scan_message(newmsg);
167 last = partinfo = find_first_text_part(info);
168 partinfo = procmime_mimeinfo_next(partinfo);
169 if (!partinfo || !gtk_tree_model_get_iter_first(model, &iter)) {
170 gtk_widget_destroy(attremover->window);
171 procmsg_msginfo_free(newmsg);
175 main_window_cursor_wait(mainwin);
176 gtk_cmclist_freeze(GTK_CMCLIST(summaryview->ctree));
177 folder_item_update_freeze();
180 while (partinfo && iter_valid) {
181 if (MIMEINFO_NOT_ATTACHMENT(partinfo)) {
183 partinfo = procmime_mimeinfo_next(partinfo);
188 gtk_tree_model_get(model, &iter, ATT_REMOVER_TOGGLE,
192 partinfo = procmime_mimeinfo_next(partinfo);
193 iter_valid = gtk_tree_model_iter_next(model, &iter);
198 partinfo = procmime_mimeinfo_next(partinfo);
199 iter_valid = gtk_tree_model_iter_next(model, &iter);
201 g_node_destroy(parent->node);
207 if (!(parent = procmime_mimeinfo_parent(partinfo)))
210 /* multipart/{alternative,mixed,related} make sense
211 only when they have at least 2 nodes, remove last
212 one and move it one level up if otherwise */
213 if (MIMEINFO_NOT_ATTACHMENT(partinfo) &&
214 g_node_n_children(partinfo->node) < 2)
216 gint pos = g_node_child_position(parent->node, partinfo->node);
217 g_node_unlink(partinfo->node);
219 if ((child = g_node_first_child(partinfo->node))) {
220 g_node_unlink(child);
221 g_node_insert(parent->node, pos, child);
223 g_node_destroy(partinfo->node);
225 child = g_node_last_child(parent->node);
226 partinfo = child ? child->data : parent;
230 if (partinfo->node->prev) {
231 partinfo = (MimeInfo *) partinfo->node->prev->data;
232 if (partinfo->node->children) {
233 child = g_node_last_child(partinfo->node);
234 partinfo = (MimeInfo *) child->data;
236 } else if (partinfo->node->parent)
237 partinfo = (MimeInfo *) partinfo->node->parent->data;
240 msgnum = save_new_message(attremover->msginfo, newmsg, info,
241 (att_all - att_removed > 0));
244 folder_item_update_thaw();
245 gtk_cmclist_thaw(GTK_CMCLIST(summaryview->ctree));
246 main_window_cursor_normal(mainwin);
249 summary_select_by_msgnum(summaryview, msgnum);
251 gtk_widget_destroy(attremover->window);
254 static void remove_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str,
257 GtkTreeModel *model = (GtkTreeModel *)data;
259 GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
262 gtk_tree_model_get_iter (model, &iter, path);
263 gtk_tree_model_get (model, &iter, ATT_REMOVER_TOGGLE, &fixed, -1);
266 gtk_list_store_set (GTK_LIST_STORE (model), &iter, ATT_REMOVER_TOGGLE, fixed, -1);
268 gtk_tree_path_free (path);
271 static void fill_attachment_store(GtkTreeView *list_view, MimeInfo *partinfo)
274 gchar *label, *content_type;
276 GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model
277 (GTK_TREE_VIEW(list_view)));
279 partinfo = find_first_text_part(partinfo);
280 partinfo = procmime_mimeinfo_next(partinfo);
285 if (MIMEINFO_NOT_ATTACHMENT(partinfo)) {
286 partinfo = procmime_mimeinfo_next(partinfo);
290 content_type = procmime_get_content_type_str(
291 partinfo->type, partinfo->subtype);
293 name = procmime_mimeinfo_get_parameter(partinfo, "filename");
295 name = procmime_mimeinfo_get_parameter(partinfo, "name");
299 label = g_strconcat(_("<b>Type: </b>"), content_type, " ",
300 _("<b>Size: </b>"), to_human_readable((goffset)partinfo->length),
301 "\n", _("<b>Filename: </b>"), name, NULL);
303 gtk_list_store_append(list_store, &iter);
304 gtk_list_store_set(list_store, &iter,
305 ATT_REMOVER_INFO, label,
306 ATT_REMOVER_TOGGLE, TRUE,
309 g_free(content_type);
310 partinfo = procmime_mimeinfo_next(partinfo);
314 static void remove_attachments_dialog(AttRemover *attremover)
318 GtkTreeView *list_view;
320 GtkTreeViewColumn *column;
321 GtkCellRenderer *renderer;
322 GtkWidget *scrollwin;
325 GtkWidget *cancel_btn;
326 MimeInfo *info = procmime_scan_message(attremover->msginfo);
328 static GdkGeometry geometry;
330 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "AttRemover");
331 gtk_container_set_border_width( GTK_CONTAINER(window), VBOX_BORDER);
332 gtk_window_set_title(GTK_WINDOW(window), _("Remove attachments"));
333 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
334 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
336 g_signal_connect(G_OBJECT(window), "delete_event",
337 G_CALLBACK(cancelled_event_cb), attremover);
338 g_signal_connect(G_OBJECT(window), "key_press_event",
339 G_CALLBACK(key_pressed_cb), attremover);
340 g_signal_connect(G_OBJECT(window), "size_allocate",
341 G_CALLBACK(size_allocate_cb), NULL);
343 vbox = gtk_vbox_new(FALSE, VBOX_BORDER);
344 gtk_container_add(GTK_CONTAINER(window), vbox);
346 model = GTK_TREE_MODEL(gtk_list_store_new(N_ATT_REMOVER_COLUMNS,
350 list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
351 g_object_unref(model);
352 gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
354 renderer = gtk_cell_renderer_toggle_new();
355 g_signal_connect(renderer, "toggled", G_CALLBACK(remove_toggled_cb), model);
356 column = gtk_tree_view_column_new_with_attributes
359 "active", ATT_REMOVER_TOGGLE,
361 gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
363 renderer = gtk_cell_renderer_text_new();
364 column = gtk_tree_view_column_new_with_attributes
367 "markup", ATT_REMOVER_INFO,
369 gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
370 fill_attachment_store(list_view, info);
372 scrollwin = gtk_scrolled_window_new(NULL, NULL);
373 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin),
374 GTK_SHADOW_ETCHED_OUT);
375 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
376 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
377 gtk_container_add(GTK_CONTAINER(scrollwin), GTK_WIDGET(list_view));
378 gtk_container_set_border_width(GTK_CONTAINER(scrollwin), 4);
379 gtk_box_pack_start(GTK_BOX(vbox), scrollwin, TRUE, TRUE, 0);
381 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
382 &ok_btn, GTK_STOCK_OK,
384 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
385 gtk_container_set_border_width(GTK_CONTAINER(hbbox), HSPACING_NARROW);
386 gtk_widget_grab_default(ok_btn);
388 g_signal_connect(G_OBJECT(ok_btn), "clicked",
389 G_CALLBACK(remove_attachments_cb), attremover);
390 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
391 G_CALLBACK(cancelled_event_cb), attremover);
393 if (!geometry.min_height) {
394 geometry.min_width = 450;
395 geometry.min_height = 350;
398 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
400 gtk_widget_set_size_request(window, attremover->win_width,
401 attremover->win_height);
403 attremover->window = window;
404 attremover->model = model;
406 gtk_widget_show_all(window);
407 gtk_widget_grab_focus(ok_btn);
410 static void remove_attachments(GSList *msglist)
412 MainWindow *mainwin = mainwindow_get_mainwindow();
413 SummaryView *summaryview = mainwin->summaryview;
417 if (alertpanel_full(_("Destroy attachments"),
418 _("Do you really want to remove all attachments from "
419 "the selected messages?\n\n"
420 "The deleted data will be unrecoverable."),
421 GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL,
422 FALSE, NULL, ALERT_QUESTION, G_ALERTALTERNATE) != G_ALERTALTERNATE)
425 main_window_cursor_wait(summaryview->mainwin);
426 gtk_cmclist_freeze(GTK_CMCLIST(summaryview->ctree));
427 folder_item_update_freeze();
430 for (cur = msglist; cur; cur = cur->next) {
431 MsgInfo *msginfo = (MsgInfo *)cur->data;
432 MsgInfo *newmsg = NULL;
433 MimeInfo *info = NULL;
434 MimeInfo *partinfo = NULL;
439 newmsg = procmsg_msginfo_copy(msginfo);
440 info = procmime_scan_message(newmsg);
442 if ( !(partinfo = find_first_text_part(info)) ) {
443 procmsg_msginfo_free(newmsg);
446 partinfo->node->next = NULL;
447 partinfo->node->children = NULL;
448 info->node->children->data = partinfo;
450 msgnum = save_new_message(msginfo, newmsg, info, FALSE);
454 folder_item_update_thaw();
455 gtk_cmclist_thaw(GTK_CMCLIST(summaryview->ctree));
456 main_window_cursor_normal(summaryview->mainwin);
459 summary_select_by_msgnum(summaryview, msgnum);
463 static void remove_attachments_ui(GtkAction *action, gpointer data)
465 MainWindow *mainwin = mainwindow_get_mainwindow();
466 GSList *msglist = summary_get_selected_msg_list(mainwin->summaryview);
467 MimeInfo *info = NULL, *partinfo = NULL;
469 if (summary_is_locked(mainwin->summaryview) || !msglist)
472 if (g_slist_length(msglist) == 1) {
473 info = procmime_scan_message(msglist->data);
475 partinfo = find_first_text_part(info);
476 partinfo = procmime_mimeinfo_next(partinfo);
479 alertpanel_notice(_("This message doesn't have any attachments."));
480 g_slist_free(msglist);
484 AttRemoverData.msginfo = msglist->data;
485 remove_attachments_dialog(&AttRemoverData);
487 remove_attachments(msglist);
489 g_slist_free(msglist);
492 static GtkActionEntry remove_att_main_menu[] = {{
494 NULL, N_("Remove attachments..."), NULL, NULL, G_CALLBACK(remove_attachments_ui)
497 static guint context_menu_id = 0;
498 static guint main_menu_id = 0;
500 gint plugin_init(gchar **error)
502 MainWindow *mainwin = mainwindow_get_mainwindow();
505 if( !check_plugin_version(MAKE_NUMERIC_VERSION(3,6,1,27),
506 VERSION_NUMERIC, _("AttRemover"), error) )
509 gtk_action_group_add_actions(mainwin->action_group, remove_att_main_menu,
510 1, (gpointer)mainwin);
511 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Message", "RemoveAtt",
512 "Message/RemoveAtt", GTK_UI_MANAGER_MENUITEM,
514 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menus/SummaryViewPopup", "RemoveAtt",
515 "Message/RemoveAtt", GTK_UI_MANAGER_MENUITEM,
518 prefs_set_default(prefs);
519 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
520 prefs_read_config(prefs, PREFS_BLOCK_NAME, rcpath, NULL);
526 gboolean plugin_done(void)
528 MainWindow *mainwin = mainwindow_get_mainwindow();
532 rc_file_path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
534 pref_file = prefs_write_open(rc_file_path);
535 g_free(rc_file_path);
537 if (!pref_file || prefs_set_block_label(pref_file, PREFS_BLOCK_NAME) < 0)
540 if (prefs_write_param(prefs, pref_file->fp) < 0) {
541 g_warning("failed to write AttRemover Plugin configuration\n");
542 prefs_file_close_revert(pref_file);
546 if (fprintf(pref_file->fp, "\n") < 0) {
547 FILE_OP_ERROR(rc_file_path, "fprintf");
548 prefs_file_close_revert(pref_file);
550 prefs_file_close(pref_file);
555 MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "Message/RemoveAtt", main_menu_id);
558 MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "Message/RemoveAtt", context_menu_id);
564 const gchar *plugin_name(void)
566 return _("AttRemover");
569 const gchar *plugin_desc(void)
571 return _("This plugin removes attachments from mails.\n\n"
572 "Warning: this operation will be completely "
573 "un-cancellable and the deleted attachments will "
574 "be lost forever, and ever, and ever.");
577 const gchar *plugin_type(void)
582 const gchar *plugin_licence(void)
587 const gchar *plugin_version(void)
592 struct PluginFeature *plugin_provides(void)
594 static struct PluginFeature features[] =
595 { {PLUGIN_UTILITY, N_("Attachment handling")},
596 {PLUGIN_NOTHING, NULL}};