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 "folderpropertiestype.h"
31 #include "foldertype.h"
32 #include "messageinfotype.h"
33 #include "accounttype.h"
34 #include "mailboxtype.h"
36 #define NO_IMPORT_PYGOBJECT
37 #include <pygobject.h>
38 #define NO_IMPORT_PYGTK
39 #include <pygtk/pygtk.h>
42 #include "mainwindow.h"
43 #include "summaryview.h"
44 #include "quicksearch.h"
46 #include "prefs_common.h"
47 #include "common/tags.h"
52 static PyObject *cm_module = NULL;
54 PyObject* get_gobj_from_address(gpointer addr)
58 if (!G_IS_OBJECT(addr))
66 return pygobject_new(obj);
69 static PyObject* private_wrap_gobj(PyObject *self, PyObject *args)
73 if (!PyArg_ParseTuple(args, "l", &addr))
76 return get_gobj_from_address(addr);
79 static PyObject *get_mainwindow_action_group(PyObject *self, PyObject *args)
83 mainwin = mainwindow_get_mainwindow();
85 return get_gobj_from_address(mainwin->action_group);
90 static PyObject *get_mainwindow_ui_manager(PyObject *self, PyObject *args)
94 mainwin = mainwindow_get_mainwindow();
96 return get_gobj_from_address(mainwin->ui_manager);
101 static PyObject *get_folderview_selected_folder(PyObject *self, PyObject *args)
105 mainwin = mainwindow_get_mainwindow();
106 if(mainwin && mainwin->folderview) {
108 item = folderview_get_selected_item(mainwin->folderview);
110 return clawsmail_folder_new(item);
115 static PyObject *get_folderview_selected_mailbox(PyObject *self, PyObject *args)
119 mainwin = mainwindow_get_mainwindow();
120 if(mainwin && mainwin->folderview) {
122 item = folderview_get_selected_item(mainwin->folderview);
125 id = folder_item_get_identifier(item);
126 /* If there is an id, it's a folder, not a mailbox */
132 return clawsmail_mailbox_new(item->folder);
138 static PyObject *folderview_select_row(PyObject *self, PyObject *args)
144 mainwin = mainwindow_get_mainwindow();
145 if(mainwin && mainwin->folderview) {
147 arg = PyTuple_GetItem(args, 0);
152 if(clawsmail_folder_check(arg)) {
154 item = clawsmail_folder_get_item(arg);
156 folderview_select(mainwin->folderview, item);
158 else if(clawsmail_mailbox_check(arg)) {
160 folder = clawsmail_mailbox_get_folder(arg);
161 if(folder && folder->node) {
162 folderview_select(mainwin->folderview, folder->node->data);
166 PyErr_SetString(PyExc_TypeError, "Bad argument type");
178 static gboolean setup_folderitem_node(GNode *item_node, GNode *item_parent, PyObject **pyparent)
180 PyObject *pynode, *children;
181 int retval, n_children, i_child;
184 /* create a python node for the folderitem node */
185 pynode = clawsmail_node_new(cm_module);
189 /* store Folder in pynode */
190 folder = clawsmail_folder_new(item_node->data);
191 retval = PyObject_SetAttrString(pynode, "data", folder);
198 if(pyparent && *pyparent) {
199 /* add this node to the parent's childs */
200 children = PyObject_GetAttrString(*pyparent, "children");
201 retval = PyList_Append(children, pynode);
214 /* call this function recursively for all children of the new node */
215 n_children = g_node_n_children(item_node);
216 for(i_child = 0; i_child < n_children; i_child++) {
217 if(!setup_folderitem_node(g_node_nth_child(item_node, i_child), item_node, &pynode)) {
227 static PyObject* get_folder_tree_from_folder(Folder *folder)
231 int n_children, i_child;
233 /* create root nodes */
234 root = clawsmail_node_new(cm_module);
238 n_children = g_node_n_children(folder->node);
239 for(i_child = 0; i_child < n_children; i_child++) {
240 if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
250 static PyObject* get_folder_tree_from_account_name(const char *str)
255 result = Py_BuildValue("[]");
259 for(walk = folder_get_list(); walk; walk = walk->next) {
260 Folder *folder = walk->data;
261 if(!str || !g_strcmp0(str, folder->name)) {
262 PyObject *tree_from_folder;
263 tree_from_folder = get_folder_tree_from_folder(folder);
264 if(tree_from_folder) {
266 retval = PyList_Append(result, tree_from_folder);
267 Py_DECREF(tree_from_folder);
282 static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
287 for(walk = folder_get_list(); walk; walk = walk->next) {
288 Folder *folder = walk->data;
292 root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
297 if(!setup_folderitem_node(root_node, NULL, &result))
304 PyErr_SetString(PyExc_LookupError, "Folder not found");
308 static PyObject* get_folder_tree(PyObject *self, PyObject *args)
316 retval = PyArg_ParseTuple(args, "|O", &arg);
321 /* calling possibilities:
322 * nothing, the mailbox name in a string, a Folder object */
324 /* no arguments: build up a list of folder trees */
325 if(PyTuple_Size(args) == 0) {
326 result = get_folder_tree_from_account_name(NULL);
328 else if(PyString_Check(arg)){
330 str = PyString_AsString(arg);
334 result = get_folder_tree_from_account_name(str);
336 else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
337 result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
339 else if(clawsmail_mailbox_check(arg)) {
340 result = get_folder_tree_from_folder(clawsmail_mailbox_get_folder(arg));
343 PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a Folder object, a Mailbox object, or a mailbox name string.");
350 static PyObject* quicksearch_search(PyObject *self, PyObject *args)
357 /* must be given exactly one argument, which is a string */
358 searchtype = prefs_common_get_prefs()->summary_quicksearch_type;
359 if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
362 mainwin = mainwindow_get_mainwindow();
363 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
364 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
368 qs = mainwin->summaryview->quicksearch;
369 quicksearch_set(qs, searchtype, string);
375 static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
380 mainwin = mainwindow_get_mainwindow();
381 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
382 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
386 qs = mainwin->summaryview->quicksearch;
387 quicksearch_set(qs, prefs_common_get_prefs()->summary_quicksearch_type, "");
393 static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
397 Py_ssize_t size, iEl;
400 mainwin = mainwindow_get_mainwindow();
401 if(!mainwin || !mainwin->summaryview) {
402 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
406 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
407 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
412 size = PyList_Size(olist);
413 for(iEl = 0; iEl < size; iEl++) {
414 PyObject *element = PyList_GET_ITEM(olist, iEl);
416 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
417 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
421 msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
424 summary_unselect_all(mainwin->summaryview);
425 summary_select_by_msg_list(mainwin->summaryview, msginfos);
426 g_slist_free(msginfos);
432 static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
438 mainwin = mainwindow_get_mainwindow();
439 if(!mainwin || !mainwin->summaryview) {
440 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
444 result = Py_BuildValue("[]");
448 list = summary_get_selected_msg_list(mainwin->summaryview);
449 for(walk = list; walk; walk = walk->next) {
451 msg = clawsmail_messageinfo_new(walk->data);
452 if(PyList_Append(result, msg) == -1) {
462 static PyObject* is_exiting(PyObject *self, PyObject *args)
464 if(claws_is_exiting())
470 static PyObject* get_tags(PyObject *self, PyObject *args)
473 PyObject *tags_tuple;
476 tags_list = tags_get_list();
477 num_tags = g_slist_length(tags_list);
479 tags_tuple = PyTuple_New(num_tags);
480 if(tags_tuple != NULL) {
482 PyObject *tag_object;
486 for(walk = tags_list; walk; walk = walk->next) {
487 tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
488 if(tag_object == NULL) {
489 Py_DECREF(tags_tuple);
492 PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
496 g_slist_free(tags_list);
501 static PyObject* get_accounts(PyObject *self, PyObject *args)
503 PyObject *accounts_tuple;
504 GList *accounts_list;
507 accounts_list = account_get_list();
509 accounts_tuple = PyTuple_New(g_list_length(accounts_list));
511 PyObject *account_object;
515 for(walk = accounts_list; walk; walk = walk->next) {
516 account_object = clawsmail_account_new(walk->data);
517 if(account_object == NULL) {
518 Py_DECREF(accounts_tuple);
521 PyTuple_SET_ITEM(accounts_tuple, iAccount++, account_object);
525 return accounts_tuple;
528 static PyObject* get_mailboxes(PyObject *self, PyObject *args)
530 PyObject *mailboxes_tuple;
531 GList *mailboxes_list;
534 mailboxes_list = folder_get_list();
536 mailboxes_tuple = PyTuple_New(g_list_length(mailboxes_list));
537 if(mailboxes_tuple) {
538 PyObject *mailbox_object;
542 for(walk = mailboxes_list; walk; walk = walk->next) {
543 mailbox_object = clawsmail_mailbox_new(walk->data);
544 if(mailbox_object == NULL) {
545 Py_DECREF(mailboxes_tuple);
548 PyTuple_SET_ITEM(mailboxes_tuple, iMailbox++, mailbox_object);
552 return mailboxes_tuple;
556 static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
561 retval = PyArg_ParseTuple(args, "s", &tag_str);
565 if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
566 /* tag name is reserved, raise KeyError */
567 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
571 tags_add_tag(tag_str);
576 static PyObject* delete_tag(PyObject *self, PyObject *args)
583 retval = PyArg_ParseTuple(args, "s", &tag_str);
587 tag_id = tags_get_id_for_str(tag_str);
589 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
593 tags_remove_tag(tag_id);
596 mainwin = mainwindow_get_mainwindow();
598 summary_redisplay_msg(mainwin->summaryview);
604 static PyObject* rename_tag(PyObject *self, PyObject *args)
607 const char *old_tag_str;
608 const char *new_tag_str;
612 retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
616 if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
617 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
621 tag_id = tags_get_id_for_str(old_tag_str);
623 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
627 tags_update_tag(tag_id, new_tag_str);
630 mainwin = mainwindow_get_mainwindow();
632 summary_redisplay_msg(mainwin->summaryview);
637 static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
639 Py_ssize_t size, iEl;
640 FolderItem *folderitem;
644 folderitem = clawsmail_folder_get_item(folder);
646 PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
650 size = PyList_Size(messagelist);
651 for(iEl = 0; iEl < size; iEl++) {
652 PyObject *element = PyList_GET_ITEM(messagelist, iEl);
655 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
656 PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
660 msginfo = clawsmail_messageinfo_get_msginfo(element);
662 PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
666 procmsg_msginfo_set_to_folder(msginfo, folderitem);
667 *list = g_slist_prepend(*list, msginfo);
673 static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
675 PyObject *messagelist;
680 retval = PyArg_ParseTuple(args, "O!O!",
681 &PyList_Type, &messagelist,
682 clawsmail_folder_get_type_object(), &folder);
686 folder_item_update_freeze();
688 if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
692 procmsg_move_messages(list);
694 procmsg_copy_messages(list);
696 folder_item_update_thaw();
701 folder_item_update_thaw();
706 static PyObject* move_messages(PyObject *self, PyObject *args)
708 return move_or_copy_messages(self, args, TRUE);
712 static PyObject* copy_messages(PyObject *self, PyObject *args)
714 return move_or_copy_messages(self, args, FALSE);
717 static PyObject* get_current_account(PyObject *self, PyObject *args)
719 PrefsAccount *account;
720 account = account_get_cur_account();
722 return clawsmail_account_new(account);
728 static PyObject* get_default_account(PyObject *self, PyObject *args)
730 PrefsAccount *account;
731 account = account_get_default();
733 return clawsmail_account_new(account);
740 static PyMethodDef ClawsMailMethods[] = {
742 {"get_mainwindow_action_group", get_mainwindow_action_group, METH_NOARGS,
743 "get_mainwindow_action_group() - get action group of main window menu\n"
745 "Returns the gtk.ActionGroup for the main window."},
747 {"get_mainwindow_ui_manager", get_mainwindow_ui_manager, METH_NOARGS,
748 "get_mainwindow_ui_manager() - get ui manager of main window\n"
750 "Returns the gtk.UIManager for the main window."},
752 {"get_folder_tree", get_folder_tree, METH_VARARGS,
753 "get_folder_tree([root]) - get a folder tree\n"
755 "Without arguments, get a list of folder trees for all mailboxes.\n"
757 "If the optional root argument is a clawsmail.Folder, the function\n"
758 "returns a tree of subfolders with the given folder as root element.\n"
760 "If the optional root argument is a clawsmail.Mailbox, the function\n"
761 "returns a tree of folders with the given mailbox as root element.\n"
763 "If the optional root argument is a string, it is supposed to be a\n"
764 "mailbox name. The function then returns a tree of folders of that mailbox.\n"
766 "In any case, a tree consists of elements of the type clawsmail.Node."},
768 {"get_folderview_selected_folder", get_folderview_selected_folder, METH_NOARGS,
769 "get_folderview_selected_folder() - get selected folder in folderview\n"
771 "Returns the currently selected folder as a clawsmail.Folder or None if no folder is selected."},
772 {"folderview_select_folder", folderview_select_row, METH_VARARGS,
773 "folderview_select_folder(folder) - select folder in folderview\n"
775 "Takes an argument of type clawsmail.Folder, and selects the corresponding folder.\n"
777 "DEPRECATED: Use folderview_select() instead."},
779 {"get_folderview_selected_mailbox", get_folderview_selected_mailbox, METH_NOARGS,
780 "get_folderview_selected_mailbox() - get selected mailbox in folderview\n"
782 "Returns the currently selected mailbox as a clawsmail.Mailbox or None if no mailbox is selected."},
784 {"folderview_select", folderview_select_row, METH_VARARGS,
785 "folderview_select(arg) - select folder or a mailbox in folderview\n"
787 "Takes an argument of type clawsmail.Folder or clawsmail.Mailbox, and selects the corresponding\n"
788 "row in the folder view."},
790 {"quicksearch_search", quicksearch_search, METH_VARARGS,
791 "quicksearch_search(string [, type]) - perform a quicksearch\n"
793 "Perform a quicksearch of the given string. The optional type argument can be\n"
794 "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
795 "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
796 "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
797 "string corresponding to the type."},
799 {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
800 "quicksearch_clear() - clear the quicksearch"},
802 {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
803 "get_summaryview_selected_message_list() - get selected message list\n"
805 "Get a list of clawsmail.MessageInfo objects of the current selection."},
807 {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
808 "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
810 "Select a list of clawsmail.MessageInfo objects in the summary view."},
812 {"is_exiting", is_exiting, METH_NOARGS,
813 "is_exiting() - test whether Claws Mail is currently exiting\n"
815 "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
816 "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
817 "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
820 {"move_messages", move_messages, METH_VARARGS,
821 "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
823 "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
824 "The target_folder argument has to be a clawsmail.Folder object."},
826 {"copy_messages", copy_messages, METH_VARARGS,
827 "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
829 "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
830 "The target_folder argument has to be a clawsmail.Folder object."},
832 {"get_tags", get_tags, METH_NOARGS,
833 "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
835 "Get a tuple of strings representing all tags that are defined in Claws Mail."},
837 {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
838 "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
840 "This function creates the given tag if it does not exist yet.\n"
841 "It is not an error if the tag already exists. In this case, this function does nothing.\n"
842 "However, if a reserved tag name is chosen, a ValueError exception is raised."},
844 {"delete_tag", delete_tag, METH_VARARGS,
845 "delete_tag(tag) - delete a tag\n"
847 "This function deletes an existing tag.\n"
848 "Raises a KeyError exception if the tag does not exist."},
850 {"rename_tag", rename_tag, METH_VARARGS,
851 "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
853 "This function renames an existing tag.\n"
854 "Raises a KeyError exception if the tag does not exist.\n"
855 "Raises a ValueError exception if the old or new tag name is a reserved name."},
857 {"get_accounts", get_accounts, METH_NOARGS,
858 "get_accounts() - get a tuple of all accounts that Claws Mail knows about\n"
860 "Get a tuple of Account objects representing all accounts that are defined in Claws Mail."},
862 {"get_current_account", get_current_account, METH_NOARGS,
863 "get_current_account() - get the current account\n"
865 "Return the object representing the currently selected account."},
867 {"get_default_account", get_default_account, METH_NOARGS,
868 "get_default_account() - get the default account\n"
870 "Return the object representing the default account."},
872 {"get_mailboxes", get_mailboxes, METH_NOARGS,
873 "get_mailboxes() - get a tuple of all mailboxes that Claws Mail knows about\n"
875 "Get a tuple of Mailbox objects representing all mailboxes that are defined in Claws Mail."},
878 {"__gobj", private_wrap_gobj, METH_VARARGS,
879 "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
881 "For internal usage only."},
883 {NULL, NULL, 0, NULL}
886 static gboolean add_miscstuff(PyObject *module)
892 "QUICK_SEARCH_SUBJECT = 0\n"
893 "QUICK_SEARCH_FROM = 1\n"
894 "QUICK_SEARCH_TO = 2\n"
895 "QUICK_SEARCH_EXTENDED = 3\n"
896 "QUICK_SEARCH_MIXED = 4\n"
897 "QUICK_SEARCH_TAG = 5\n"
899 dict = PyModule_GetDict(module);
900 res = PyRun_String(cmd, Py_file_input, dict, dict);
901 retval = (res != NULL);
907 PyMODINIT_FUNC initclawsmail(void)
912 cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
913 "This module can be used to access some of Claws Mail's data structures\n"
914 "in order to extend or modify the user interface or automate repetitive tasks.\n"
916 "Whenever possible, the interface works with standard GTK+ widgets\n"
917 "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
918 "to find out about all possible options.\n"
920 "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
921 "If you're missing something specific, try contacting the author.");
923 /* add module member "compose_window" set to None */
925 if (PyModule_AddObject(cm_module, "compose_window", Py_None) == -1)
926 debug_print("Error: Could not add object 'compose_window'\n");
928 /* initialize classes */
929 ok = ok && cmpy_add_node(cm_module);
930 ok = ok && cmpy_add_composewindow(cm_module);
931 ok = ok && cmpy_add_folder(cm_module);
932 ok = ok && cmpy_add_messageinfo(cm_module);
933 ok = ok && cmpy_add_account(cm_module);
934 ok = ok && cmpy_add_folderproperties(cm_module);
935 ok = ok && cmpy_add_mailbox(cm_module);
937 /* initialize misc things */
939 add_miscstuff(cm_module);
943 void put_composewindow_into_module(Compose *compose)
947 pycompose = clawsmail_compose_new(cm_module, compose);
948 PyObject_SetAttrString(cm_module, "compose_window", pycompose);
949 Py_DECREF(pycompose);