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_account_name(const char *str)
212 result = Py_BuildValue("[]");
216 for(walk = folder_get_list(); walk; walk = walk->next) {
217 Folder *folder = walk->data;
218 if((!str || !g_strcmp0(str, folder->name)) && folder->node) {
220 int n_children, i_child, retval;
222 /* create root nodes */
223 root = clawsmail_node_new(cm_module);
229 n_children = g_node_n_children(folder->node);
230 for(i_child = 0; i_child < n_children; i_child++) {
231 if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
237 retval = PyList_Append(result, root);
248 static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
253 for(walk = folder_get_list(); walk; walk = walk->next) {
254 Folder *folder = walk->data;
258 root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
263 if(!setup_folderitem_node(root_node, NULL, &result))
270 PyErr_SetString(PyExc_LookupError, "Folder not found");
274 static PyObject* get_folder_tree(PyObject *self, PyObject *args)
282 retval = PyArg_ParseTuple(args, "|O", &arg);
287 /* calling possibilities:
288 * nothing, the mailbox name in a string, a Folder object */
290 /* no arguments: build up a list of folder trees */
291 if(PyTuple_Size(args) == 0) {
292 result = get_folder_tree_from_account_name(NULL);
294 else if(PyString_Check(arg)){
296 str = PyString_AsString(arg);
300 result = get_folder_tree_from_account_name(str);
302 else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
303 result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
306 PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a mailbox string or a Folder object.");
313 static PyObject* quicksearch_search(PyObject *self, PyObject *args)
320 /* must be given exactly one argument, which is a string */
321 searchtype = prefs_common.summary_quicksearch_type;
322 if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
325 mainwin = mainwindow_get_mainwindow();
326 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
327 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
331 qs = mainwin->summaryview->quicksearch;
332 quicksearch_set(qs, searchtype, string);
338 static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
343 mainwin = mainwindow_get_mainwindow();
344 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
345 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
349 qs = mainwin->summaryview->quicksearch;
350 quicksearch_set(qs, prefs_common.summary_quicksearch_type, "");
356 static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
360 Py_ssize_t size, iEl;
363 mainwin = mainwindow_get_mainwindow();
364 if(!mainwin || !mainwin->summaryview) {
365 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
369 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
370 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
375 size = PyList_Size(olist);
376 for(iEl = 0; iEl < size; iEl++) {
377 PyObject *element = PyList_GET_ITEM(olist, iEl);
379 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
380 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
384 msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
387 summary_unselect_all(mainwin->summaryview);
388 summary_select_by_msg_list(mainwin->summaryview, msginfos);
389 g_slist_free(msginfos);
395 static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
401 mainwin = mainwindow_get_mainwindow();
402 if(!mainwin || !mainwin->summaryview) {
403 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
407 result = Py_BuildValue("[]");
411 list = summary_get_selected_msg_list(mainwin->summaryview);
412 for(walk = list; walk; walk = walk->next) {
414 msg = clawsmail_messageinfo_new(walk->data);
415 if(PyList_Append(result, msg) == -1) {
425 static PyObject* is_exiting(PyObject *self, PyObject *args)
427 if(claws_is_exiting())
433 static PyObject* get_tags(PyObject *self, PyObject *args)
436 PyObject *tags_tuple;
439 tags_list = tags_get_list();
440 num_tags = g_slist_length(tags_list);
442 tags_tuple = PyTuple_New(num_tags);
443 if(tags_tuple != NULL) {
445 PyObject *tag_object;
449 for(walk = tags_list; walk; walk = walk->next) {
450 tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
451 if(tag_object == NULL) {
452 Py_DECREF(tags_tuple);
455 PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
459 g_slist_free(tags_list);
464 static PyObject* get_accounts(PyObject *self, PyObject *args)
466 PyObject *accounts_tuple;
467 GList *accounts_list;
470 accounts_list = account_get_list();
472 accounts_tuple = PyTuple_New(g_list_length(accounts_list));
474 PyObject *account_object;
478 for(walk = accounts_list; walk; walk = walk->next) {
479 account_object = clawsmail_account_new(walk->data);
480 if(account_object == NULL) {
481 Py_DECREF(accounts_tuple);
484 PyTuple_SET_ITEM(accounts_tuple, iAccount++, account_object);
488 return accounts_tuple;
491 static PyObject* get_mailboxes(PyObject *self, PyObject *args)
493 PyObject *mailboxes_tuple;
494 GList *mailboxes_list;
497 mailboxes_list = folder_get_list();
499 mailboxes_tuple = PyTuple_New(g_list_length(mailboxes_list));
500 if(mailboxes_tuple) {
501 PyObject *mailbox_object;
505 for(walk = mailboxes_list; walk; walk = walk->next) {
506 mailbox_object = clawsmail_mailbox_new(walk->data);
507 if(mailbox_object == NULL) {
508 Py_DECREF(mailboxes_tuple);
511 PyTuple_SET_ITEM(mailboxes_tuple, iMailbox++, mailbox_object);
515 return mailboxes_tuple;
519 static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
524 retval = PyArg_ParseTuple(args, "s", &tag_str);
528 if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
529 /* tag name is reserved, raise KeyError */
530 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
534 tags_add_tag(tag_str);
539 static PyObject* delete_tag(PyObject *self, PyObject *args)
546 retval = PyArg_ParseTuple(args, "s", &tag_str);
550 tag_id = tags_get_id_for_str(tag_str);
552 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
556 tags_remove_tag(tag_id);
559 mainwin = mainwindow_get_mainwindow();
561 summary_redisplay_msg(mainwin->summaryview);
567 static PyObject* rename_tag(PyObject *self, PyObject *args)
570 const char *old_tag_str;
571 const char *new_tag_str;
575 retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
579 if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
580 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
584 tag_id = tags_get_id_for_str(old_tag_str);
586 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
590 tags_update_tag(tag_id, new_tag_str);
593 mainwin = mainwindow_get_mainwindow();
595 summary_redisplay_msg(mainwin->summaryview);
600 static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
602 Py_ssize_t size, iEl;
603 FolderItem *folderitem;
607 folderitem = clawsmail_folder_get_item(folder);
609 PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
613 size = PyList_Size(messagelist);
614 for(iEl = 0; iEl < size; iEl++) {
615 PyObject *element = PyList_GET_ITEM(messagelist, iEl);
618 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
619 PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
623 msginfo = clawsmail_messageinfo_get_msginfo(element);
625 PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
629 procmsg_msginfo_set_to_folder(msginfo, folderitem);
630 *list = g_slist_prepend(*list, msginfo);
636 static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
638 PyObject *messagelist;
643 retval = PyArg_ParseTuple(args, "O!O!",
644 &PyList_Type, &messagelist,
645 clawsmail_folder_get_type_object(), &folder);
649 folder_item_update_freeze();
651 if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
655 procmsg_move_messages(list);
657 procmsg_copy_messages(list);
659 folder_item_update_thaw();
664 folder_item_update_thaw();
669 static PyObject* move_messages(PyObject *self, PyObject *args)
671 return move_or_copy_messages(self, args, TRUE);
675 static PyObject* copy_messages(PyObject *self, PyObject *args)
677 return move_or_copy_messages(self, args, FALSE);
680 static PyObject* get_current_account(PyObject *self, PyObject *args)
682 PrefsAccount *account;
683 account = account_get_cur_account();
685 return clawsmail_account_new(account);
691 static PyObject* get_default_account(PyObject *self, PyObject *args)
693 PrefsAccount *account;
694 account = account_get_default();
696 return clawsmail_account_new(account);
703 static PyMethodDef ClawsMailMethods[] = {
705 {"get_mainwindow_action_group", get_mainwindow_action_group, METH_NOARGS,
706 "get_mainwindow_action_group() - get action group of main window menu\n"
708 "Returns the gtk.ActionGroup for the main window."},
710 {"get_mainwindow_ui_manager", get_mainwindow_ui_manager, METH_NOARGS,
711 "get_mainwindow_ui_manager() - get ui manager of main window\n"
713 "Returns the gtk.UIManager for the main window."},
715 {"get_folder_tree", get_folder_tree, METH_VARARGS,
716 "get_folder_tree([root]) - get a folder tree\n"
718 "Without arguments, get a list of folder trees for all mailboxes.\n"
720 "If the optional root argument is a string, it is supposed to be a\n"
721 "mailbox name. The function then returns a tree of folders of that mailbox.\n"
723 "If the optional root argument is a clawsmail.Folder, the function\n"
724 "returns a tree of subfolders with the given folder as root element.\n"
726 "In any case, a tree consists of elements of the type clawsmail.Node."},
728 {"get_folderview_selected_folder", get_folderview_selected_folder, METH_NOARGS,
729 "get_folderview_selected_folder() - get selected folder in folderview\n"
731 "Returns the currently selected folder as a clawsmail.Folder or None if no folder is selected."},
732 {"folderview_select_folder", folderview_select_folder, METH_VARARGS,
733 "folderview_select_folder(folder) - select folder in folderview\n"
735 "Takes an argument of type clawsmail.Folder, and selects the corresponding folder."},
737 {"get_folderview_selected_mailbox", get_folderview_selected_mailbox, METH_NOARGS,
738 "get_folderview_selected_mailbox() - get selected mailbox in folderview\n"
740 "Returns the currently selected mailbox as a clawsmail.Mailbox or None if no mailbox is selected."},
742 {"quicksearch_search", quicksearch_search, METH_VARARGS,
743 "quicksearch_search(string [, type]) - perform a quicksearch\n"
745 "Perform a quicksearch of the given string. The optional type argument can be\n"
746 "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
747 "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
748 "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
749 "string corresponding to the type."},
751 {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
752 "quicksearch_clear() - clear the quicksearch"},
754 {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
755 "get_summaryview_selected_message_list() - get selected message list\n"
757 "Get a list of clawsmail.MessageInfo objects of the current selection."},
759 {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
760 "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
762 "Select a list of clawsmail.MessageInfo objects in the summary view."},
764 {"is_exiting", is_exiting, METH_NOARGS,
765 "is_exiting() - test whether Claws Mail is currently exiting\n"
767 "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
768 "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
769 "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
772 {"move_messages", move_messages, METH_VARARGS,
773 "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
775 "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
776 "The target_folder argument has to be a clawsmail.Folder object."},
778 {"copy_messages", copy_messages, METH_VARARGS,
779 "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
781 "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
782 "The target_folder argument has to be a clawsmail.Folder object."},
784 {"get_tags", get_tags, METH_NOARGS,
785 "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
787 "Get a tuple of strings representing all tags that are defined in Claws Mail."},
789 {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
790 "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
792 "This function creates the given tag if it does not exist yet.\n"
793 "It is not an error if the tag already exists. In this case, this function does nothing.\n"
794 "However, if a reserved tag name is chosen, a ValueError exception is raised."},
796 {"delete_tag", delete_tag, METH_VARARGS,
797 "delete_tag(tag) - delete a tag\n"
799 "This function deletes an existing tag.\n"
800 "Raises a KeyError exception if the tag does not exist."},
802 {"rename_tag", rename_tag, METH_VARARGS,
803 "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
805 "This function renames an existing tag.\n"
806 "Raises a KeyError exception if the tag does not exist.\n"
807 "Raises a ValueError exception if the old or new tag name is a reserved name."},
809 {"get_accounts", get_accounts, METH_NOARGS,
810 "get_accounts() - get a tuple of all accounts that Claws Mail knows about\n"
812 "Get a tuple of Account objects representing all accounts that are defined in Claws Mail."},
814 {"get_current_account", get_current_account, METH_NOARGS,
815 "get_current_account() - get the current account\n"
817 "Return the object representing the currently selected account."},
819 {"get_default_account", get_default_account, METH_NOARGS,
820 "get_default_account() - get the default account\n"
822 "Return the object representing the default account."},
824 {"get_mailboxes", get_mailboxes, METH_NOARGS,
825 "get_mailboxes() - get a tuple of all mailboxes that Claws Mail knows about\n"
827 "Get a tuple of Mailbox objects representing all mailboxes that are defined in Claws Mail."},
830 {"__gobj", private_wrap_gobj, METH_VARARGS,
831 "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
833 "For internal usage only."},
835 {NULL, NULL, 0, NULL}
838 static gboolean add_miscstuff(PyObject *module)
844 "QUICK_SEARCH_SUBJECT = 0\n"
845 "QUICK_SEARCH_FROM = 1\n"
846 "QUICK_SEARCH_TO = 2\n"
847 "QUICK_SEARCH_EXTENDED = 3\n"
848 "QUICK_SEARCH_MIXED = 4\n"
849 "QUICK_SEARCH_TAG = 5\n"
851 dict = PyModule_GetDict(module);
852 res = PyRun_String(cmd, Py_file_input, dict, dict);
853 retval = (res != NULL);
859 PyMODINIT_FUNC initclawsmail(void)
864 cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
865 "This module can be used to access some of Claws Mail's data structures\n"
866 "in order to extend or modify the user interface or automate repetitive tasks.\n"
868 "Whenever possible, the interface works with standard GTK+ widgets\n"
869 "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
870 "to find out about all possible options.\n"
872 "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
873 "If you're missing something specific, try contacting the author.");
875 /* add module member "compose_window" set to None */
877 PyModule_AddObject(cm_module, "compose_window", Py_None);
879 /* initialize classes */
880 ok = ok && cmpy_add_node(cm_module);
881 ok = ok && cmpy_add_composewindow(cm_module);
882 ok = ok && cmpy_add_folder(cm_module);
883 ok = ok && cmpy_add_messageinfo(cm_module);
884 ok = ok && cmpy_add_account(cm_module);
885 ok = ok && cmpy_add_folderproperties(cm_module);
886 ok = ok && cmpy_add_mailbox(cm_module);
888 /* initialize misc things */
890 add_miscstuff(cm_module);
894 void put_composewindow_into_module(Compose *compose)
898 pycompose = clawsmail_compose_new(cm_module, compose);
899 PyObject_SetAttrString(cm_module, "compose_window", pycompose);
900 Py_DECREF(pycompose);