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"
51 static PyObject *cm_module = NULL;
53 PyObject* get_gobj_from_address(gpointer addr)
57 if (!G_IS_OBJECT(addr))
65 return pygobject_new(obj);
68 static PyObject* private_wrap_gobj(PyObject *self, PyObject *args)
72 if (!PyArg_ParseTuple(args, "l", &addr))
75 return get_gobj_from_address(addr);
78 static PyObject *get_mainwindow_action_group(PyObject *self, PyObject *args)
82 mainwin = mainwindow_get_mainwindow();
84 return get_gobj_from_address(mainwin->action_group);
89 static PyObject *get_mainwindow_ui_manager(PyObject *self, PyObject *args)
93 mainwin = mainwindow_get_mainwindow();
95 return get_gobj_from_address(mainwin->ui_manager);
100 static PyObject *get_folderview_selected_folder(PyObject *self, PyObject *args)
104 mainwin = mainwindow_get_mainwindow();
105 if(mainwin && mainwin->folderview) {
107 item = folderview_get_selected_item(mainwin->folderview);
109 return clawsmail_folder_new(item);
114 static PyObject *get_folderview_selected_mailbox(PyObject *self, PyObject *args)
118 mainwin = mainwindow_get_mainwindow();
119 if(mainwin && mainwin->folderview) {
121 item = folderview_get_selected_item(mainwin->folderview);
124 id = folder_item_get_identifier(item);
125 /* If there is an id, it's a folder, not a mailbox */
131 return clawsmail_mailbox_new(item->folder);
137 static PyObject *folderview_select_row(PyObject *self, PyObject *args)
143 mainwin = mainwindow_get_mainwindow();
144 if(mainwin && mainwin->folderview) {
146 arg = PyTuple_GetItem(args, 0);
151 if(clawsmail_folder_check(arg)) {
153 item = clawsmail_folder_get_item(arg);
155 folderview_select(mainwin->folderview, item);
157 else if(clawsmail_mailbox_check(arg)) {
159 folder = clawsmail_mailbox_get_folder(arg);
160 if(folder && folder->node) {
161 folderview_select(mainwin->folderview, folder->node->data);
165 PyErr_SetString(PyExc_TypeError, "Bad argument type");
177 static gboolean setup_folderitem_node(GNode *item_node, GNode *item_parent, PyObject **pyparent)
179 PyObject *pynode, *children;
180 int retval, n_children, i_child;
183 /* create a python node for the folderitem node */
184 pynode = clawsmail_node_new(cm_module);
188 /* store Folder in pynode */
189 folder = clawsmail_folder_new(item_node->data);
190 retval = PyObject_SetAttrString(pynode, "data", folder);
197 if(pyparent && *pyparent) {
198 /* add this node to the parent's childs */
199 children = PyObject_GetAttrString(*pyparent, "children");
200 retval = PyList_Append(children, pynode);
213 /* call this function recursively for all children of the new node */
214 n_children = g_node_n_children(item_node);
215 for(i_child = 0; i_child < n_children; i_child++) {
216 if(!setup_folderitem_node(g_node_nth_child(item_node, i_child), item_node, &pynode)) {
226 static PyObject* get_folder_tree_from_folder(Folder *folder)
230 int n_children, i_child;
232 /* create root nodes */
233 root = clawsmail_node_new(cm_module);
237 n_children = g_node_n_children(folder->node);
238 for(i_child = 0; i_child < n_children; i_child++) {
239 if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
249 static PyObject* get_folder_tree_from_account_name(const char *str)
254 result = Py_BuildValue("[]");
258 for(walk = folder_get_list(); walk; walk = walk->next) {
259 Folder *folder = walk->data;
260 if(!str || !g_strcmp0(str, folder->name)) {
261 PyObject *tree_from_folder;
262 tree_from_folder = get_folder_tree_from_folder(folder);
263 if(tree_from_folder) {
265 retval = PyList_Append(result, tree_from_folder);
266 Py_DECREF(tree_from_folder);
281 static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
286 for(walk = folder_get_list(); walk; walk = walk->next) {
287 Folder *folder = walk->data;
291 root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
296 if(!setup_folderitem_node(root_node, NULL, &result))
303 PyErr_SetString(PyExc_LookupError, "Folder not found");
307 static PyObject* get_folder_tree(PyObject *self, PyObject *args)
315 retval = PyArg_ParseTuple(args, "|O", &arg);
320 /* calling possibilities:
321 * nothing, the mailbox name in a string, a Folder object */
323 /* no arguments: build up a list of folder trees */
324 if(PyTuple_Size(args) == 0) {
325 result = get_folder_tree_from_account_name(NULL);
327 else if(PyString_Check(arg)){
329 str = PyString_AsString(arg);
333 result = get_folder_tree_from_account_name(str);
335 else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
336 result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
338 else if(clawsmail_mailbox_check(arg)) {
339 result = get_folder_tree_from_folder(clawsmail_mailbox_get_folder(arg));
342 PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a Folder object, a Mailbox object, or a mailbox name string.");
349 static PyObject* quicksearch_search(PyObject *self, PyObject *args)
356 /* must be given exactly one argument, which is a string */
357 searchtype = prefs_common_get_prefs()->summary_quicksearch_type;
358 if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
361 mainwin = mainwindow_get_mainwindow();
362 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
363 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
367 qs = mainwin->summaryview->quicksearch;
368 quicksearch_set(qs, searchtype, string);
374 static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
379 mainwin = mainwindow_get_mainwindow();
380 if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
381 PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
385 qs = mainwin->summaryview->quicksearch;
386 quicksearch_set(qs, prefs_common_get_prefs()->summary_quicksearch_type, "");
392 static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
396 Py_ssize_t size, iEl;
399 mainwin = mainwindow_get_mainwindow();
400 if(!mainwin || !mainwin->summaryview) {
401 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
405 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
406 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
411 size = PyList_Size(olist);
412 for(iEl = 0; iEl < size; iEl++) {
413 PyObject *element = PyList_GET_ITEM(olist, iEl);
415 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
416 PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
420 msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
423 summary_unselect_all(mainwin->summaryview);
424 summary_select_by_msg_list(mainwin->summaryview, msginfos);
425 g_slist_free(msginfos);
431 static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
437 mainwin = mainwindow_get_mainwindow();
438 if(!mainwin || !mainwin->summaryview) {
439 PyErr_SetString(PyExc_LookupError, "SummaryView not found");
443 result = Py_BuildValue("[]");
447 list = summary_get_selected_msg_list(mainwin->summaryview);
448 for(walk = list; walk; walk = walk->next) {
450 msg = clawsmail_messageinfo_new(walk->data);
451 if(PyList_Append(result, msg) == -1) {
461 static PyObject* is_exiting(PyObject *self, PyObject *args)
463 if(claws_is_exiting())
469 static PyObject* get_tags(PyObject *self, PyObject *args)
472 PyObject *tags_tuple;
475 tags_list = tags_get_list();
476 num_tags = g_slist_length(tags_list);
478 tags_tuple = PyTuple_New(num_tags);
479 if(tags_tuple != NULL) {
481 PyObject *tag_object;
485 for(walk = tags_list; walk; walk = walk->next) {
486 tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
487 if(tag_object == NULL) {
488 Py_DECREF(tags_tuple);
491 PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
495 g_slist_free(tags_list);
500 static PyObject* get_accounts(PyObject *self, PyObject *args)
502 PyObject *accounts_tuple;
503 GList *accounts_list;
506 accounts_list = account_get_list();
508 accounts_tuple = PyTuple_New(g_list_length(accounts_list));
510 PyObject *account_object;
514 for(walk = accounts_list; walk; walk = walk->next) {
515 account_object = clawsmail_account_new(walk->data);
516 if(account_object == NULL) {
517 Py_DECREF(accounts_tuple);
520 PyTuple_SET_ITEM(accounts_tuple, iAccount++, account_object);
524 return accounts_tuple;
527 static PyObject* get_mailboxes(PyObject *self, PyObject *args)
529 PyObject *mailboxes_tuple;
530 GList *mailboxes_list;
533 mailboxes_list = folder_get_list();
535 mailboxes_tuple = PyTuple_New(g_list_length(mailboxes_list));
536 if(mailboxes_tuple) {
537 PyObject *mailbox_object;
541 for(walk = mailboxes_list; walk; walk = walk->next) {
542 mailbox_object = clawsmail_mailbox_new(walk->data);
543 if(mailbox_object == NULL) {
544 Py_DECREF(mailboxes_tuple);
547 PyTuple_SET_ITEM(mailboxes_tuple, iMailbox++, mailbox_object);
551 return mailboxes_tuple;
555 static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
560 retval = PyArg_ParseTuple(args, "s", &tag_str);
564 if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
565 /* tag name is reserved, raise KeyError */
566 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
570 tags_add_tag(tag_str);
575 static PyObject* delete_tag(PyObject *self, PyObject *args)
582 retval = PyArg_ParseTuple(args, "s", &tag_str);
586 tag_id = tags_get_id_for_str(tag_str);
588 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
592 tags_remove_tag(tag_id);
595 mainwin = mainwindow_get_mainwindow();
597 summary_redisplay_msg(mainwin->summaryview);
603 static PyObject* rename_tag(PyObject *self, PyObject *args)
606 const char *old_tag_str;
607 const char *new_tag_str;
611 retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
615 if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
616 PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
620 tag_id = tags_get_id_for_str(old_tag_str);
622 PyErr_SetString(PyExc_KeyError, "Tag does not exist");
626 tags_update_tag(tag_id, new_tag_str);
629 mainwin = mainwindow_get_mainwindow();
631 summary_redisplay_msg(mainwin->summaryview);
636 static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
638 Py_ssize_t size, iEl;
639 FolderItem *folderitem;
643 folderitem = clawsmail_folder_get_item(folder);
645 PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
649 size = PyList_Size(messagelist);
650 for(iEl = 0; iEl < size; iEl++) {
651 PyObject *element = PyList_GET_ITEM(messagelist, iEl);
654 if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
655 PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
659 msginfo = clawsmail_messageinfo_get_msginfo(element);
661 PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
665 procmsg_msginfo_set_to_folder(msginfo, folderitem);
666 *list = g_slist_prepend(*list, msginfo);
672 static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
674 PyObject *messagelist;
679 retval = PyArg_ParseTuple(args, "O!O!",
680 &PyList_Type, &messagelist,
681 clawsmail_folder_get_type_object(), &folder);
685 folder_item_update_freeze();
687 if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
691 procmsg_move_messages(list);
693 procmsg_copy_messages(list);
695 folder_item_update_thaw();
700 folder_item_update_thaw();
705 static PyObject* move_messages(PyObject *self, PyObject *args)
707 return move_or_copy_messages(self, args, TRUE);
711 static PyObject* copy_messages(PyObject *self, PyObject *args)
713 return move_or_copy_messages(self, args, FALSE);
716 static PyObject* get_current_account(PyObject *self, PyObject *args)
718 PrefsAccount *account;
719 account = account_get_cur_account();
721 return clawsmail_account_new(account);
727 static PyObject* get_default_account(PyObject *self, PyObject *args)
729 PrefsAccount *account;
730 account = account_get_default();
732 return clawsmail_account_new(account);
739 static PyMethodDef ClawsMailMethods[] = {
741 {"get_mainwindow_action_group", get_mainwindow_action_group, METH_NOARGS,
742 "get_mainwindow_action_group() - get action group of main window menu\n"
744 "Returns the gtk.ActionGroup for the main window."},
746 {"get_mainwindow_ui_manager", get_mainwindow_ui_manager, METH_NOARGS,
747 "get_mainwindow_ui_manager() - get ui manager of main window\n"
749 "Returns the gtk.UIManager for the main window."},
751 {"get_folder_tree", get_folder_tree, METH_VARARGS,
752 "get_folder_tree([root]) - get a folder tree\n"
754 "Without arguments, get a list of folder trees for all mailboxes.\n"
756 "If the optional root argument is a clawsmail.Folder, the function\n"
757 "returns a tree of subfolders with the given folder as root element.\n"
759 "If the optional root argument is a clawsmail.Mailbox, the function\n"
760 "returns a tree of folders with the given mailbox as root element.\n"
762 "If the optional root argument is a string, it is supposed to be a\n"
763 "mailbox name. The function then returns a tree of folders of that mailbox.\n"
765 "In any case, a tree consists of elements of the type clawsmail.Node."},
767 {"get_folderview_selected_folder", get_folderview_selected_folder, METH_NOARGS,
768 "get_folderview_selected_folder() - get selected folder in folderview\n"
770 "Returns the currently selected folder as a clawsmail.Folder or None if no folder is selected."},
771 {"folderview_select_folder", folderview_select_row, METH_VARARGS,
772 "folderview_select_folder(folder) - select folder in folderview\n"
774 "Takes an argument of type clawsmail.Folder, and selects the corresponding folder.\n"
776 "DEPRECATED: Use folderview_select() instead."},
778 {"get_folderview_selected_mailbox", get_folderview_selected_mailbox, METH_NOARGS,
779 "get_folderview_selected_mailbox() - get selected mailbox in folderview\n"
781 "Returns the currently selected mailbox as a clawsmail.Mailbox or None if no mailbox is selected."},
783 {"folderview_select", folderview_select_row, METH_VARARGS,
784 "folderview_select(arg) - select folder or a mailbox in folderview\n"
786 "Takes an argument of type clawsmail.Folder or clawsmail.Mailbox, and selects the corresponding\n"
787 "row in the folder view."},
789 {"quicksearch_search", quicksearch_search, METH_VARARGS,
790 "quicksearch_search(string [, type]) - perform a quicksearch\n"
792 "Perform a quicksearch of the given string. The optional type argument can be\n"
793 "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
794 "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
795 "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
796 "string corresponding to the type."},
798 {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
799 "quicksearch_clear() - clear the quicksearch"},
801 {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
802 "get_summaryview_selected_message_list() - get selected message list\n"
804 "Get a list of clawsmail.MessageInfo objects of the current selection."},
806 {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
807 "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
809 "Select a list of clawsmail.MessageInfo objects in the summary view."},
811 {"is_exiting", is_exiting, METH_NOARGS,
812 "is_exiting() - test whether Claws Mail is currently exiting\n"
814 "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
815 "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
816 "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
819 {"move_messages", move_messages, METH_VARARGS,
820 "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
822 "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
823 "The target_folder argument has to be a clawsmail.Folder object."},
825 {"copy_messages", copy_messages, METH_VARARGS,
826 "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
828 "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
829 "The target_folder argument has to be a clawsmail.Folder object."},
831 {"get_tags", get_tags, METH_NOARGS,
832 "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
834 "Get a tuple of strings representing all tags that are defined in Claws Mail."},
836 {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
837 "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
839 "This function creates the given tag if it does not exist yet.\n"
840 "It is not an error if the tag already exists. In this case, this function does nothing.\n"
841 "However, if a reserved tag name is chosen, a ValueError exception is raised."},
843 {"delete_tag", delete_tag, METH_VARARGS,
844 "delete_tag(tag) - delete a tag\n"
846 "This function deletes an existing tag.\n"
847 "Raises a KeyError exception if the tag does not exist."},
849 {"rename_tag", rename_tag, METH_VARARGS,
850 "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
852 "This function renames an existing tag.\n"
853 "Raises a KeyError exception if the tag does not exist.\n"
854 "Raises a ValueError exception if the old or new tag name is a reserved name."},
856 {"get_accounts", get_accounts, METH_NOARGS,
857 "get_accounts() - get a tuple of all accounts that Claws Mail knows about\n"
859 "Get a tuple of Account objects representing all accounts that are defined in Claws Mail."},
861 {"get_current_account", get_current_account, METH_NOARGS,
862 "get_current_account() - get the current account\n"
864 "Return the object representing the currently selected account."},
866 {"get_default_account", get_default_account, METH_NOARGS,
867 "get_default_account() - get the default account\n"
869 "Return the object representing the default account."},
871 {"get_mailboxes", get_mailboxes, METH_NOARGS,
872 "get_mailboxes() - get a tuple of all mailboxes that Claws Mail knows about\n"
874 "Get a tuple of Mailbox objects representing all mailboxes that are defined in Claws Mail."},
877 {"__gobj", private_wrap_gobj, METH_VARARGS,
878 "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
880 "For internal usage only."},
882 {NULL, NULL, 0, NULL}
885 static gboolean add_miscstuff(PyObject *module)
891 "QUICK_SEARCH_SUBJECT = 0\n"
892 "QUICK_SEARCH_FROM = 1\n"
893 "QUICK_SEARCH_TO = 2\n"
894 "QUICK_SEARCH_EXTENDED = 3\n"
895 "QUICK_SEARCH_MIXED = 4\n"
896 "QUICK_SEARCH_TAG = 5\n"
898 dict = PyModule_GetDict(module);
899 res = PyRun_String(cmd, Py_file_input, dict, dict);
900 retval = (res != NULL);
906 PyMODINIT_FUNC initclawsmail(void)
911 cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
912 "This module can be used to access some of Claws Mail's data structures\n"
913 "in order to extend or modify the user interface or automate repetitive tasks.\n"
915 "Whenever possible, the interface works with standard GTK+ widgets\n"
916 "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
917 "to find out about all possible options.\n"
919 "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
920 "If you're missing something specific, try contacting the author.");
922 /* add module member "compose_window" set to None */
924 if (PyModule_AddObject(cm_module, "compose_window", Py_None) == -1)
925 debug_print("Error: Could not add object 'compose_window'\n");
927 /* initialize classes */
928 ok = ok && cmpy_add_node(cm_module);
929 ok = ok && cmpy_add_composewindow(cm_module);
930 ok = ok && cmpy_add_folder(cm_module);
931 ok = ok && cmpy_add_messageinfo(cm_module);
932 ok = ok && cmpy_add_account(cm_module);
933 ok = ok && cmpy_add_folderproperties(cm_module);
934 ok = ok && cmpy_add_mailbox(cm_module);
936 /* initialize misc things */
938 add_miscstuff(cm_module);
942 void put_composewindow_into_module(Compose *compose)
946 pycompose = clawsmail_compose_new(cm_module, compose);
947 PyObject_SetAttrString(cm_module, "compose_window", pycompose);
948 Py_DECREF(pycompose);