Improve some sieve-related translations
[claws.git] / src / plugins / att_remover / att_remover.c
blob282c1b19b629428ee17f860417d94b901691f62e
1 /*
2 * att_remover -- for Claws Mail
4 * Copyright (C) 2005-2022 Colin Leroy <colin@colino.net>
5 * and the Claws Mail Team
7 * Claws Mail is a GTK based, lightweight, and fast e-mail client
8 * Copyright (C) 1999-2019 Hiroyuki Yamamoto and the Claws Mail Team
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #include "claws-features.h"
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
33 #include <string.h>
35 #include <gdk/gdkkeysyms.h>
36 #include <gtk/gtk.h>
38 #include "mainwindow.h"
39 #include "summaryview.h"
40 #include "folder.h"
41 #include "version.h"
42 #include "procmime.h"
43 #include "alertpanel.h"
44 #include "inc.h"
45 #include "menu.h"
46 #include "claws.h"
47 #include "plugin.h"
48 #include "prefs_common.h"
49 #include "defs.h"
50 #include "prefs_gtk.h"
52 #define PREFS_BLOCK_NAME "AttRemover"
54 static struct _AttRemover {
55 GtkWidget *window;
56 MsgInfo *msginfo;
57 GtkTreeModel *model;
58 gint win_width;
59 gint win_height;
60 } AttRemoverData;
62 typedef struct _AttRemover AttRemover;
64 static PrefParam prefs[] = {
65 {"win_width", "-1", &AttRemoverData.win_width, P_INT, NULL,
66 NULL, NULL},
67 {"win_height", "-1", &AttRemoverData.win_height, P_INT, NULL,
68 NULL, NULL},
69 {0,0,0,0,0,0,0}
72 enum {
73 ATT_REMOVER_INFO,
74 ATT_REMOVER_TOGGLE,
75 N_ATT_REMOVER_COLUMNS
78 static MimeInfo *find_first_text_part(MimeInfo *partinfo)
80 while (partinfo && partinfo->type != MIMETYPE_TEXT) {
81 partinfo = procmime_mimeinfo_next(partinfo);
84 return partinfo;
87 static gboolean key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
88 AttRemover *attremover)
90 if (event && event->keyval == GDK_KEY_Escape)
91 gtk_widget_destroy(attremover->window);
93 return FALSE;
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 gtk_window_get_size(GTK_WINDOW(widget),
106 &AttRemoverData.win_width, &AttRemoverData.win_height);
109 static gint save_new_message(MsgInfo *oldmsg, MsgInfo *newmsg, MimeInfo *info,
110 gboolean has_attachment)
112 MsgInfo *finalmsg;
113 FolderItem *item = oldmsg->folder;
114 MsgFlags flags = {0, 0};
115 gint msgnum = -1;
117 finalmsg = procmsg_msginfo_new_from_mimeinfo(newmsg, info);
118 if (!finalmsg) {
119 procmsg_msginfo_free(&newmsg);
120 return -1;
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;
128 if (!has_attachment)
129 flags.tmp_flags &= ~MSG_HAS_ATTACHMENT;
131 oldmsg->flags.perm_flags &= ~MSG_LOCKED;
132 msgnum = folder_item_add_msg(item, finalmsg->plaintext_file, &flags, TRUE);
133 if (msgnum < 0) {
134 g_warning("could not add message without attachments");
135 procmsg_msginfo_free(&newmsg);
136 procmsg_msginfo_free(&finalmsg);
137 return msgnum;
139 folder_item_remove_msg(item, oldmsg->msgnum);
140 finalmsg->msgnum = msgnum;
141 procmsg_msginfo_free(&newmsg);
142 procmsg_msginfo_free(&finalmsg);
144 newmsg = folder_item_get_msginfo(item, msgnum);
145 if (newmsg && item) {
146 procmsg_msginfo_unset_flags(newmsg, ~0, ~0);
147 procmsg_msginfo_set_flags(newmsg, flags.perm_flags, flags.tmp_flags);
148 procmsg_msginfo_free(&newmsg);
151 return msgnum;
154 #define MIMEINFO_NOT_ATTACHMENT(m) \
155 ((m)->type == MIMETYPE_MESSAGE || (m)->type == MIMETYPE_MULTIPART)
157 static void remove_attachments_cb(GtkWidget *widget, AttRemover *attremover)
159 MainWindow *mainwin = mainwindow_get_mainwindow();
160 SummaryView *summaryview = mainwin->summaryview;
161 GtkTreeModel *model = attremover->model;
162 GtkTreeIter iter;
163 MsgInfo *newmsg;
164 MimeInfo *info, *parent, *last, *partinfo;
165 GNode *child;
166 gint att_all = 0, att_removed = 0, msgnum;
167 gboolean to_removal, iter_valid=TRUE;
169 newmsg = procmsg_msginfo_copy(attremover->msginfo);
170 info = procmime_scan_message(newmsg);
172 last = partinfo = find_first_text_part(info);
173 partinfo = procmime_mimeinfo_next(partinfo);
174 if (!partinfo || !gtk_tree_model_get_iter_first(model, &iter)) {
175 gtk_widget_destroy(attremover->window);
176 procmsg_msginfo_free(&newmsg);
177 return;
180 main_window_cursor_wait(mainwin);
181 summary_freeze(summaryview);
182 folder_item_update_freeze();
183 inc_lock();
185 while (partinfo && iter_valid) {
186 if (MIMEINFO_NOT_ATTACHMENT(partinfo)) {
187 last = partinfo;
188 partinfo = procmime_mimeinfo_next(partinfo);
189 continue;
192 att_all++;
193 gtk_tree_model_get(model, &iter, ATT_REMOVER_TOGGLE,
194 &to_removal, -1);
195 if (!to_removal) {
196 last = partinfo;
197 partinfo = procmime_mimeinfo_next(partinfo);
198 iter_valid = gtk_tree_model_iter_next(model, &iter);
199 continue;
202 parent = partinfo;
203 partinfo = procmime_mimeinfo_next(partinfo);
204 iter_valid = gtk_tree_model_iter_next(model, &iter);
206 g_node_destroy(parent->node);
207 att_removed++;
210 partinfo = last;
211 while (partinfo) {
212 if (!(parent = procmime_mimeinfo_parent(partinfo)))
213 break;
215 /* multipart/{alternative,mixed,related} make sense
216 only when they have at least 2 nodes, remove last
217 one and move it one level up if otherwise */
218 if (MIMEINFO_NOT_ATTACHMENT(partinfo) &&
219 g_node_n_children(partinfo->node) < 2)
221 gint pos = g_node_child_position(parent->node, partinfo->node);
222 g_node_unlink(partinfo->node);
224 if ((child = g_node_first_child(partinfo->node))) {
225 g_node_unlink(child);
226 g_node_insert(parent->node, pos, child);
228 g_node_destroy(partinfo->node);
230 child = g_node_last_child(parent->node);
231 partinfo = child ? child->data : parent;
232 continue;
235 if (partinfo->node->prev) {
236 partinfo = (MimeInfo *) partinfo->node->prev->data;
237 if (partinfo->node->children) {
238 child = g_node_last_child(partinfo->node);
239 partinfo = (MimeInfo *) child->data;
241 } else if (partinfo->node->parent)
242 partinfo = (MimeInfo *) partinfo->node->parent->data;
245 msgnum = save_new_message(attremover->msginfo, newmsg, info,
246 (att_all - att_removed > 0));
248 inc_unlock();
249 folder_item_update_thaw();
250 summary_thaw(summaryview);
251 main_window_cursor_normal(mainwin);
253 if (msgnum > 0)
254 summary_select_by_msgnum(summaryview, msgnum, TRUE);
256 gtk_widget_destroy(attremover->window);
259 static void remove_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str,
260 gpointer data)
262 GtkTreeModel *model = (GtkTreeModel *)data;
263 GtkTreeIter iter;
264 GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
265 gboolean fixed;
267 gtk_tree_model_get_iter (model, &iter, path);
268 gtk_tree_model_get (model, &iter, ATT_REMOVER_TOGGLE, &fixed, -1);
270 fixed ^= 1;
271 gtk_list_store_set (GTK_LIST_STORE (model), &iter, ATT_REMOVER_TOGGLE, fixed, -1);
273 gtk_tree_path_free (path);
276 static void fill_attachment_store(GtkTreeView *list_view, MimeInfo *partinfo)
278 const gchar *name;
279 gchar *label, *content_type;
280 GtkTreeIter iter;
281 GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model
282 (GTK_TREE_VIEW(list_view)));
284 partinfo = find_first_text_part(partinfo);
285 partinfo = procmime_mimeinfo_next(partinfo);
286 if (!partinfo)
287 return;
289 while (partinfo) {
290 if (MIMEINFO_NOT_ATTACHMENT(partinfo)) {
291 partinfo = procmime_mimeinfo_next(partinfo);
292 continue;
295 content_type = procmime_get_content_type_str(
296 partinfo->type, partinfo->subtype);
298 name = g_markup_escape_text(procmime_mimeinfo_get_parameter(partinfo, "filename"), -1);
299 if (!name)
300 name = g_markup_escape_text(procmime_mimeinfo_get_parameter(partinfo, "name"), -1);
301 if (!name)
302 name = _("unknown");
304 label = g_strconcat("<b>", _("Type"), ":</b> ", content_type, " <b>",
305 _("Size"), ":</b> ", to_human_readable((goffset)partinfo->length),
306 "\n", "<b>", _("Filename"), ":</b> ", name, NULL);
308 gtk_list_store_append(list_store, &iter);
309 gtk_list_store_set(list_store, &iter,
310 ATT_REMOVER_INFO, label,
311 ATT_REMOVER_TOGGLE, TRUE,
312 -1);
313 g_free(label);
314 g_free(content_type);
315 partinfo = procmime_mimeinfo_next(partinfo);
319 static void remove_attachments_dialog(AttRemover *attremover)
321 GtkWidget *window;
322 GtkWidget *vbox;
323 GtkTreeView *list_view;
324 GtkTreeModel *model;
325 GtkTreeViewColumn *column;
326 GtkCellRenderer *renderer;
327 GtkWidget *scrollwin;
328 GtkWidget *hbbox;
329 GtkWidget *ok_btn;
330 GtkWidget *cancel_btn;
331 MimeInfo *info = procmime_scan_message(attremover->msginfo);
333 static GdkGeometry geometry;
335 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "AttRemover");
336 gtk_container_set_border_width( GTK_CONTAINER(window), VBOX_BORDER);
337 gtk_window_set_title(GTK_WINDOW(window), _("Remove attachments"));
338 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
339 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
340 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
342 g_signal_connect(G_OBJECT(window), "delete_event",
343 G_CALLBACK(cancelled_event_cb), attremover);
344 g_signal_connect(G_OBJECT(window), "key_press_event",
345 G_CALLBACK(key_pressed_cb), attremover);
346 g_signal_connect(G_OBJECT(window), "size_allocate",
347 G_CALLBACK(size_allocate_cb), NULL);
349 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, VBOX_BORDER);
350 gtk_container_add(GTK_CONTAINER(window), vbox);
352 model = GTK_TREE_MODEL(gtk_list_store_new(N_ATT_REMOVER_COLUMNS,
353 G_TYPE_STRING,
354 G_TYPE_BOOLEAN,
355 -1));
356 list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
357 g_object_unref(model);
358 gtk_tree_view_set_rules_hint(list_view, prefs_common_get_prefs()->use_stripes_everywhere);
360 renderer = gtk_cell_renderer_toggle_new();
361 g_signal_connect(renderer, "toggled", G_CALLBACK(remove_toggled_cb), model);
362 column = gtk_tree_view_column_new_with_attributes
363 (_("Remove"),
364 renderer,
365 "active", ATT_REMOVER_TOGGLE,
366 NULL);
367 gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
369 renderer = gtk_cell_renderer_text_new();
370 column = gtk_tree_view_column_new_with_attributes
371 (_("Attachment"),
372 renderer,
373 "markup", ATT_REMOVER_INFO,
374 NULL);
375 gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
376 fill_attachment_store(list_view, info);
378 scrollwin = gtk_scrolled_window_new(NULL, NULL);
379 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin),
380 GTK_SHADOW_ETCHED_OUT);
381 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
382 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
383 gtk_container_add(GTK_CONTAINER(scrollwin), GTK_WIDGET(list_view));
384 gtk_container_set_border_width(GTK_CONTAINER(scrollwin), 4);
385 gtk_box_pack_start(GTK_BOX(vbox), scrollwin, TRUE, TRUE, 0);
387 gtkut_stock_button_set_create(&hbbox, &cancel_btn, NULL, _("_Cancel"),
388 &ok_btn, NULL, _("_OK"),
389 NULL, NULL, NULL);
390 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
391 gtk_container_set_border_width(GTK_CONTAINER(hbbox), HSPACING_NARROW);
392 gtk_widget_grab_default(ok_btn);
394 g_signal_connect(G_OBJECT(ok_btn), "clicked",
395 G_CALLBACK(remove_attachments_cb), attremover);
396 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
397 G_CALLBACK(cancelled_event_cb), attremover);
399 if (!geometry.min_height) {
400 geometry.min_width = 450;
401 geometry.min_height = 350;
404 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
405 GDK_HINT_MIN_SIZE);
406 gtk_window_set_default_size(GTK_WINDOW(window), attremover->win_width,
407 attremover->win_height);
409 attremover->window = window;
410 attremover->model = model;
412 gtk_widget_show_all(window);
413 gtk_widget_grab_focus(ok_btn);
416 static void remove_attachments(GSList *msglist)
418 MainWindow *mainwin = mainwindow_get_mainwindow();
419 SummaryView *summaryview = mainwin->summaryview;
420 GSList *cur;
421 gint msgnum = -1;
422 gint stripped_msgs = 0;
423 gint total_msgs = 0;
425 if (alertpanel_full(_("Destroy attachments"),
426 _("Do you really want to remove all attachments from "
427 "the selected messages?\n\n"
428 "The deleted data will be unrecoverable."),
429 NULL, _("_Cancel"), "edit-delete", _("_Delete"), NULL, NULL,
430 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_QUESTION) != G_ALERTALTERNATE)
431 return;
433 main_window_cursor_wait(summaryview->mainwin);
434 summary_freeze(summaryview);
435 folder_item_update_freeze();
436 inc_lock();
438 for (cur = msglist; cur; cur = cur->next) {
439 MsgInfo *msginfo = (MsgInfo *)cur->data;
440 MsgInfo *newmsg = NULL;
441 MimeInfo *info = NULL;
442 MimeInfo *partinfo = NULL;
443 MimeInfo *nextpartinfo = NULL;
445 if (!msginfo)
446 continue;
447 total_msgs++; /* count all processed messages */
449 newmsg = procmsg_msginfo_copy(msginfo);
450 info = procmime_scan_message(newmsg);
452 if ( !(partinfo = find_first_text_part(info)) ) {
453 procmsg_msginfo_free(&newmsg);
454 continue;
456 /* only strip attachments where there is at least one */
457 nextpartinfo = procmime_mimeinfo_next(partinfo);
458 if (nextpartinfo) {
459 partinfo->node->next = NULL;
460 partinfo->node->children = NULL;
461 info->node->children->data = partinfo;
463 msgnum = save_new_message(msginfo, newmsg, info, FALSE);
465 stripped_msgs++; /* count messages with removed attachment(s) */
468 if (stripped_msgs == 0) {
469 alertpanel_notice(_("The selected messages don't have any attachments."));
470 } else {
471 if (stripped_msgs != total_msgs)
472 alertpanel_notice(_("Attachments removed from %d of the %d selected messages."),
473 stripped_msgs, total_msgs);
474 else
475 alertpanel_notice(_("Attachments removed from all %d selected messages."), total_msgs);
478 inc_unlock();
479 folder_item_update_thaw();
480 summary_thaw(summaryview);
481 main_window_cursor_normal(summaryview->mainwin);
483 if (msgnum > 0) {
484 summary_select_by_msgnum(summaryview, msgnum, TRUE);
488 static void remove_attachments_ui(GtkAction *action, gpointer data)
490 MainWindow *mainwin = mainwindow_get_mainwindow();
491 GSList *msglist = summary_get_selected_msg_list(mainwin->summaryview);
492 MimeInfo *info = NULL, *partinfo = NULL;
494 if (summary_is_locked(mainwin->summaryview) || !msglist)
495 return;
497 if (g_slist_length(msglist) == 1) {
498 info = procmime_scan_message(msglist->data);
500 partinfo = find_first_text_part(info);
501 partinfo = procmime_mimeinfo_next(partinfo);
503 if (!partinfo) {
504 alertpanel_notice(_("This message doesn't have any attachments."));
505 } else {
506 AttRemoverData.msginfo = msglist->data;
507 remove_attachments_dialog(&AttRemoverData);
509 } else
510 remove_attachments(msglist);
512 g_slist_free(msglist);
515 static GtkActionEntry remove_att_main_menu[] = {{
516 "Message/RemoveAtt",
517 NULL, N_("Remove attachments..."), NULL, NULL, G_CALLBACK(remove_attachments_ui)
520 static guint context_menu_id = 0;
521 static guint main_menu_id = 0;
523 gint plugin_init(gchar **error)
525 MainWindow *mainwin = mainwindow_get_mainwindow();
526 gchar *rcpath;
528 if( !check_plugin_version(MAKE_NUMERIC_VERSION(3,6,1,27),
529 VERSION_NUMERIC, _("AttRemover"), error) )
530 return -1;
532 gtk_action_group_add_actions(mainwin->action_group, remove_att_main_menu,
533 1, (gpointer)mainwin);
534 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Message", "RemoveAtt",
535 "Message/RemoveAtt", GTK_UI_MANAGER_MENUITEM,
536 main_menu_id)
537 MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menus/SummaryViewPopup", "RemoveAtt",
538 "Message/RemoveAtt", GTK_UI_MANAGER_MENUITEM,
539 context_menu_id)
541 prefs_set_default(prefs);
542 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
543 prefs_read_config(prefs, PREFS_BLOCK_NAME, rcpath, NULL);
544 g_free(rcpath);
546 return 0;
549 gboolean plugin_done(void)
551 MainWindow *mainwin = mainwindow_get_mainwindow();
552 PrefFile *pref_file;
553 gchar *rc_file_path;
555 rc_file_path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
556 COMMON_RC, NULL);
557 pref_file = prefs_write_open(rc_file_path);
558 g_free(rc_file_path);
560 if (!pref_file || prefs_set_block_label(pref_file, PREFS_BLOCK_NAME) < 0)
561 return TRUE;
563 if (prefs_write_param(prefs, pref_file->fp) < 0) {
564 g_warning("failed to write AttRemover plugin configuration");
565 prefs_file_close_revert(pref_file);
566 return TRUE;
569 if (fprintf(pref_file->fp, "\n") < 0) {
570 FILE_OP_ERROR(rc_file_path, "fprintf");
571 prefs_file_close_revert(pref_file);
572 } else
573 prefs_file_close(pref_file);
575 if (mainwin == NULL)
576 return TRUE;
578 MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "Message/RemoveAtt", main_menu_id);
579 main_menu_id = 0;
581 MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "Message/RemoveAtt", context_menu_id);
582 context_menu_id = 0;
584 return TRUE;
587 const gchar *plugin_name(void)
589 return _("AttRemover");
592 const gchar *plugin_desc(void)
594 return _("This plugin removes attachments from mails.\n\n"
595 "Warning: this operation will be completely "
596 "un-cancellable and the deleted attachments will "
597 "be lost forever, and ever, and ever.");
600 const gchar *plugin_type(void)
602 return "GTK3";
605 const gchar *plugin_licence(void)
607 return "GPL3+";
610 const gchar *plugin_version(void)
612 return VERSION;
615 struct PluginFeature *plugin_provides(void)
617 static struct PluginFeature features[] =
618 { {PLUGIN_UTILITY, N_("Attachment handling")},
619 {PLUGIN_NOTHING, NULL}};
620 return features;