1 /* Python plugin for Claws-Mail
2 * Copyright (C) 2009 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/>.
20 # include "claws-features.h"
24 #include <glib/gi18n.h>
26 #include "composewindowtype.h"
27 #include "accounttype.h"
29 #include "clawsmailmodule.h"
30 #include "foldertype.h"
31 #include "messageinfotype.h"
33 #include "mainwindow.h"
35 #include "summaryview.h"
37 #include <structmember.h>
48 } clawsmail_ComposeWindowObject;
50 static void ComposeWindow_dealloc(clawsmail_ComposeWindowObject* self)
52 Py_XDECREF(self->ui_manager);
53 Py_XDECREF(self->text);
54 Py_XDECREF(self->replyinfo);
55 Py_XDECREF(self->fwdinfo);
56 self->ob_type->tp_free((PyObject*)self);
59 static void flush_gtk_queue(void)
61 while(gtk_events_pending())
65 static void store_py_object(PyObject **target, PyObject *obj)
79 static void composewindow_set_compose(clawsmail_ComposeWindowObject *self, Compose *compose)
81 self->compose = compose;
83 store_py_object(&(self->ui_manager), get_gobj_from_address(compose->ui_manager));
84 store_py_object(&(self->text), get_gobj_from_address(compose->text));
86 store_py_object(&(self->replyinfo), clawsmail_messageinfo_new(compose->replyinfo));
87 store_py_object(&(self->fwdinfo), clawsmail_messageinfo_new(compose->fwdinfo));
90 static int ComposeWindow_init(clawsmail_ComposeWindowObject *self, PyObject *args, PyObject *kwds)
93 PrefsAccount *ac = NULL;
97 gboolean did_find_compose;
98 Compose *compose = NULL;
100 unsigned char open_window;
101 /* if __open_window is set to 0/False,
102 * composewindow_set_compose must be called before this object is valid */
103 static char *kwlist[] = {"address", "__open_window", NULL};
107 PyArg_ParseTupleAndKeywords(args, kwds, "|sb", kwlist, &ss, &open_window);
110 mainwin = mainwindow_get_mainwindow();
111 item = mainwin->summaryview->folder_item;
112 did_find_compose = FALSE;
115 ac = account_find_from_address(ss, FALSE);
116 if (ac && ac->protocol != A_NNTP) {
117 compose = compose_new_with_folderitem(ac, item, NULL);
118 did_find_compose = TRUE;
121 if(!did_find_compose) {
123 ac = account_find_from_item(item);
124 if (ac && ac->protocol != A_NNTP) {
125 compose = compose_new_with_folderitem(ac, item, NULL);
126 did_find_compose = TRUE;
130 /* use current account */
131 if (!did_find_compose && cur_account && (cur_account->protocol != A_NNTP)) {
132 compose = compose_new_with_folderitem(cur_account, item, NULL);
133 did_find_compose = TRUE;
136 if(!did_find_compose) {
137 /* just get the first one */
138 list = account_get_list();
139 for (cur = list ; cur != NULL ; cur = g_list_next(cur)) {
140 ac = (PrefsAccount *) cur->data;
141 if (ac->protocol != A_NNTP) {
142 compose = compose_new_with_folderitem(ac, item, NULL);
143 did_find_compose = TRUE;
149 if(!did_find_compose)
152 composewindow_set_compose(self, compose);
153 gtk_widget_show_all(compose->window);
159 /* this is here because wrapping GTK_EDITABLEs in PyGTK is buggy */
160 static PyObject* get_python_object_from_gtk_entry(GtkWidget *entry)
162 return Py_BuildValue("s", gtk_entry_get_text(GTK_ENTRY(entry)));
165 static PyObject* set_gtk_entry_from_python_object(GtkWidget *entry, PyObject *args)
169 if(!PyArg_ParseTuple(args, "s", &ss))
172 gtk_entry_set_text(GTK_ENTRY(entry), ss);
178 static PyObject* ComposeWindow_get_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
180 return get_python_object_from_gtk_entry(self->compose->subject_entry);
183 static PyObject* ComposeWindow_set_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
186 ret = set_gtk_entry_from_python_object(self->compose->subject_entry, args);
191 static PyObject* ComposeWindow_get_from(clawsmail_ComposeWindowObject *self, PyObject *args)
193 return get_python_object_from_gtk_entry(self->compose->from_name);
196 static PyObject* ComposeWindow_set_from(clawsmail_ComposeWindowObject *self, PyObject *args)
199 ret = set_gtk_entry_from_python_object(self->compose->from_name, args);
204 static PyObject* ComposeWindow_add_To(clawsmail_ComposeWindowObject *self, PyObject *args)
208 if(!PyArg_ParseTuple(args, "s", &ss))
211 compose_entry_append(self->compose, ss, COMPOSE_TO, PREF_NONE);
219 static PyObject* ComposeWindow_add_Cc(clawsmail_ComposeWindowObject *self, PyObject *args)
223 if(!PyArg_ParseTuple(args, "s", &ss))
226 compose_entry_append(self->compose, ss, COMPOSE_CC, PREF_NONE);
234 static PyObject* ComposeWindow_add_Bcc(clawsmail_ComposeWindowObject *self, PyObject *args)
238 if(!PyArg_ParseTuple(args, "s", &ss))
241 compose_entry_append(self->compose, ss, COMPOSE_BCC, PREF_NONE);
249 static PyObject* ComposeWindow_attach(clawsmail_ComposeWindowObject *self, PyObject *args)
252 Py_ssize_t size, iEl;
255 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist))
258 size = PyList_Size(olist);
259 for(iEl = 0; iEl < size; iEl++) {
261 PyObject *element = PyList_GET_ITEM(olist, iEl);
267 if(!PyArg_Parse(element, "s", &ss)) {
273 list = g_list_prepend(list, ss);
277 compose_attach_from_list(self->compose, list, FALSE);
286 static PyObject* ComposeWindow_get_header_list(clawsmail_ComposeWindowObject *self, PyObject *args)
291 retval = Py_BuildValue("[]");
292 for(walk = self->compose->header_list; walk; walk = walk->next) {
293 ComposeHeaderEntry *headerentry = walk->data;
297 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo))), 0, -1);
298 text = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
300 if(text && strcmp("", text)) {
304 ee = Py_BuildValue("(ss)", header, text);
305 ok = PyList_Append(retval, ee);
316 static PyObject* ComposeWindow_add_header(clawsmail_ComposeWindowObject *self, PyObject *args)
322 if(!PyArg_ParseTuple(args, "ss", &header, &text))
325 /* add a dummy, and modify it then */
326 compose_entry_append(self->compose, "dummy1dummy2dummy3", COMPOSE_TO, PREF_NONE);
327 num = g_slist_length(self->compose->header_list);
329 ComposeHeaderEntry *headerentry;
330 headerentry = g_slist_nth_data(self->compose->header_list, num-2);
332 GtkEditable *editable;
334 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), text);
335 editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo)));
336 gtk_editable_delete_text(editable, 0, -1);
337 gtk_editable_insert_text(editable, header, -1, &pos);
347 /* this is pretty ugly, as the compose struct does not maintain a pointer to the account selection combo */
348 static PyObject* ComposeWindow_get_account_selection(clawsmail_ComposeWindowObject *self, PyObject *args)
350 GList *children, *walk;
352 children = gtk_container_get_children(GTK_CONTAINER(self->compose->header_table));
353 for(walk = children; walk; walk = walk->next) {
354 if(GTK_IS_HBOX(walk->data)) {
355 GList *children2, *walk2;
356 children2 = gtk_container_get_children(GTK_CONTAINER(walk->data));
357 for(walk2 = children2; walk2; walk2 = walk2->next) {
358 if(GTK_IS_EVENT_BOX(walk2->data)) {
359 return get_gobj_from_address(gtk_container_get_children(GTK_CONTAINER(walk2->data))->data);
368 static PyObject* ComposeWindow_save_message_to(clawsmail_ComposeWindowObject *self, PyObject *args)
372 if(!PyArg_ParseTuple(args, "O", &arg))
375 if(PyString_Check(arg)) {
376 GtkEditable *editable;
379 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);
381 editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
382 gtk_editable_delete_text(editable, 0, -1);
383 gtk_editable_insert_text(editable, PyString_AsString(arg), -1, &pos);
385 else if(clawsmail_folder_check(arg)) {
386 GtkEditable *editable;
389 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);
391 editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
392 gtk_editable_delete_text(editable, 0, -1);
393 gtk_editable_insert_text(editable, folder_item_get_identifier(clawsmail_folder_get_item(arg)), -1, &pos);
395 else if (arg == Py_None){
396 /* turn off checkbutton */
397 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), FALSE);
400 PyErr_SetString(PyExc_TypeError, "function takes exactly one argument which may be a folder object, a string, or None");
410 static PyObject* ComposeWindow_set_modified(clawsmail_ComposeWindowObject *self, PyObject *args)
413 gboolean old_modified;
415 if(!PyArg_ParseTuple(args, "b", &modified))
418 old_modified = self->compose->modified;
420 self->compose->modified = (modified != 0);
422 /* If the modified state changed, rewrite window title.
423 * This partly duplicates functionality in compose.c::compose_set_title().
424 * While it's nice to not have to modify Claws Mail for this to work,
425 * it would be cleaner to export that function in Claws Mail. */
426 if((strcmp(gtk_window_get_title(GTK_WINDOW(self->compose->window)), _("Compose message")) != 0) &&
427 (old_modified != self->compose->modified)) {
432 edited = self->compose->modified ? _(" [Edited]") : "";
433 subject = gtk_editable_get_chars(GTK_EDITABLE(self->compose->subject_entry), 0, -1);
434 if(subject && strlen(subject))
435 str = g_strdup_printf(_("%s - Compose message%s"),
438 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
439 gtk_window_set_title(GTK_WINDOW(self->compose->window), str);
450 static PyObject* get_account(clawsmail_ComposeWindowObject *self, void *closure)
452 if(self->compose->account) {
453 return clawsmail_account_new(self->compose->account);
459 static PyMethodDef ComposeWindow_methods[] = {
460 {"set_subject", (PyCFunction)ComposeWindow_set_subject, METH_VARARGS,
461 "set_subject(text) - set subject to text\n"
463 "Set the subject to text. text must be a string."},
465 {"get_subject", (PyCFunction)ComposeWindow_get_subject, METH_NOARGS,
466 "get_subject() - get subject\n"
468 "Get a string of the current subject entry."},
470 {"set_from", (PyCFunction)ComposeWindow_set_from, METH_VARARGS,
471 "set_from(text) - set From header entry to text\n"
473 "Set the From header entry to text. text must be a string.\n"
474 "Beware: No sanity checking is performed."},
476 {"get_from", (PyCFunction)ComposeWindow_get_from, METH_NOARGS,
477 "get_from - get From header entry\n"
479 "Get a string of the current From header entry."},
481 {"add_To", (PyCFunction)ComposeWindow_add_To, METH_VARARGS,
482 "add_To(text) - append another To header with text\n"
484 "Add another header line with the combo box set to To:, and the\n"
485 "content set to text."},
487 {"add_Cc", (PyCFunction)ComposeWindow_add_Cc, METH_VARARGS,
488 "add_Cc(text) - append another Cc header with text\n"
490 "Add another header line with the combo box set to Cc:, and the\n"
491 "content set to text."},
493 {"add_Bcc", (PyCFunction)ComposeWindow_add_Bcc, METH_VARARGS,
494 "add_Bcc(text) - append another Bcc header with text\n"
496 "Add another header line with the combo box set to Bcc:, and the\n"
497 "content set to text."},
499 {"add_header", (PyCFunction)ComposeWindow_add_header, METH_VARARGS,
500 "add_header(headername, text) - add a custom header\n"
502 "Adds a custom header with the header set to headername, and the\n"
503 "contents set to text."},
505 {"get_header_list", (PyCFunction)ComposeWindow_get_header_list, METH_NOARGS,
506 "get_header_list() - get list of headers\n"
508 "Gets a list of headers that are currently defined in the compose window.\n"
509 "The return value is a list of tuples, where the first tuple element is\n"
510 "the header name (entry in the combo box) and the second element is the contents."},
512 {"attach", (PyCFunction)ComposeWindow_attach, METH_VARARGS,
513 "attach(filenames) - attach a list of files\n"
515 "Attach files to the mail. The filenames argument is a list of\n"
516 "string of the filenames that are being attached."},
518 {"get_account_selection", (PyCFunction)ComposeWindow_get_account_selection, METH_NOARGS,
519 "get_account_selection() - get account selection widget\n"
521 "Returns the account selection combo box as a gtk.ComboBox"},
523 {"save_message_to", (PyCFunction)ComposeWindow_save_message_to, METH_VARARGS,
524 "save_message_to(folder) - save message to folder id\n"
526 "Set the folder where the sent message will be saved to. folder may be\n"
527 "a Folder, a string of the folder identifier (e.g. #mh/foo/bar), or\n"
528 "None is which case the message will not be saved at all."},
530 {"set_modified", (PyCFunction)ComposeWindow_set_modified, METH_VARARGS,
531 "set_modified(bool) - set or unset modification marker of compose window\n"
533 "Set or unset the modification marker of the compose window. This marker determines\n"
534 "for example whether you get a confirmation dialog when closing the compose window\n"
536 "In the usual case, Claws Mail keeps track of the modification status itself.\n"
537 "However, there are cases when it might be desirable to overwrite the marker,\n"
538 "for example because a compose_any script modifies the body or subject which\n"
539 "can be regarded compose window preprocessing and should not trigger a confirmation\n"
540 "dialog on close like a manual edit."},
545 static PyMemberDef ComposeWindow_members[] = {
546 {"ui_manager", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, ui_manager), 0,
547 "ui_manager - the gtk.UIManager of the compose window"},
549 {"text", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, text), 0,
550 "text - the gtk.TextView widget of the message body"},
552 {"replyinfo", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, replyinfo), 0,
553 "replyinfo - The MessageInfo object of the message that is being replied to, or None"},
555 {"forwardinfo", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, fwdinfo), 0,
556 "forwardinfo - The MessageInfo object of the message that is being forwarded, or None"},
561 static PyGetSetDef ComposeWindow_getset[] = {
562 {"account", (getter)get_account, (setter)NULL,
563 "account - the account corresponding to this compose window", NULL},
568 static PyTypeObject clawsmail_ComposeWindowType = {
569 PyObject_HEAD_INIT(NULL)
571 "clawsmail.ComposeWindow", /*tp_name*/
572 sizeof(clawsmail_ComposeWindowObject), /*tp_basicsize*/
574 (destructor)ComposeWindow_dealloc, /*tp_dealloc*/
581 0, /*tp_as_sequence*/
589 Py_TPFLAGS_DEFAULT, /*tp_flags*/
591 "ComposeWindow objects. Optional argument to constructor: sender account address. ",
594 0, /* tp_richcompare */
595 0, /* tp_weaklistoffset */
598 ComposeWindow_methods, /* tp_methods */
599 ComposeWindow_members, /* tp_members */
600 ComposeWindow_getset, /* tp_getset */
603 0, /* tp_descr_get */
604 0, /* tp_descr_set */
605 0, /* tp_dictoffset */
606 (initproc)ComposeWindow_init, /* tp_init */
611 gboolean cmpy_add_composewindow(PyObject *module)
613 clawsmail_ComposeWindowType.tp_new = PyType_GenericNew;
614 if(PyType_Ready(&clawsmail_ComposeWindowType) < 0)
617 Py_INCREF(&clawsmail_ComposeWindowType);
618 return (PyModule_AddObject(module, "ComposeWindow", (PyObject*)&clawsmail_ComposeWindowType) == 0);
621 PyObject* clawsmail_compose_new(PyObject *module, Compose *compose)
623 PyObject *class, *dict;
624 PyObject *self, *args, *kw;
631 dict = PyModule_GetDict(module);
632 class = PyDict_GetItemString(dict, "ComposeWindow");
633 args = Py_BuildValue("()");
634 kw = Py_BuildValue("{s:b}", "__open_window", 0);
635 self = PyObject_Call(class, args, kw);
638 composewindow_set_compose((clawsmail_ComposeWindowObject*)self, compose);