1 /* Python plugin for Claws-Mail
2 * Copyright (C) 2009-2012 Holger Berndt
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "clawsmailmodule.h"
22 #include "claws-features.h"
26 #include <glib/gi18n.h>
29 #include "composewindowtype.h"
30 #include "foldertype.h"
31 #include "messageinfotype.h"
33 #include <pygobject.h>
34 #include <pygtk/pygtk.h>
37 #include "mainwindow.h"
38 #include "summaryview.h"
39 #include "quicksearch.h"
41 #include "prefs_common.h"
42 #include "common/tags.h"
46 static PyObject *cm_module = NULL;
48 PyObject* get_gobj_from_address(gpointer addr)
52 if (!G_IS_OBJECT(addr))
60 return pygobject_new(obj);
63 static PyObject* private_wrap_gobj(PyObject *self, PyObject *args)
67 if (!PyArg_ParseTuple(args, "l", &addr))
70 return get_gobj_from_address(addr);
73 static PyObject *get_mainwindow_action_group(PyObject *self, PyObject *args)
77 mainwin = mainwindow_get_mainwindow();
79 return get_gobj_from_address(mainwin->action_group);
84 static PyObject *get_mainwindow_ui_manager(PyObject *self, PyObject *args)
88 mainwin = mainwindow_get_mainwindow();
90 return get_gobj_from_address(mainwin->ui_manager);
95 static PyObject *get_folderview_selected_folder(PyObject *self, PyObject *args)
99 mainwin = mainwindow_get_mainwindow();
100 if(mainwin && mainwin->folderview) {
102 item = folderview_get_selected_item(mainwin->folderview);
104 return clawsmail_folder_new(item);
110 static PyObject *folderview_select_folder(PyObject *self, PyObject *args)
114 mainwin = mainwindow_get_mainwindow();
115 if(mainwin && mainwin->folderview) {
118 folder = PyTuple_GetItem(args, 0);
122 item = clawsmail_folder_get_item(folder);
125 folderview_select(mainwin->folderview, item);
131 static gboolean setup_folderitem_node(GNode *item_node, GNode *item_parent, PyObject **pyparent)
133 PyObject *pynode, *children;
134 int retval, n_children, i_child;
137 /* create a python node for the folderitem node */
138 pynode = clawsmail_node_new(cm_module);
142 /* store Folder in pynode */
143 folder = clawsmail_folder_new(item_node->data);
144 retval = PyObject_SetAttrString(pynode, "data", folder);
151 if(pyparent && *pyparent) {
152 /* add this node to the parent's childs */
153 children = PyObject_GetAttrString(*pyparent, "children");
154 retval = PyList_Append(children, pynode);
167 /* call this function recursively for all children of the new node */
168 n_children = g_node_n_children(item_node);
169 for(i_child = 0; i_child < n_children; i_child++) {
170 if(!setup_folderitem_node(g_node_nth_child(item_node, i_child), item_node, &pynode)) {
180 static PyObject* get_folder_tree_from_account_name(const char *str)
185 result = Py_BuildValue("[]");
189 for(walk = folder_get_list(); walk; walk = walk->next) {
190 Folder *folder = walk->data;
191 if((!str || !g_strcmp0(str, folder->name)) && folder->node) {
193 int n_children, i_child, retval;
195 /* create root nodes */
196 root = clawsmail_node_new(cm_module);
202 n_children = g_node_n_children(folder->node);
203 for(i_child = 0; i_child < n_children; i_child++) {
204 if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
210 retval = PyList_Append(result, root);
221 static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
226 for(walk = folder_get_list(); walk; walk = walk->next) {
227 Folder *folder = walk->data;
231 root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
236 if(!setup_folderitem_node(root_node, NULL, &result))
243 PyErr_SetString(PyExc_LookupError, "Folder not found");
247 static PyObject* get_folder_tree(PyObject *self, PyObject *args)
255 retval = PyArg_ParseTuple(args, "|O", &arg);
260 /* calling possibilities:
261 * nothing, the mailbox name in a string, a Folder object */
263 /* no arguments: build up a list of folder trees */
264 if(PyTuple_Size(args) == 0) {
265 result = get_folder_tree_from_account_name(NULL);
267 else if(PyString_Check(arg)){
269 str = PyString_AsString(arg);
273 result = get_folder_tree_from_account_name(str);
275 else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
276 result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
279 PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a mailbox string or a Folder object.");
286 static PyObject* quicksearch_search(PyObject *self, PyObject *args)
293 /* must be given exactly one argument, which is a string */
294 searchtype = prefs_common.summary_quicksearch_type;
295 if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
298 mainwin = mainwindow_get_mainwindow();
299 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
300 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
304 qs = mainwin->summaryview->quicksearch;
305 quicksearch_set(qs, searchtype, string);
311 static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
316 mainwin = mainwindow_get_mainwindow();
317 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
318 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
322 qs = mainwin->summaryview->quicksearch;
323 quicksearch_set(qs, prefs_common.summary_quicksearch_type, "");
329 static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
333 Py_ssize_t size, iEl;
336 mainwin = mainwindow_get_mainwindow();
337 if(!mainwin || !mainwin->summaryview) {
338 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
342 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
343 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
348 size = PyList_Size(olist);
349 for(iEl = 0; iEl < size; iEl++) {
350 PyObject *element = PyList_GET_ITEM(olist, iEl);
352 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
353 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
357 msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
360 summary_unselect_all(mainwin->summaryview);
361 summary_select_by_msg_list(mainwin->summaryview, msginfos);
362 g_slist_free(msginfos);
368 static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
374 mainwin = mainwindow_get_mainwindow();
375 if(!mainwin || !mainwin->summaryview) {
376 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
380 result = Py_BuildValue("[]");
384 list = summary_get_selected_msg_list(mainwin->summaryview);
385 for(walk = list; walk; walk = walk->next) {
387 msg = clawsmail_messageinfo_new(walk->data);
388 if(PyList_Append(result, msg) == -1) {
398 static PyObject* is_exiting(PyObject *self, PyObject *args)
400 if(claws_is_exiting())
406 static PyObject* get_tags(PyObject *self, PyObject *args)
409 PyObject *tags_tuple;
412 tags_list = tags_get_list();
413 num_tags = g_slist_length(tags_list);
415 tags_tuple = PyTuple_New(num_tags);
416 if(tags_tuple != NULL) {
418 PyObject *tag_object;
422 for(walk = tags_list; walk; walk = walk->next) {
423 tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
424 if(tag_object == NULL) {
425 Py_DECREF(tags_tuple);
428 PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
432 g_slist_free(tags_list);
437 static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
442 retval = PyArg_ParseTuple(args, "s", &tag_str);
446 if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
447 /* tag name is reserved, raise KeyError */
448 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
452 tags_add_tag(tag_str);
457 static PyObject* delete_tag(PyObject *self, PyObject *args)
464 retval = PyArg_ParseTuple(args, "s", &tag_str);
468 tag_id = tags_get_id_for_str(tag_str);
470 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
474 tags_remove_tag(tag_id);
477 mainwin = mainwindow_get_mainwindow();
479 summary_redisplay_msg(mainwin->summaryview);
485 static PyObject* rename_tag(PyObject *self, PyObject *args)
488 const char *old_tag_str;
489 const char *new_tag_str;
493 retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
497 if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
498 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
502 tag_id = tags_get_id_for_str(old_tag_str);
504 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
508 tags_update_tag(tag_id, new_tag_str);
511 mainwin = mainwindow_get_mainwindow();
513 summary_redisplay_msg(mainwin->summaryview);
518 static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
520 Py_ssize_t size, iEl;
521 FolderItem *folderitem;
525 folderitem = clawsmail_folder_get_item(folder);
527 PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
531 size = PyList_Size(messagelist);
532 for(iEl = 0; iEl < size; iEl++) {
533 PyObject *element = PyList_GET_ITEM(messagelist, iEl);
536 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
537 PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
541 msginfo = clawsmail_messageinfo_get_msginfo(element);
543 PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
547 procmsg_msginfo_set_to_folder(msginfo, folderitem);
548 *list = g_slist_prepend(*list, msginfo);
554 static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
556 PyObject *messagelist;
561 retval = PyArg_ParseTuple(args, "O!O!",
562 &PyList_Type, &messagelist,
563 clawsmail_folder_get_type_object(), &folder);
567 folder_item_update_freeze();
569 if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
573 procmsg_move_messages(list);
575 procmsg_copy_messages(list);
577 folder_item_update_thaw();
582 folder_item_update_thaw();
587 static PyObject* move_messages(PyObject *self, PyObject *args)
589 return move_or_copy_messages(self, args, TRUE);
593 static PyObject* copy_messages(PyObject *self, PyObject *args)
595 return move_or_copy_messages(self, args, FALSE);
598 static PyMethodDef ClawsMailMethods[] = {
600 {"get_mainwindow_action_group", get_mainwindow_action_group, METH_NOARGS,
601 "get_mainwindow_action_group() - get action group of main window menu\n"
603 "Returns the gtk.ActionGroup for the main window."},
605 {"get_mainwindow_ui_manager", get_mainwindow_ui_manager, METH_NOARGS,
606 "get_mainwindow_ui_manager() - get ui manager of main window\n"
608 "Returns the gtk.UIManager for the main window."},
610 {"get_folder_tree", get_folder_tree, METH_VARARGS,
611 "get_folder_tree([root]) - get a folder tree\n"
613 "Without arguments, get a list of folder trees for all mailboxes.\n"
615 "If the optional root argument is a string, it is supposed to be a\n"
616 "mailbox name. The function then returns a tree of folders of that mailbox.\n"
618 "If the optional root argument is a clawsmail.Folder, the function\n"
619 "returns a tree of subfolders with the given folder as root element.\n"
621 "In any case, a tree consists of elements of the type clawsmail.Node."},
623 {"get_folderview_selected_folder", get_folderview_selected_folder, METH_NOARGS,
624 "get_folderview_selected_folder() - get selected folder in folderview\n"
626 "Returns the currently selected folder as a clawsmail.Folder."},
627 {"folderview_select_folder", folderview_select_folder, METH_VARARGS,
628 "folderview_select_folder(folder) - select folder in folderview\n"
630 "Takes an argument of type clawsmail.Folder, and selects the corresponding folder."},
632 {"quicksearch_search", quicksearch_search, METH_VARARGS,
633 "quicksearch_search(string [, type]) - perform a quicksearch\n"
635 "Perform a quicksearch of the given string. The optional type argument can be\n"
636 "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
637 "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
638 "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
639 "string corresponding to the type."},
641 {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
642 "quicksearch_clear() - clear the quicksearch"},
644 {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
645 "get_summaryview_selected_message_list() - get selected message list\n"
647 "Get a list of clawsmail.MessageInfo objects of the current selection."},
649 {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
650 "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
652 "Select a list of clawsmail.MessageInfo objects in the summary view."},
654 {"is_exiting", is_exiting, METH_NOARGS,
655 "is_exiting() - test whether Claws Mail is currently exiting\n"
657 "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
658 "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
659 "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
662 {"move_messages", move_messages, METH_VARARGS,
663 "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
665 "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
666 "The target_folder argument has to be a clawsmail.Folder object."},
668 {"copy_messages", copy_messages, METH_VARARGS,
669 "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
671 "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
672 "The target_folder argument has to be a clawsmail.Folder object."},
674 {"get_tags", get_tags, METH_NOARGS,
675 "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
677 "Get a tuple of strings representing all tags that are defined in Claws Mail."},
679 {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
680 "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
682 "This function creates the given tag if it does not exist yet.\n"
683 "It is not an error if the tag already exists. In this case, this function does nothing.\n"
684 "However, if a reserved tag name is chosen, a ValueError exception is raised."},
686 {"delete_tag", delete_tag, METH_VARARGS,
687 "delete_tag(tag) - delete a tag\n"
689 "This function deletes an existing tag.\n"
690 "Raises a KeyError exception if the tag does not exist."},
692 {"rename_tag", rename_tag, METH_VARARGS,
693 "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
695 "This function renames an existing tag.\n"
696 "Raises a KeyError exception if the tag does not exist.\n"
697 "Raises a ValueError exception if the old or new tag name is a reserved name."},
700 {"__gobj", private_wrap_gobj, METH_VARARGS,
701 "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
703 "For internal usage only."},
705 {NULL, NULL, 0, NULL}
708 static void initmiscstuff(PyObject *module)
713 "QUICK_SEARCH_SUBJECT = 0\n"
714 "QUICK_SEARCH_FROM = 1\n"
715 "QUICK_SEARCH_TO = 2\n"
716 "QUICK_SEARCH_EXTENDED = 3\n"
717 "QUICK_SEARCH_MIXED = 4\n"
718 "QUICK_SEARCH_TAG = 5\n"
720 dict = PyModule_GetDict(module);
721 res = PyRun_String(cmd, Py_file_input, dict, dict);
726 void claws_mail_python_init(void)
728 if (!Py_IsInitialized())
732 cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
733 "This module can be used to access some of Claws Mail's data structures\n"
734 "in order to extend or modify the user interface or automate repetitive tasks.\n"
736 "Whenever possible, the interface works with standard GTK+ widgets\n"
737 "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
738 "to find out about all possible options.\n"
740 "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
741 "If you're missing something specific, try contacting the author.");
743 /* initialize classes */
745 initcomposewindow(cm_module);
746 initfolder(cm_module);
747 initmessageinfo(cm_module);
749 /* initialize misc things */
750 initmiscstuff(cm_module);
752 PyRun_SimpleString("import clawsmail\n");
753 PyRun_SimpleString("clawsmail.compose_window = None\n");
757 void put_composewindow_into_module(Compose *compose)
761 pycompose = clawsmail_compose_new(cm_module, compose);
762 PyObject_SetAttrString(cm_module, "compose_window", pycompose);
763 Py_DECREF(pycompose);