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 #include <pygobject.h>
37 #include <pygtk/pygtk.h>
40 #include "mainwindow.h"
41 #include "summaryview.h"
42 #include "quicksearch.h"
44 #include "prefs_common.h"
45 #include "common/tags.h"
50 static PyObject *cm_module = NULL;
52 PyObject* get_gobj_from_address(gpointer addr)
56 if (!G_IS_OBJECT(addr))
64 return pygobject_new(obj);
67 static PyObject* private_wrap_gobj(PyObject *self, PyObject *args)
71 if (!PyArg_ParseTuple(args, "l", &addr))
74 return get_gobj_from_address(addr);
77 static PyObject *get_mainwindow_action_group(PyObject *self, PyObject *args)
81 mainwin = mainwindow_get_mainwindow();
83 return get_gobj_from_address(mainwin->action_group);
88 static PyObject *get_mainwindow_ui_manager(PyObject *self, PyObject *args)
92 mainwin = mainwindow_get_mainwindow();
94 return get_gobj_from_address(mainwin->ui_manager);
99 static PyObject *get_folderview_selected_folder(PyObject *self, PyObject *args)
103 mainwin = mainwindow_get_mainwindow();
104 if(mainwin && mainwin->folderview) {
106 item = folderview_get_selected_item(mainwin->folderview);
108 return clawsmail_folder_new(item);
113 static PyObject *get_folderview_selected_mailbox(PyObject *self, PyObject *args)
117 mainwin = mainwindow_get_mainwindow();
118 if(mainwin && mainwin->folderview) {
120 item = folderview_get_selected_item(mainwin->folderview);
123 id = folder_item_get_identifier(item);
124 /* If there is an id, it's a folder, not a mailbox */
130 return clawsmail_mailbox_new(item->folder);
137 static PyObject *folderview_select_folder(PyObject *self, PyObject *args)
141 mainwin = mainwindow_get_mainwindow();
142 if(mainwin && mainwin->folderview) {
145 folder = PyTuple_GetItem(args, 0);
149 item = clawsmail_folder_get_item(folder);
152 folderview_select(mainwin->folderview, item);
158 static gboolean setup_folderitem_node(GNode *item_node, GNode *item_parent, PyObject **pyparent)
160 PyObject *pynode, *children;
161 int retval, n_children, i_child;
164 /* create a python node for the folderitem node */
165 pynode = clawsmail_node_new(cm_module);
169 /* store Folder in pynode */
170 folder = clawsmail_folder_new(item_node->data);
171 retval = PyObject_SetAttrString(pynode, "data", folder);
178 if(pyparent && *pyparent) {
179 /* add this node to the parent's childs */
180 children = PyObject_GetAttrString(*pyparent, "children");
181 retval = PyList_Append(children, pynode);
194 /* call this function recursively for all children of the new node */
195 n_children = g_node_n_children(item_node);
196 for(i_child = 0; i_child < n_children; i_child++) {
197 if(!setup_folderitem_node(g_node_nth_child(item_node, i_child), item_node, &pynode)) {
207 static PyObject* get_folder_tree_from_folder(Folder *folder)
211 int n_children, i_child;
213 /* create root nodes */
214 root = clawsmail_node_new(cm_module);
218 n_children = g_node_n_children(folder->node);
219 for(i_child = 0; i_child < n_children; i_child++) {
220 if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
230 static PyObject* get_folder_tree_from_account_name(const char *str)
235 result = Py_BuildValue("[]");
239 for(walk = folder_get_list(); walk; walk = walk->next) {
240 Folder *folder = walk->data;
241 if(!str || !g_strcmp0(str, folder->name)) {
242 PyObject *tree_from_folder;
243 tree_from_folder = get_folder_tree_from_folder(folder);
244 if(tree_from_folder) {
246 retval = PyList_Append(result, tree_from_folder);
247 Py_DECREF(tree_from_folder);
262 static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
267 for(walk = folder_get_list(); walk; walk = walk->next) {
268 Folder *folder = walk->data;
272 root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
277 if(!setup_folderitem_node(root_node, NULL, &result))
284 PyErr_SetString(PyExc_LookupError, "Folder not found");
288 static PyObject* get_folder_tree(PyObject *self, PyObject *args)
296 retval = PyArg_ParseTuple(args, "|O", &arg);
301 /* calling possibilities:
302 * nothing, the mailbox name in a string, a Folder object */
304 /* no arguments: build up a list of folder trees */
305 if(PyTuple_Size(args) == 0) {
306 result = get_folder_tree_from_account_name(NULL);
308 else if(PyString_Check(arg)){
310 str = PyString_AsString(arg);
314 result = get_folder_tree_from_account_name(str);
316 else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
317 result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
319 else if(PyObject_TypeCheck(arg, clawsmail_mailbox_get_type_object())) {
320 result = get_folder_tree_from_folder(clawsmail_mailbox_get_folder(arg));
323 PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a Folder object, a Mailbox object, or a mailbox name string.");
330 static PyObject* quicksearch_search(PyObject *self, PyObject *args)
337 /* must be given exactly one argument, which is a string */
338 searchtype = prefs_common.summary_quicksearch_type;
339 if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
342 mainwin = mainwindow_get_mainwindow();
343 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
344 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
348 qs = mainwin->summaryview->quicksearch;
349 quicksearch_set(qs, searchtype, string);
355 static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
360 mainwin = mainwindow_get_mainwindow();
361 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
362 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
366 qs = mainwin->summaryview->quicksearch;
367 quicksearch_set(qs, prefs_common.summary_quicksearch_type, "");
373 static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
377 Py_ssize_t size, iEl;
380 mainwin = mainwindow_get_mainwindow();
381 if(!mainwin || !mainwin->summaryview) {
382 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
386 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
387 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
392 size = PyList_Size(olist);
393 for(iEl = 0; iEl < size; iEl++) {
394 PyObject *element = PyList_GET_ITEM(olist, iEl);
396 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
397 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
401 msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
404 summary_unselect_all(mainwin->summaryview);
405 summary_select_by_msg_list(mainwin->summaryview, msginfos);
406 g_slist_free(msginfos);
412 static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
418 mainwin = mainwindow_get_mainwindow();
419 if(!mainwin || !mainwin->summaryview) {
420 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
424 result = Py_BuildValue("[]");
428 list = summary_get_selected_msg_list(mainwin->summaryview);
429 for(walk = list; walk; walk = walk->next) {
431 msg = clawsmail_messageinfo_new(walk->data);
432 if(PyList_Append(result, msg) == -1) {
442 static PyObject* is_exiting(PyObject *self, PyObject *args)
444 if(claws_is_exiting())
450 static PyObject* get_tags(PyObject *self, PyObject *args)
453 PyObject *tags_tuple;
456 tags_list = tags_get_list();
457 num_tags = g_slist_length(tags_list);
459 tags_tuple = PyTuple_New(num_tags);
460 if(tags_tuple != NULL) {
462 PyObject *tag_object;
466 for(walk = tags_list; walk; walk = walk->next) {
467 tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
468 if(tag_object == NULL) {
469 Py_DECREF(tags_tuple);
472 PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
476 g_slist_free(tags_list);
481 static PyObject* get_accounts(PyObject *self, PyObject *args)
483 PyObject *accounts_tuple;
484 GList *accounts_list;
487 accounts_list = account_get_list();
489 accounts_tuple = PyTuple_New(g_list_length(accounts_list));
491 PyObject *account_object;
495 for(walk = accounts_list; walk; walk = walk->next) {
496 account_object = clawsmail_account_new(walk->data);
497 if(account_object == NULL) {
498 Py_DECREF(accounts_tuple);
501 PyTuple_SET_ITEM(accounts_tuple, iAccount++, account_object);
505 return accounts_tuple;
508 static PyObject* get_mailboxes(PyObject *self, PyObject *args)
510 PyObject *mailboxes_tuple;
511 GList *mailboxes_list;
514 mailboxes_list = folder_get_list();
516 mailboxes_tuple = PyTuple_New(g_list_length(mailboxes_list));
517 if(mailboxes_tuple) {
518 PyObject *mailbox_object;
522 for(walk = mailboxes_list; walk; walk = walk->next) {
523 mailbox_object = clawsmail_mailbox_new(walk->data);
524 if(mailbox_object == NULL) {
525 Py_DECREF(mailboxes_tuple);
528 PyTuple_SET_ITEM(mailboxes_tuple, iMailbox++, mailbox_object);
532 return mailboxes_tuple;
536 static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
541 retval = PyArg_ParseTuple(args, "s", &tag_str);
545 if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
546 /* tag name is reserved, raise KeyError */
547 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
551 tags_add_tag(tag_str);
556 static PyObject* delete_tag(PyObject *self, PyObject *args)
563 retval = PyArg_ParseTuple(args, "s", &tag_str);
567 tag_id = tags_get_id_for_str(tag_str);
569 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
573 tags_remove_tag(tag_id);
576 mainwin = mainwindow_get_mainwindow();
578 summary_redisplay_msg(mainwin->summaryview);
584 static PyObject* rename_tag(PyObject *self, PyObject *args)
587 const char *old_tag_str;
588 const char *new_tag_str;
592 retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
596 if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
597 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
601 tag_id = tags_get_id_for_str(old_tag_str);
603 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
607 tags_update_tag(tag_id, new_tag_str);
610 mainwin = mainwindow_get_mainwindow();
612 summary_redisplay_msg(mainwin->summaryview);
617 static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
619 Py_ssize_t size, iEl;
620 FolderItem *folderitem;
624 folderitem = clawsmail_folder_get_item(folder);
626 PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
630 size = PyList_Size(messagelist);
631 for(iEl = 0; iEl < size; iEl++) {
632 PyObject *element = PyList_GET_ITEM(messagelist, iEl);
635 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
636 PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
640 msginfo = clawsmail_messageinfo_get_msginfo(element);
642 PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
646 procmsg_msginfo_set_to_folder(msginfo, folderitem);
647 *list = g_slist_prepend(*list, msginfo);
653 static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
655 PyObject *messagelist;
660 retval = PyArg_ParseTuple(args, "O!O!",
661 &PyList_Type, &messagelist,
662 clawsmail_folder_get_type_object(), &folder);
666 folder_item_update_freeze();
668 if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
672 procmsg_move_messages(list);
674 procmsg_copy_messages(list);
676 folder_item_update_thaw();
681 folder_item_update_thaw();
686 static PyObject* move_messages(PyObject *self, PyObject *args)
688 return move_or_copy_messages(self, args, TRUE);
692 static PyObject* copy_messages(PyObject *self, PyObject *args)
694 return move_or_copy_messages(self, args, FALSE);
697 static PyObject* get_current_account(PyObject *self, PyObject *args)
699 PrefsAccount *account;
700 account = account_get_cur_account();
702 return clawsmail_account_new(account);
708 static PyObject* get_default_account(PyObject *self, PyObject *args)
710 PrefsAccount *account;
711 account = account_get_default();
713 return clawsmail_account_new(account);
720 static PyMethodDef ClawsMailMethods[] = {
722 {"get_mainwindow_action_group", get_mainwindow_action_group, METH_NOARGS,
723 "get_mainwindow_action_group() - get action group of main window menu\n"
725 "Returns the gtk.ActionGroup for the main window."},
727 {"get_mainwindow_ui_manager", get_mainwindow_ui_manager, METH_NOARGS,
728 "get_mainwindow_ui_manager() - get ui manager of main window\n"
730 "Returns the gtk.UIManager for the main window."},
732 {"get_folder_tree", get_folder_tree, METH_VARARGS,
733 "get_folder_tree([root]) - get a folder tree\n"
735 "Without arguments, get a list of folder trees for all mailboxes.\n"
737 "If the optional root argument is a clawsmail.Folder, the function\n"
738 "returns a tree of subfolders with the given folder as root element.\n"
740 "If the optional root argument is a clawsmail.Mailbox, the function\n"
741 "returns a tree of folders with the given mailbox as root element.\n"
743 "If the optional root argument is a string, it is supposed to be a\n"
744 "mailbox name. The function then returns a tree of folders of that mailbox.\n"
746 "In any case, a tree consists of elements of the type clawsmail.Node."},
748 {"get_folderview_selected_folder", get_folderview_selected_folder, METH_NOARGS,
749 "get_folderview_selected_folder() - get selected folder in folderview\n"
751 "Returns the currently selected folder as a clawsmail.Folder or None if no folder is selected."},
752 {"folderview_select_folder", folderview_select_folder, METH_VARARGS,
753 "folderview_select_folder(folder) - select folder in folderview\n"
755 "Takes an argument of type clawsmail.Folder, and selects the corresponding folder."},
757 {"get_folderview_selected_mailbox", get_folderview_selected_mailbox, METH_NOARGS,
758 "get_folderview_selected_mailbox() - get selected mailbox in folderview\n"
760 "Returns the currently selected mailbox as a clawsmail.Mailbox or None if no mailbox is selected."},
762 {"quicksearch_search", quicksearch_search, METH_VARARGS,
763 "quicksearch_search(string [, type]) - perform a quicksearch\n"
765 "Perform a quicksearch of the given string. The optional type argument can be\n"
766 "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
767 "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
768 "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
769 "string corresponding to the type."},
771 {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
772 "quicksearch_clear() - clear the quicksearch"},
774 {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
775 "get_summaryview_selected_message_list() - get selected message list\n"
777 "Get a list of clawsmail.MessageInfo objects of the current selection."},
779 {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
780 "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
782 "Select a list of clawsmail.MessageInfo objects in the summary view."},
784 {"is_exiting", is_exiting, METH_NOARGS,
785 "is_exiting() - test whether Claws Mail is currently exiting\n"
787 "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
788 "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
789 "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
792 {"move_messages", move_messages, METH_VARARGS,
793 "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
795 "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
796 "The target_folder argument has to be a clawsmail.Folder object."},
798 {"copy_messages", copy_messages, METH_VARARGS,
799 "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
801 "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
802 "The target_folder argument has to be a clawsmail.Folder object."},
804 {"get_tags", get_tags, METH_NOARGS,
805 "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
807 "Get a tuple of strings representing all tags that are defined in Claws Mail."},
809 {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
810 "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
812 "This function creates the given tag if it does not exist yet.\n"
813 "It is not an error if the tag already exists. In this case, this function does nothing.\n"
814 "However, if a reserved tag name is chosen, a ValueError exception is raised."},
816 {"delete_tag", delete_tag, METH_VARARGS,
817 "delete_tag(tag) - delete a tag\n"
819 "This function deletes an existing tag.\n"
820 "Raises a KeyError exception if the tag does not exist."},
822 {"rename_tag", rename_tag, METH_VARARGS,
823 "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
825 "This function renames an existing tag.\n"
826 "Raises a KeyError exception if the tag does not exist.\n"
827 "Raises a ValueError exception if the old or new tag name is a reserved name."},
829 {"get_accounts", get_accounts, METH_NOARGS,
830 "get_accounts() - get a tuple of all accounts that Claws Mail knows about\n"
832 "Get a tuple of Account objects representing all accounts that are defined in Claws Mail."},
834 {"get_current_account", get_current_account, METH_NOARGS,
835 "get_current_account() - get the current account\n"
837 "Return the object representing the currently selected account."},
839 {"get_default_account", get_default_account, METH_NOARGS,
840 "get_default_account() - get the default account\n"
842 "Return the object representing the default account."},
844 {"get_mailboxes", get_mailboxes, METH_NOARGS,
845 "get_mailboxes() - get a tuple of all mailboxes that Claws Mail knows about\n"
847 "Get a tuple of Mailbox objects representing all mailboxes that are defined in Claws Mail."},
850 {"__gobj", private_wrap_gobj, METH_VARARGS,
851 "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
853 "For internal usage only."},
855 {NULL, NULL, 0, NULL}
858 static gboolean add_miscstuff(PyObject *module)
864 "QUICK_SEARCH_SUBJECT = 0\n"
865 "QUICK_SEARCH_FROM = 1\n"
866 "QUICK_SEARCH_TO = 2\n"
867 "QUICK_SEARCH_EXTENDED = 3\n"
868 "QUICK_SEARCH_MIXED = 4\n"
869 "QUICK_SEARCH_TAG = 5\n"
871 dict = PyModule_GetDict(module);
872 res = PyRun_String(cmd, Py_file_input, dict, dict);
873 retval = (res != NULL);
879 PyMODINIT_FUNC initclawsmail(void)
884 cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
885 "This module can be used to access some of Claws Mail's data structures\n"
886 "in order to extend or modify the user interface or automate repetitive tasks.\n"
888 "Whenever possible, the interface works with standard GTK+ widgets\n"
889 "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
890 "to find out about all possible options.\n"
892 "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
893 "If you're missing something specific, try contacting the author.");
895 /* add module member "compose_window" set to None */
897 PyModule_AddObject(cm_module, "compose_window", Py_None);
899 /* initialize classes */
900 ok = ok && cmpy_add_node(cm_module);
901 ok = ok && cmpy_add_composewindow(cm_module);
902 ok = ok && cmpy_add_folder(cm_module);
903 ok = ok && cmpy_add_messageinfo(cm_module);
904 ok = ok && cmpy_add_account(cm_module);
905 ok = ok && cmpy_add_folderproperties(cm_module);
906 ok = ok && cmpy_add_mailbox(cm_module);
908 /* initialize misc things */
910 add_miscstuff(cm_module);
914 void put_composewindow_into_module(Compose *compose)
918 pycompose = clawsmail_compose_new(cm_module, compose);
919 PyObject_SetAttrString(cm_module, "compose_window", pycompose);
920 Py_DECREF(pycompose);