1 /*
2  * att_remover -- for Claws Mail
3  *
4  * Copyright (C) 2005 Colin Leroy <colin@colino.net>
5  *
6  * Sylpheed is a GTK+ based, lightweight, and fast e-mail client
7  * Copyright (C) 1999-2005 Hiroyuki Yamamoto and the Claws Mail Team
8  *
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.
13  *
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.
18  *
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.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #include "claws-features.h"
27 #endif
28 
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 
32 #include <string.h>
33 
34 #include <gdk/gdkkeysyms.h>
35 #include <gtk/gtk.h>
36 
37 #include "mainwindow.h"
38 #include "summaryview.h"
39 #include "folder.h"
40 #include "version.h"
41 #include "summaryview.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"
51 
52 #define PREFS_BLOCK_NAME "AttRemover"
53 
54 static struct _AttRemover {
55 	GtkWidget	*window;
56 	MsgInfo		*msginfo;
57 	GtkTreeModel	*model;
58 	gint		win_width;
59 	gint		win_height;
60 } AttRemoverData;
61 
62 typedef struct _AttRemover AttRemover;
63 
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}
70 };
71 
72 enum {
73 	ATT_REMOVER_INFO,
74 	ATT_REMOVER_TOGGLE,
75 	N_ATT_REMOVER_COLUMNS
76 };
77 
find_first_text_part(MimeInfo * partinfo)78 static MimeInfo *find_first_text_part(MimeInfo *partinfo)
79 {
80 	while (partinfo && partinfo->type != MIMETYPE_TEXT) {
81 		partinfo = procmime_mimeinfo_next(partinfo);
82 	}
83 
84 	return partinfo;
85 }
86 
key_pressed_cb(GtkWidget * widget,GdkEventKey * event,AttRemover * attremover)87 static gboolean key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
88 				AttRemover *attremover)
89 {
90 	if (event && event->keyval == GDK_KEY_Escape)
91 		gtk_widget_destroy(attremover->window);
92 
93 	return FALSE;
94 }
95 
cancelled_event_cb(GtkWidget * widget,AttRemover * attremover)96 static void cancelled_event_cb(GtkWidget *widget, AttRemover *attremover)
97 {
98 	gtk_widget_destroy(attremover->window);
99 }
100 
size_allocate_cb(GtkWidget * widget,GtkAllocation * allocation)101 static void size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation)
102 {
103 	cm_return_if_fail(allocation != NULL);
104 
105 	AttRemoverData.win_width = allocation->width;
106 	AttRemoverData.win_height = allocation->height;
107 }
108 
save_new_message(MsgInfo * oldmsg,MsgInfo * newmsg,MimeInfo * info,gboolean has_attachment)109 static gint save_new_message(MsgInfo *oldmsg, MsgInfo *newmsg, MimeInfo *info,
110 				gboolean has_attachment)
111 {
112 	MsgInfo *finalmsg;
113 	FolderItem *item = oldmsg->folder;
114 	MsgFlags flags = {0, 0};
115 	gint msgnum = -1;
116 
117 	finalmsg = procmsg_msginfo_new_from_mimeinfo(newmsg, info);
118 	if (!finalmsg) {
119 		procmsg_msginfo_free(&newmsg);
120 		return -1;
121 	}
122 
123 	debug_print("finalmsg %s\n", finalmsg->plaintext_file);
124 
125 	flags.tmp_flags = oldmsg->flags.tmp_flags;
126 	flags.perm_flags = oldmsg->flags.perm_flags;
127 
128 	if (!has_attachment)
129 		flags.tmp_flags &= ~MSG_HAS_ATTACHMENT;
130 
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;
138 	}
139 	folder_item_remove_msg(item, oldmsg->msgnum);
140 	finalmsg->msgnum = msgnum;
141 	procmsg_msginfo_free(&newmsg);
142 	procmsg_msginfo_free(&finalmsg);
143 
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);
149 	}
150 
151 	return msgnum;
152 }
153 
154 #define MIMEINFO_NOT_ATTACHMENT(m) \
155 	((m)->type == MIMETYPE_MESSAGE || (m)->type == MIMETYPE_MULTIPART)
156 
remove_attachments_cb(GtkWidget * widget,AttRemover * attremover)157 static void remove_attachments_cb(GtkWidget *widget, AttRemover *attremover)
158 {
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;
168 
169 	newmsg = procmsg_msginfo_copy(attremover->msginfo);
170 	info = procmime_scan_message(newmsg);
171 
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;
178 	}
179 
180 	main_window_cursor_wait(mainwin);
181 	summary_freeze(summaryview);
182 	folder_item_update_freeze();
183 	inc_lock();
184 
185 	while (partinfo && iter_valid) {
186 		if (MIMEINFO_NOT_ATTACHMENT(partinfo)) {
187     			last = partinfo;
188     			partinfo = procmime_mimeinfo_next(partinfo);
189 			continue;
190 		}
191 
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;
200 		}
201 
202 		parent = partinfo;
203 		partinfo = procmime_mimeinfo_next(partinfo);
204 		iter_valid = gtk_tree_model_iter_next(model, &iter);
205 
206 		g_node_destroy(parent->node);
207 		att_removed++;
208 	}
209 
210 	partinfo = last;
211 	while (partinfo) {
212 		if (!(parent = procmime_mimeinfo_parent(partinfo)))
213 			break;
214 
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)
220 		{
221 			gint pos = g_node_child_position(parent->node, partinfo->node);
222 			g_node_unlink(partinfo->node);
223 
224 			if ((child = g_node_first_child(partinfo->node))) {
225 				g_node_unlink(child);
226 				g_node_insert(parent->node, pos, child);
227 			}
228 			g_node_destroy(partinfo->node);
229 
230 			child = g_node_last_child(parent->node);
231 			partinfo = child ? child->data : parent;
232 			continue;
233 		}
234 
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;
240 			}
241 		} else if (partinfo->node->parent)
242 			partinfo = (MimeInfo *) partinfo->node->parent->data;
243 	}
244 
245 	msgnum = save_new_message(attremover->msginfo, newmsg, info,
246 			 (att_all - att_removed > 0));
247 
248 	inc_unlock();
249 	folder_item_update_thaw();
250 	summary_thaw(summaryview);
251 	main_window_cursor_normal(mainwin);
252 
253 	if (msgnum > 0)
254 		summary_select_by_msgnum(summaryview, msgnum, TRUE);
255 
256 	gtk_widget_destroy(attremover->window);
257 }
258 
remove_toggled_cb(GtkCellRendererToggle * cell,gchar * path_str,gpointer data)259 static void remove_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str,
260 				gpointer data)
261 {
262 	GtkTreeModel *model = (GtkTreeModel *)data;
263 	GtkTreeIter  iter;
264 	GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
265 	gboolean fixed;
266 
267 	gtk_tree_model_get_iter (model, &iter, path);
268 	gtk_tree_model_get (model, &iter, ATT_REMOVER_TOGGLE, &fixed, -1);
269 
270 	fixed ^= 1;
271 	gtk_list_store_set (GTK_LIST_STORE (model), &iter, ATT_REMOVER_TOGGLE, fixed, -1);
272 
273 	gtk_tree_path_free (path);
274 }
275 
fill_attachment_store(GtkTreeView * list_view,MimeInfo * partinfo)276 static void fill_attachment_store(GtkTreeView *list_view, MimeInfo *partinfo)
277 {
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)));
283 
284 	partinfo = find_first_text_part(partinfo);
285 	partinfo = procmime_mimeinfo_next(partinfo);
286 	if (!partinfo)
287 		return;
288 
289 	while (partinfo) {
290 		if (MIMEINFO_NOT_ATTACHMENT(partinfo)) {
291     			partinfo = procmime_mimeinfo_next(partinfo);
292 			continue;
293 		}
294 
295 		content_type = procmime_get_content_type_str(
296 					partinfo->type, partinfo->subtype);
297 
298 		name = procmime_mimeinfo_get_parameter(partinfo, "filename");
299 		if (!name)
300 			name = procmime_mimeinfo_get_parameter(partinfo, "name");
301 		if (!name)
302 			name = _("unknown");
303 
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);
307 
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);
316 	}
317 }
318 
remove_attachments_dialog(AttRemover * attremover)319 static void remove_attachments_dialog(AttRemover *attremover)
320 {
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);
332 
333 	static GdkGeometry geometry;
334 
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);
341 
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);
348 
349 	vbox = gtk_vbox_new(FALSE, VBOX_BORDER);
350 	gtk_container_add(GTK_CONTAINER(window), vbox);
351 
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);
359 
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);
368 
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);
377 
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);
386 
387 	gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
388 				      &ok_btn, GTK_STOCK_OK,
389 				      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);
393 
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);
398 
399 	if (!geometry.min_height) {
400 		geometry.min_width = 450;
401 		geometry.min_height = 350;
402 	}
403 
404 	gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
405 				      GDK_HINT_MIN_SIZE);
406 	gtk_widget_set_size_request(window, attremover->win_width,
407 					attremover->win_height);
408 
409 	attremover->window = window;
410 	attremover->model  = model;
411 
412 	gtk_widget_show_all(window);
413 	gtk_widget_grab_focus(ok_btn);
414 }
415 
remove_attachments(GSList * msglist)416 static void remove_attachments(GSList *msglist)
417 {
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;
424 
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 		  GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL, ALERTFOCUS_SECOND,
430                   FALSE, NULL, ALERT_QUESTION) != G_ALERTALTERNATE)
431 		return;
432 
433 	main_window_cursor_wait(summaryview->mainwin);
434 	summary_freeze(summaryview);
435 	folder_item_update_freeze();
436 	inc_lock();
437 
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;
444 
445 		if (!msginfo)
446 			continue;
447 		total_msgs++;			/* count all processed messages */
448 
449 		newmsg = procmsg_msginfo_copy(msginfo);
450 		info = procmime_scan_message(newmsg);
451 
452 		if ( !(partinfo = find_first_text_part(info)) ) {
453 			procmsg_msginfo_free(&newmsg);
454 			continue;
455 		}
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;
462 
463 			msgnum = save_new_message(msginfo, newmsg, info, FALSE);
464 
465 			stripped_msgs++;	/* count messages with removed attachment(s) */
466 		}
467 	}
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);
476 	}
477 
478 	inc_unlock();
479 	folder_item_update_thaw();
480 	summary_thaw(summaryview);
481 	main_window_cursor_normal(summaryview->mainwin);
482 
483 	if (msgnum > 0) {
484 		summary_select_by_msgnum(summaryview, msgnum, TRUE);
485 	}
486 }
487 
remove_attachments_ui(GtkAction * action,gpointer data)488 static void remove_attachments_ui(GtkAction *action, gpointer data)
489 {
490 	MainWindow *mainwin = mainwindow_get_mainwindow();
491 	GSList *msglist = summary_get_selected_msg_list(mainwin->summaryview);
492 	MimeInfo *info = NULL, *partinfo = NULL;
493 
494 	if (summary_is_locked(mainwin->summaryview) || !msglist)
495 		return;
496 
497 	if (g_slist_length(msglist) == 1) {
498 		info = procmime_scan_message(msglist->data);
499 
500 		partinfo = find_first_text_part(info);
501 		partinfo = procmime_mimeinfo_next(partinfo);
502 
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);
508 		}
509 	} else
510 		remove_attachments(msglist);
511 
512 	g_slist_free(msglist);
513 }
514 
515 static GtkActionEntry remove_att_main_menu[] = {{
516 	"Message/RemoveAtt",
517 	NULL, N_("Remove attachments..."), NULL, NULL, G_CALLBACK(remove_attachments_ui)
518 }};
519 
520 static guint context_menu_id = 0;
521 static guint main_menu_id = 0;
522 
plugin_init(gchar ** error)523 gint plugin_init(gchar **error)
524 {
525 	MainWindow *mainwin = mainwindow_get_mainwindow();
526 	gchar *rcpath;
527 
528 	if( !check_plugin_version(MAKE_NUMERIC_VERSION(3,6,1,27),
529 				VERSION_NUMERIC, _("AttRemover"), error) )
530 		return -1;
531 
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)
540 
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);
545 
546 	return 0;
547 }
548 
plugin_done(void)549 gboolean plugin_done(void)
550 {
551 	MainWindow *mainwin = mainwindow_get_mainwindow();
552 	PrefFile *pref_file;
553 	gchar *rc_file_path;
554 
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);
559 
560 	if (!pref_file || prefs_set_block_label(pref_file, PREFS_BLOCK_NAME) < 0)
561         	return TRUE;
562 
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;
567         }
568 
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);
574 
575 	if (mainwin == NULL)
576 		return TRUE;
577 
578 	MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "Message/RemoveAtt", main_menu_id);
579 	main_menu_id = 0;
580 
581 	MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "Message/RemoveAtt", context_menu_id);
582 	context_menu_id = 0;
583 
584 	return TRUE;
585 }
586 
plugin_name(void)587 const gchar *plugin_name(void)
588 {
589 	return _("AttRemover");
590 }
591 
plugin_desc(void)592 const gchar *plugin_desc(void)
593 {
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.");
598 }
599 
plugin_type(void)600 const gchar *plugin_type(void)
601 {
602 	return "GTK2";
603 }
604 
plugin_licence(void)605 const gchar *plugin_licence(void)
606 {
607 		return "GPL3+";
608 }
609 
plugin_version(void)610 const gchar *plugin_version(void)
611 {
612 	return VERSION;
613 }
614 
plugin_provides(void)615 struct PluginFeature *plugin_provides(void)
616 {
617 	static struct PluginFeature features[] =
618 		{ {PLUGIN_UTILITY, N_("Attachment handling")},
619 		  {PLUGIN_NOTHING, NULL}};
620 	return features;
621 }
622