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);
136 static PyObject *folderview_select_row(PyObject *self, PyObject *args)
142 mainwin = mainwindow_get_mainwindow();
143 if(mainwin && mainwin->folderview) {
145 arg = PyTuple_GetItem(args, 0);
150 if(clawsmail_folder_check(arg)) {
152 item = clawsmail_folder_get_item(arg);
154 folderview_select(mainwin->folderview, item);
156 else if(clawsmail_mailbox_check(arg)) {
158 folder = clawsmail_mailbox_get_folder(arg);
159 if(folder && folder->node) {
160 folderview_select(mainwin->folderview, folder->node->data);
164 PyErr_SetString(PyExc_TypeError, "Bad argument type");
176 static gboolean setup_folderitem_node(GNode *item_node, GNode *item_parent, PyObject **pyparent)
178 PyObject *pynode, *children;
179 int retval, n_children, i_child;
182 /* create a python node for the folderitem node */
183 pynode = clawsmail_node_new(cm_module);
187 /* store Folder in pynode */
188 folder = clawsmail_folder_new(item_node->data);
189 retval = PyObject_SetAttrString(pynode, "data", folder);
196 if(pyparent && *pyparent) {
197 /* add this node to the parent's childs */
198 children = PyObject_GetAttrString(*pyparent, "children");
199 retval = PyList_Append(children, pynode);
212 /* call this function recursively for all children of the new node */
213 n_children = g_node_n_children(item_node);
214 for(i_child = 0; i_child < n_children; i_child++) {
215 if(!setup_folderitem_node(g_node_nth_child(item_node, i_child), item_node, &pynode)) {
225 static PyObject* get_folder_tree_from_folder(Folder *folder)
229 int n_children, i_child;
231 /* create root nodes */
232 root = clawsmail_node_new(cm_module);
236 n_children = g_node_n_children(folder->node);
237 for(i_child = 0; i_child < n_children; i_child++) {
238 if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
248 static PyObject* get_folder_tree_from_account_name(const char *str)
253 result = Py_BuildValue("[]");
257 for(walk = folder_get_list(); walk; walk = walk->next) {
258 Folder *folder = walk->data;
259 if(!str || !g_strcmp0(str, folder->name)) {
260 PyObject *tree_from_folder;
261 tree_from_folder = get_folder_tree_from_folder(folder);
262 if(tree_from_folder) {
264 retval = PyList_Append(result, tree_from_folder);
265 Py_DECREF(tree_from_folder);
280 static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
285 for(walk = folder_get_list(); walk; walk = walk->next) {
286 Folder *folder = walk->data;
290 root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
295 if(!setup_folderitem_node(root_node, NULL, &result))
302 PyErr_SetString(PyExc_LookupError, "Folder not found");
306 static PyObject* get_folder_tree(PyObject *self, PyObject *args)
314 retval = PyArg_ParseTuple(args, "|O", &arg);
319 /* calling possibilities:
320 * nothing, the mailbox name in a string, a Folder object */
322 /* no arguments: build up a list of folder trees */
323 if(PyTuple_Size(args) == 0) {
324 result = get_folder_tree_from_account_name(NULL);
326 else if(PyString_Check(arg)){
328 str = PyString_AsString(arg);
332 result = get_folder_tree_from_account_name(str);
334 else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
335 result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
337 else if(clawsmail_mailbox_check(arg)) {
338 result = get_folder_tree_from_folder(clawsmail_mailbox_get_folder(arg));
341 PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a Folder object, a Mailbox object, or a mailbox name string.");
348 static PyObject* quicksearch_search(PyObject *self, PyObject *args)
355 /* must be given exactly one argument, which is a string */
356 searchtype = prefs_common.summary_quicksearch_type;
357 if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
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, searchtype, string);
373 static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
378 mainwin = mainwindow_get_mainwindow();
379 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
380 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
384 qs = mainwin->summaryview->quicksearch;
385 quicksearch_set(qs, prefs_common.summary_quicksearch_type, "");
391 static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
395 Py_ssize_t size, iEl;
398 mainwin = mainwindow_get_mainwindow();
399 if(!mainwin || !mainwin->summaryview) {
400 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
404 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
405 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
410 size = PyList_Size(olist);
411 for(iEl = 0; iEl < size; iEl++) {
412 PyObject *element = PyList_GET_ITEM(olist, iEl);
414 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
415 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
419 msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
422 summary_unselect_all(mainwin->summaryview);
423 summary_select_by_msg_list(mainwin->summaryview, msginfos);
424 g_slist_free(msginfos);
430 static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
436 mainwin = mainwindow_get_mainwindow();
437 if(!mainwin || !mainwin->summaryview) {
438 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
442 result = Py_BuildValue("[]");
446 list = summary_get_selected_msg_list(mainwin->summaryview);
447 for(walk = list; walk; walk = walk->next) {
449 msg = clawsmail_messageinfo_new(walk->data);
450 if(PyList_Append(result, msg) == -1) {
460 static PyObject* is_exiting(PyObject *self, PyObject *args)
462 if(claws_is_exiting())
468 static PyObject* get_tags(PyObject *self, PyObject *args)
471 PyObject *tags_tuple;
474 tags_list = tags_get_list();
475 num_tags = g_slist_length(tags_list);
477 tags_tuple = PyTuple_New(num_tags);
478 if(tags_tuple != NULL) {
480 PyObject *tag_object;
484 for(walk = tags_list; walk; walk = walk->next) {
485 tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
486 if(tag_object == NULL) {
487 Py_DECREF(tags_tuple);
490 PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
494 g_slist_free(tags_list);
499 static PyObject* get_accounts(PyObject *self, PyObject *args)
501 PyObject *accounts_tuple;
502 GList *accounts_list;
505 accounts_list = account_get_list();
507 accounts_tuple = PyTuple_New(g_list_length(accounts_list));
509 PyObject *account_object;
513 for(walk = accounts_list; walk; walk = walk->next) {
514 account_object = clawsmail_account_new(walk->data);
515 if(account_object == NULL) {
516 Py_DECREF(accounts_tuple);
519 PyTuple_SET_ITEM(accounts_tuple, iAccount++, account_object);
523 return accounts_tuple;
526 static PyObject* get_mailboxes(PyObject *self, PyObject *args)
528 PyObject *mailboxes_tuple;
529 GList *mailboxes_list;
532 mailboxes_list = folder_get_list();
534 mailboxes_tuple = PyTuple_New(g_list_length(mailboxes_list));
535 if(mailboxes_tuple) {
536 PyObject *mailbox_object;
540 for(walk = mailboxes_list; walk; walk = walk->next) {
541 mailbox_object = clawsmail_mailbox_new(walk->data);
542 if(mailbox_object == NULL) {
543 Py_DECREF(mailboxes_tuple);
546 PyTuple_SET_ITEM(mailboxes_tuple, iMailbox++, mailbox_object);
550 return mailboxes_tuple;
554 static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
559 retval = PyArg_ParseTuple(args, "s", &tag_str);
563 if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
564 /* tag name is reserved, raise KeyError */
565 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
569 tags_add_tag(tag_str);
574 static PyObject* delete_tag(PyObject *self, PyObject *args)
581 retval = PyArg_ParseTuple(args, "s", &tag_str);
585 tag_id = tags_get_id_for_str(tag_str);
587 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
591 tags_remove_tag(tag_id);
594 mainwin = mainwindow_get_mainwindow();
596 summary_redisplay_msg(mainwin->summaryview);
602 static PyObject* rename_tag(PyObject *self, PyObject *args)
605 const char *old_tag_str;
606 const char *new_tag_str;
610 retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
614 if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
615 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
619 tag_id = tags_get_id_for_str(old_tag_str);
621 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
625 tags_update_tag(tag_id, new_tag_str);
628 mainwin = mainwindow_get_mainwindow();
630 summary_redisplay_msg(mainwin->summaryview);
635 static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
637 Py_ssize_t size, iEl;
638 FolderItem *folderitem;
642 folderitem = clawsmail_folder_get_item(folder);
644 PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
648 size = PyList_Size(messagelist);
649 for(iEl = 0; iEl < size; iEl++) {
650 PyObject *element = PyList_GET_ITEM(messagelist, iEl);
653 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
654 PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
658 msginfo = clawsmail_messageinfo_get_msginfo(element);
660 PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
664 procmsg_msginfo_set_to_folder(msginfo, folderitem);
665 *list = g_slist_prepend(*list, msginfo);
671 static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
673 PyObject *messagelist;
678 retval = PyArg_ParseTuple(args, "O!O!",
679 &PyList_Type, &messagelist,
680 clawsmail_folder_get_type_object(), &folder);
684 folder_item_update_freeze();
686 if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
690 procmsg_move_messages(list);
692 procmsg_copy_messages(list);
694 folder_item_update_thaw();
699 folder_item_update_thaw();
704 static PyObject* move_messages(PyObject *self, PyObject *args)
706 return move_or_copy_messages(self, args, TRUE);
710 static PyObject* copy_messages(PyObject *self, PyObject *args)
712 return move_or_copy_messages(self, args, FALSE);
715 static PyObject* get_current_account(PyObject *self, PyObject *args)
717 PrefsAccount *account;
718 account = account_get_cur_account();
720 return clawsmail_account_new(account);
726 static PyObject* get_default_account(PyObject *self, PyObject *args)
728 PrefsAccount *account;
729 account = account_get_default();
731 return clawsmail_account_new(account);
738 static PyMethodDef ClawsMailMethods[] = {
740 {"get_mainwindow_action_group", get_mainwindow_action_group, METH_NOARGS,
741 "get_mainwindow_action_group() - get action group of main window menu\n"
743 "Returns the gtk.ActionGroup for the main window."},
745 {"get_mainwindow_ui_manager", get_mainwindow_ui_manager, METH_NOARGS,
746 "get_mainwindow_ui_manager() - get ui manager of main window\n"
748 "Returns the gtk.UIManager for the main window."},
750 {"get_folder_tree", get_folder_tree, METH_VARARGS,
751 "get_folder_tree([root]) - get a folder tree\n"
753 "Without arguments, get a list of folder trees for all mailboxes.\n"
755 "If the optional root argument is a clawsmail.Folder, the function\n"
756 "returns a tree of subfolders with the given folder as root element.\n"
758 "If the optional root argument is a clawsmail.Mailbox, the function\n"
759 "returns a tree of folders with the given mailbox as root element.\n"
761 "If the optional root argument is a string, it is supposed to be a\n"
762 "mailbox name. The function then returns a tree of folders of that mailbox.\n"
764 "In any case, a tree consists of elements of the type clawsmail.Node."},
766 {"get_folderview_selected_folder", get_folderview_selected_folder, METH_NOARGS,
767 "get_folderview_selected_folder() - get selected folder in folderview\n"
769 "Returns the currently selected folder as a clawsmail.Folder or None if no folder is selected."},
770 {"folderview_select_folder", folderview_select_row, METH_VARARGS,
771 "folderview_select_folder(folder) - select folder in folderview\n"
773 "Takes an argument of type clawsmail.Folder, and selects the corresponding folder.\n"
775 "DEPRECATED: Use folderview_select() instead."},
777 {"get_folderview_selected_mailbox", get_folderview_selected_mailbox, METH_NOARGS,
778 "get_folderview_selected_mailbox() - get selected mailbox in folderview\n"
780 "Returns the currently selected mailbox as a clawsmail.Mailbox or None if no mailbox is selected."},
782 {"folderview_select", folderview_select_row, METH_VARARGS,
783 "folderview_select(arg) - select folder or a mailbox in folderview\n"
785 "Takes an argument of type clawsmail.Folder or clawsmail.Mailbox, and selects the corresponding\n"
786 "row in the folder view."},
788 {"quicksearch_search", quicksearch_search, METH_VARARGS,
789 "quicksearch_search(string [, type]) - perform a quicksearch\n"
791 "Perform a quicksearch of the given string. The optional type argument can be\n"
792 "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
793 "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
794 "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
795 "string corresponding to the type."},
797 {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
798 "quicksearch_clear() - clear the quicksearch"},
800 {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
801 "get_summaryview_selected_message_list() - get selected message list\n"
803 "Get a list of clawsmail.MessageInfo objects of the current selection."},
805 {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
806 "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
808 "Select a list of clawsmail.MessageInfo objects in the summary view."},
810 {"is_exiting", is_exiting, METH_NOARGS,
811 "is_exiting() - test whether Claws Mail is currently exiting\n"
813 "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
814 "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
815 "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
818 {"move_messages", move_messages, METH_VARARGS,
819 "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
821 "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
822 "The target_folder argument has to be a clawsmail.Folder object."},
824 {"copy_messages", copy_messages, METH_VARARGS,
825 "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
827 "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
828 "The target_folder argument has to be a clawsmail.Folder object."},
830 {"get_tags", get_tags, METH_NOARGS,
831 "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
833 "Get a tuple of strings representing all tags that are defined in Claws Mail."},
835 {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
836 "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
838 "This function creates the given tag if it does not exist yet.\n"
839 "It is not an error if the tag already exists. In this case, this function does nothing.\n"
840 "However, if a reserved tag name is chosen, a ValueError exception is raised."},
842 {"delete_tag", delete_tag, METH_VARARGS,
843 "delete_tag(tag) - delete a tag\n"
845 "This function deletes an existing tag.\n"
846 "Raises a KeyError exception if the tag does not exist."},
848 {"rename_tag", rename_tag, METH_VARARGS,
849 "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
851 "This function renames an existing tag.\n"
852 "Raises a KeyError exception if the tag does not exist.\n"
853 "Raises a ValueError exception if the old or new tag name is a reserved name."},
855 {"get_accounts", get_accounts, METH_NOARGS,
856 "get_accounts() - get a tuple of all accounts that Claws Mail knows about\n"
858 "Get a tuple of Account objects representing all accounts that are defined in Claws Mail."},
860 {"get_current_account", get_current_account, METH_NOARGS,
861 "get_current_account() - get the current account\n"
863 "Return the object representing the currently selected account."},
865 {"get_default_account", get_default_account, METH_NOARGS,
866 "get_default_account() - get the default account\n"
868 "Return the object representing the default account."},
870 {"get_mailboxes", get_mailboxes, METH_NOARGS,
871 "get_mailboxes() - get a tuple of all mailboxes that Claws Mail knows about\n"
873 "Get a tuple of Mailbox objects representing all mailboxes that are defined in Claws Mail."},
876 {"__gobj", private_wrap_gobj, METH_VARARGS,
877 "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
879 "For internal usage only."},
881 {NULL, NULL, 0, NULL}
884 static gboolean add_miscstuff(PyObject *module)
890 "QUICK_SEARCH_SUBJECT = 0\n"
891 "QUICK_SEARCH_FROM = 1\n"
892 "QUICK_SEARCH_TO = 2\n"
893 "QUICK_SEARCH_EXTENDED = 3\n"
894 "QUICK_SEARCH_MIXED = 4\n"
895 "QUICK_SEARCH_TAG = 5\n"
897 dict = PyModule_GetDict(module);
898 res = PyRun_String(cmd, Py_file_input, dict, dict);
899 retval = (res != NULL);
905 PyMODINIT_FUNC initclawsmail(void)
910 cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
911 "This module can be used to access some of Claws Mail's data structures\n"
912 "in order to extend or modify the user interface or automate repetitive tasks.\n"
914 "Whenever possible, the interface works with standard GTK+ widgets\n"
915 "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
916 "to find out about all possible options.\n"
918 "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
919 "If you're missing something specific, try contacting the author.");
921 /* add module member "compose_window" set to None */
923 PyModule_AddObject(cm_module, "compose_window", Py_None);
925 /* initialize classes */
926 ok = ok && cmpy_add_node(cm_module);
927 ok = ok && cmpy_add_composewindow(cm_module);
928 ok = ok && cmpy_add_folder(cm_module);
929 ok = ok && cmpy_add_messageinfo(cm_module);
930 ok = ok && cmpy_add_account(cm_module);
931 ok = ok && cmpy_add_folderproperties(cm_module);
932 ok = ok && cmpy_add_mailbox(cm_module);
934 /* initialize misc things */
936 add_miscstuff(cm_module);
940 void put_composewindow_into_module(Compose *compose)
944 pycompose = clawsmail_compose_new(cm_module, compose);
945 PyObject_SetAttrString(cm_module, "compose_window", pycompose);
946 Py_DECREF(pycompose);