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"
28 #include "clawsmailmodule.h"
29 #include "foldertype.h"
30 #include "messageinfotype.h"
32 #include "mainwindow.h"
34 #include "summaryview.h"
36 #include <structmember.h>
46 } clawsmail_ComposeWindowObject;
48 static void ComposeWindow_dealloc(clawsmail_ComposeWindowObject* self)
50 Py_XDECREF(self->ui_manager);
51 Py_XDECREF(self->text);
52 Py_XDECREF(self->replyinfo);
53 self->ob_type->tp_free((PyObject*)self);
56 static void flush_gtk_queue(void)
58 while(gtk_events_pending())
62 static void store_py_object(PyObject **target, PyObject *obj)
76 static void composewindow_set_compose(clawsmail_ComposeWindowObject *self, Compose *compose)
78 self->compose = compose;
80 store_py_object(&(self->ui_manager), get_gobj_from_address(compose->ui_manager));
81 store_py_object(&(self->text), get_gobj_from_address(compose->text));
83 store_py_object(&(self->replyinfo), clawsmail_messageinfo_new(compose->replyinfo));
86 static int ComposeWindow_init(clawsmail_ComposeWindowObject *self, PyObject *args, PyObject *kwds)
89 PrefsAccount *ac = NULL;
93 gboolean did_find_compose;
94 Compose *compose = NULL;
96 unsigned char open_window;
97 /* if __open_window is set to 0/False,
98 * composewindow_set_compose must be called before this object is valid */
99 static char *kwlist[] = {"address", "__open_window", NULL};
103 PyArg_ParseTupleAndKeywords(args, kwds, "|sb", kwlist, &ss, &open_window);
106 mainwin = mainwindow_get_mainwindow();
107 item = mainwin->summaryview->folder_item;
108 did_find_compose = FALSE;
111 ac = account_find_from_address(ss, FALSE);
112 if (ac && ac->protocol != A_NNTP) {
113 compose = compose_new_with_folderitem(ac, item, NULL);
114 did_find_compose = TRUE;
117 if(!did_find_compose) {
119 ac = account_find_from_item(item);
120 if (ac && ac->protocol != A_NNTP) {
121 compose = compose_new_with_folderitem(ac, item, NULL);
122 did_find_compose = TRUE;
126 /* use current account */
127 if (!did_find_compose && cur_account && (cur_account->protocol != A_NNTP)) {
128 compose = compose_new_with_folderitem(cur_account, item, NULL);
129 did_find_compose = TRUE;
132 if(!did_find_compose) {
133 /* just get the first one */
134 list = account_get_list();
135 for (cur = list ; cur != NULL ; cur = g_list_next(cur)) {
136 ac = (PrefsAccount *) cur->data;
137 if (ac->protocol != A_NNTP) {
138 compose = compose_new_with_folderitem(ac, item, NULL);
139 did_find_compose = TRUE;
145 if(!did_find_compose)
148 composewindow_set_compose(self, compose);
149 gtk_widget_show_all(compose->window);
155 /* this is here because wrapping GTK_EDITABLEs in PyGTK is buggy */
156 static PyObject* get_python_object_from_gtk_entry(GtkWidget *entry)
158 return Py_BuildValue("s", gtk_entry_get_text(GTK_ENTRY(entry)));
161 static PyObject* set_gtk_entry_from_python_object(GtkWidget *entry, PyObject *args)
165 if(!PyArg_ParseTuple(args, "s", &ss))
168 gtk_entry_set_text(GTK_ENTRY(entry), ss);
174 static PyObject* ComposeWindow_get_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
176 return get_python_object_from_gtk_entry(self->compose->subject_entry);
179 static PyObject* ComposeWindow_set_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
182 ret = set_gtk_entry_from_python_object(self->compose->subject_entry, args);
187 static PyObject* ComposeWindow_get_from(clawsmail_ComposeWindowObject *self, PyObject *args)
189 return get_python_object_from_gtk_entry(self->compose->from_name);
192 static PyObject* ComposeWindow_set_from(clawsmail_ComposeWindowObject *self, PyObject *args)
195 ret = set_gtk_entry_from_python_object(self->compose->from_name, args);
200 static PyObject* ComposeWindow_add_To(clawsmail_ComposeWindowObject *self, PyObject *args)
204 if(!PyArg_ParseTuple(args, "s", &ss))
207 compose_entry_append(self->compose, ss, COMPOSE_TO, PREF_NONE);
215 static PyObject* ComposeWindow_add_Cc(clawsmail_ComposeWindowObject *self, PyObject *args)
219 if(!PyArg_ParseTuple(args, "s", &ss))
222 compose_entry_append(self->compose, ss, COMPOSE_CC, PREF_NONE);
230 static PyObject* ComposeWindow_add_Bcc(clawsmail_ComposeWindowObject *self, PyObject *args)
234 if(!PyArg_ParseTuple(args, "s", &ss))
237 compose_entry_append(self->compose, ss, COMPOSE_BCC, PREF_NONE);
245 static PyObject* ComposeWindow_attach(clawsmail_ComposeWindowObject *self, PyObject *args)
248 Py_ssize_t size, iEl;
251 if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist))
254 size = PyList_Size(olist);
255 for(iEl = 0; iEl < size; iEl++) {
257 PyObject *element = PyList_GET_ITEM(olist, iEl);
263 if(!PyArg_Parse(element, "s", &ss)) {
269 list = g_list_prepend(list, ss);
273 compose_attach_from_list(self->compose, list, FALSE);
282 static PyObject* ComposeWindow_get_header_list(clawsmail_ComposeWindowObject *self, PyObject *args)
287 retval = Py_BuildValue("[]");
288 for(walk = self->compose->header_list; walk; walk = walk->next) {
289 ComposeHeaderEntry *headerentry = walk->data;
293 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo))), 0, -1);
294 text = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
296 if(text && strcmp("", text)) {
300 ee = Py_BuildValue("(ss)", header, text);
301 ok = PyList_Append(retval, ee);
312 static PyObject* ComposeWindow_add_header(clawsmail_ComposeWindowObject *self, PyObject *args)
318 if(!PyArg_ParseTuple(args, "ss", &header, &text))
321 /* add a dummy, and modify it then */
322 compose_entry_append(self->compose, "dummy1dummy2dummy3", COMPOSE_TO, PREF_NONE);
323 num = g_slist_length(self->compose->header_list);
325 ComposeHeaderEntry *headerentry;
326 headerentry = g_slist_nth_data(self->compose->header_list, num-2);
328 GtkEditable *editable;
330 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), text);
331 editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo)));
332 gtk_editable_delete_text(editable, 0, -1);
333 gtk_editable_insert_text(editable, header, -1, &pos);
343 /* this is pretty ugly, as the compose struct does not maintain a pointer to the account selection combo */
344 static PyObject* ComposeWindow_get_account_selection(clawsmail_ComposeWindowObject *self, PyObject *args)
346 GList *children, *walk;
348 children = gtk_container_get_children(GTK_CONTAINER(self->compose->header_table));
349 for(walk = children; walk; walk = walk->next) {
350 if(GTK_IS_HBOX(walk->data)) {
351 GList *children2, *walk2;
352 children2 = gtk_container_get_children(GTK_CONTAINER(walk->data));
353 for(walk2 = children2; walk2; walk2 = walk2->next) {
354 if(GTK_IS_EVENT_BOX(walk2->data)) {
355 return get_gobj_from_address(gtk_container_get_children(GTK_CONTAINER(walk2->data))->data);
364 static PyObject* ComposeWindow_save_message_to(clawsmail_ComposeWindowObject *self, PyObject *args)
368 if(!PyArg_ParseTuple(args, "O", &arg))
371 if(PyString_Check(arg)) {
372 GtkEditable *editable;
375 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);
377 editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
378 gtk_editable_delete_text(editable, 0, -1);
379 gtk_editable_insert_text(editable, PyString_AsString(arg), -1, &pos);
381 else if(clawsmail_folder_check(arg)) {
382 GtkEditable *editable;
385 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);
387 editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
388 gtk_editable_delete_text(editable, 0, -1);
389 gtk_editable_insert_text(editable, folder_item_get_identifier(clawsmail_folder_get_item(arg)), -1, &pos);
391 else if (arg == Py_None){
392 /* turn off checkbutton */
393 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), FALSE);
396 PyErr_SetString(PyExc_TypeError, "function takes exactly one argument which may be a folder object, a string, or None");
406 static PyObject* ComposeWindow_set_modified(clawsmail_ComposeWindowObject *self, PyObject *args)
409 gboolean old_modified;
411 if(!PyArg_ParseTuple(args, "b", &modified))
414 old_modified = self->compose->modified;
416 self->compose->modified = (modified != 0);
418 /* If the modified state changed, rewrite window title.
419 * This partly duplicates functionality in compose.c::compose_set_title().
420 * While it's nice to not have to modify Claws Mail for this to work,
421 * it would be cleaner to export that function in Claws Mail. */
422 if((strcmp(gtk_window_get_title(GTK_WINDOW(self->compose->window)), _("Compose message")) != 0) &&
423 (old_modified != self->compose->modified)) {
428 edited = self->compose->modified ? _(" [Edited]") : "";
429 subject = gtk_editable_get_chars(GTK_EDITABLE(self->compose->subject_entry), 0, -1);
430 if(subject && strlen(subject))
431 str = g_strdup_printf(_("%s - Compose message%s"),
434 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
435 gtk_window_set_title(GTK_WINDOW(self->compose->window), str);
446 static PyMethodDef ComposeWindow_methods[] = {
447 {"set_subject", (PyCFunction)ComposeWindow_set_subject, METH_VARARGS,
448 "set_subject(text) - set subject to text\n"
450 "Set the subject to text. text must be a string."},
452 {"get_subject", (PyCFunction)ComposeWindow_get_subject, METH_NOARGS,
453 "get_subject() - get subject\n"
455 "Get a string of the current subject entry."},
457 {"set_from", (PyCFunction)ComposeWindow_set_from, METH_VARARGS,
458 "set_from(text) - set From header entry to text\n"
460 "Set the From header entry to text. text must be a string.\n"
461 "Beware: No sanity checking is performed."},
463 {"get_from", (PyCFunction)ComposeWindow_get_from, METH_NOARGS,
464 "get_from - get From header entry\n"
466 "Get a string of the current From header entry."},
468 {"add_To", (PyCFunction)ComposeWindow_add_To, METH_VARARGS,
469 "add_To(text) - append another To header with text\n"
471 "Add another header line with the combo box set to To:, and the\n"
472 "content set to text."},
474 {"add_Cc", (PyCFunction)ComposeWindow_add_Cc, METH_VARARGS,
475 "add_Cc(text) - append another Cc header with text\n"
477 "Add another header line with the combo box set to Cc:, and the\n"
478 "content set to text."},
480 {"add_Bcc", (PyCFunction)ComposeWindow_add_Bcc, METH_VARARGS,
481 "add_Bcc(text) - append another Bcc header with text\n"
483 "Add another header line with the combo box set to Bcc:, and the\n"
484 "content set to text."},
486 {"add_header", (PyCFunction)ComposeWindow_add_header, METH_VARARGS,
487 "add_header(headername, text) - add a custom header\n"
489 "Adds a custom header with the header set to headername, and the\n"
490 "contents set to text."},
492 {"get_header_list", (PyCFunction)ComposeWindow_get_header_list, METH_NOARGS,
493 "get_header_list() - get list of headers\n"
495 "Gets a list of headers that are currently defined in the compose window.\n"
496 "The return value is a list of tuples, where the first tuple element is\n"
497 "the header name (entry in the combo box) and the second element is the contents."},
499 {"attach", (PyCFunction)ComposeWindow_attach, METH_VARARGS,
500 "attach(filenames) - attach a list of files\n"
502 "Attach files to the mail. The filenames argument is a list of\n"
503 "string of the filenames that are being attached."},
505 {"get_account_selection", (PyCFunction)ComposeWindow_get_account_selection, METH_NOARGS,
506 "get_account_selection() - get account selection widget\n"
508 "Returns the account selection combo box as a gtk.ComboBox"},
510 {"save_message_to", (PyCFunction)ComposeWindow_save_message_to, METH_VARARGS,
511 "save_message_to(folder) - save message to folder id\n"
513 "Set the folder where the sent message will be saved to. folder may be\n"
514 "a Folder, a string of the folder identifier (e.g. #mh/foo/bar), or\n"
515 "None is which case the message will not be saved at all."},
517 {"set_modified", (PyCFunction)ComposeWindow_set_modified, METH_VARARGS,
518 "set_modified(bool) - set or unset modification marker of compose window\n"
520 "Set or unset the modification marker of the compose window. This marker determines\n"
521 "for example whether you get a confirmation dialog when closing the compose window\n"
523 "In the usual case, Claws Mail keeps track of the modification status itself.\n"
524 "However, there are cases when it might be desirable to overwrite the marker,\n"
525 "for example because a compose_any script modifies the body or subject which\n"
526 "can be regarded compose window preprocessing and should not trigger a confirmation\n"
527 "dialog on close like a manual edit."},
532 static PyMemberDef ComposeWindow_members[] = {
533 {"ui_manager", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, ui_manager), 0,
534 "ui_manager - the gtk.UIManager of the compose window"},
536 {"text", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, text), 0,
537 "text - the gtk.TextView widget of the message body"},
539 {"replyinfo", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, replyinfo), 0,
540 "replyinfo - The MessageInfo object of the message that is being replied to, or None"},
545 static PyTypeObject clawsmail_ComposeWindowType = {
546 PyObject_HEAD_INIT(NULL)
548 "clawsmail.ComposeWindow", /*tp_name*/
549 sizeof(clawsmail_ComposeWindowObject), /*tp_basicsize*/
551 (destructor)ComposeWindow_dealloc, /*tp_dealloc*/
558 0, /*tp_as_sequence*/
566 Py_TPFLAGS_DEFAULT, /*tp_flags*/
568 "ComposeWindow objects. Optional argument to constructor: sender account address. ",
571 0, /* tp_richcompare */
572 0, /* tp_weaklistoffset */
575 ComposeWindow_methods, /* tp_methods */
576 ComposeWindow_members, /* tp_members */
580 0, /* tp_descr_get */
581 0, /* tp_descr_set */
582 0, /* tp_dictoffset */
583 (initproc)ComposeWindow_init, /* tp_init */
588 PyMODINIT_FUNC initcomposewindow(PyObject *module)
590 clawsmail_ComposeWindowType.tp_new = PyType_GenericNew;
591 if(PyType_Ready(&clawsmail_ComposeWindowType) < 0)
594 Py_INCREF(&clawsmail_ComposeWindowType);
595 PyModule_AddObject(module, "ComposeWindow", (PyObject*)&clawsmail_ComposeWindowType);
598 PyObject* clawsmail_compose_new(PyObject *module, Compose *compose)
600 PyObject *class, *dict;
601 PyObject *self, *args, *kw;
608 dict = PyModule_GetDict(module);
609 class = PyDict_GetItemString(dict, "ComposeWindow");
610 args = Py_BuildValue("()");
611 kw = Py_BuildValue("{s:b}", "__open_window", 0);
612 self = PyObject_Call(class, args, kw);
615 composewindow_set_compose((clawsmail_ComposeWindowObject*)self, compose);