talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit e4f8cd00497d548e9afb874183e64ad335bb598d
parent 98b03dfda49ad0c206a73b6d8741587b6b5d60b5
Author: Holger Berndt <hb@claws-mail.org>
Date:   Sun,  7 Apr 2013 15:04:41 +0200

Python plugin: Add examples

Diffstat:
M.gitignore | 2++
Mconfigure.ac | 13+++++++------
Msrc/plugins/python/Makefile.am | 2++
Asrc/plugins/python/examples/Makefile.am | 10++++++++++
Asrc/plugins/python/examples/README.examples | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/examples/auto/compose_any | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/examples/auto/shutdown | 23+++++++++++++++++++++++
Asrc/plugins/python/examples/auto/startup | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/examples/compose/Macro-Expansion | 28++++++++++++++++++++++++++++
Asrc/plugins/python/examples/main/Create-Tomboy-Note | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/examples/main/Mass-mail | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/examples/main/Open-Tomboy-Notes | 25+++++++++++++++++++++++++
Asrc/plugins/python/examples/main/Print-action-names-to-stdout | 5+++++
Asrc/plugins/python/examples/main/Recusively-mark-messages-as-read | 35+++++++++++++++++++++++++++++++++++
14 files changed, 511 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -387,6 +387,8 @@ /src/plugins/python/Makefile /src/plugins/python/Makefile.in /src/plugins/python/*.o +/src/plugins/python/examples/Makefile +/src/plugins/python/examples/Makefile.in /src/plugins/rssyl/.deps /src/plugins/rssyl/*.la /src/plugins/rssyl/.libs diff --git a/configure.ac b/configure.ac @@ -101,7 +101,7 @@ AC_SYS_LARGEFILE dnl ****************************** dnl Checks for host -dnl Not needed anymore because we +dnl Not needed anymore because we dnl do AC_CANONICAL_SYSTEM above dnl ****************************** dnl AC_CANONICAL_HOST @@ -184,7 +184,7 @@ case "$target" in CFLAGS="$CFLAGS -std=gnu99 -DSOLARIS" ;; esac - + dnl Checks for iconv AM_ICONV @@ -379,7 +379,7 @@ AC_CHECK_HEADERS(fcntl.h sys/file.h unistd.h paths.h \ AC_CHECK_HEADER([execinfo.h], [AC_DEFINE(HAVE_BACKTRACE,1,[Has backtrace*() needed for retrieving stack traces])]) AC_SEARCH_LIBS(backtrace_symbols, [execinfo]) -dnl alf - Check for apache installation f*ck up. apache may also install an +dnl alf - Check for apache installation f*ck up. apache may also install an dnl fnmatch, which includes their own regex stuff if USE_HSREGEX is defined AC_TRY_COMPILE([#include <stdlib.h> #include <fnmatch.h>], @@ -427,7 +427,7 @@ dnl ***************** dnl check for glib PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.6 gmodule-2.0 >= 2.6 gobject-2.0 >= 2.6 gthread-2.0 >= 2.6) - + GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0` AC_SUBST(GLIB_GENMARSHAL) @@ -512,7 +512,7 @@ AC_ARG_WITH(config-dir, [ --with-config-dir=RCDIR Local configuration d ac_cv_with_config_dir="$withval", ac_cv_with_config_dir=".claws-mail") if test x"$ac_cv_with_config_dir" = x""; then ac_cv_with_config_dir=".claws-mail" -fi +fi AC_DEFINE_UNQUOTED(CFG_RC_DIR, "$ac_cv_with_config_dir", Configuration directory) dnl ************************ @@ -607,7 +607,7 @@ dnl Check for X-Face support AC_MSG_CHECKING([whether to use compface]) if test x"$enable_compface" = xyes; then AC_MSG_RESULT(yes) - AC_CHECK_LIB(compface, uncompface, + AC_CHECK_LIB(compface, uncompface, [AC_DEFINE(HAVE_LIBCOMPFACE, 1, Define if you want compface support.)], [enable_compface=no]) if test x"$enable_compface" = xyes; then @@ -1895,6 +1895,7 @@ src/plugins/pdf_viewer/Makefile src/plugins/perl/Makefile src/plugins/perl/tools/Makefile src/plugins/python/Makefile +src/plugins/python/examples/Makefile src/plugins/pgpcore/Makefile src/plugins/pgpmime/Makefile src/plugins/pgpinline/Makefile diff --git a/src/plugins/python/Makefile.am b/src/plugins/python/Makefile.am @@ -1,3 +1,5 @@ +SUBDIRS = examples + plugindir = $(pkglibdir)/plugins if BUILD_PYTHON_PLUGIN diff --git a/src/plugins/python/examples/Makefile.am b/src/plugins/python/examples/Makefile.am @@ -0,0 +1,10 @@ +EXTRA_DIST = \ + README.examples \ + auto/shutdown \ + auto/startup \ + compose/Macro-Expansion \ + main/Create-Tomboy-Note \ + main/Mass-mail \ + main/Open-Tomboy-Notes \ + main/Print-action-names-to-stdout \ + main/Recusively-mark-messages-as-read diff --git a/src/plugins/python/examples/README.examples b/src/plugins/python/examples/README.examples @@ -0,0 +1,58 @@ +This directory tree contains some example scripts. Feel free +to copy them to the appropriate subdirectories under +~/.claws-mail/python-scripts and adjust them to your needs. + +Unless otherwise noted in the respective files, all example snippets +are in the public domain. + +Table of contents: +================== + +* auto/startup + An example startup script. It demonstrates how to modify the menu of + Claws Mail's main window by adding a menu item under the Help menu + to generate and browse the API documentation of the plugin. + It also adds a menu item to mark a whole thread as read in the + Edit menu. + Furthermore, it shows how to build a D-Bus bridge to Claws Mail + that other processes can use to trigger Claws Mail events. + Also see auto/shutdown for cleanup. + +* auto/shutdown + Demonstrates how to clean up the stuff that has been introduced in + the auto/startup example. + +* auto/compose_any + An example compose script. It demonstrates how to strip reply + prefixes that Claws Mail doesn't yet know about from the subject + header. + +* main/Print-action-names-to-stdout + Prints the names of all actions that are currently in the + action group of Claws Mail's main window to standard output + +* main/Recursively-mark-messages-as-read + Demonstrates how to perform actions to a directory tree under the + currently selected folder in the main window. + +* main/Mass-mail + Demonstrates how to automate composing of mails, and sending them out + +* main/Create-Tomboy-Note + Create a Tomboy note for each selected message, containing the + subject as note title and a Claws Mail link in the body which + links back to Claws Mail (requires the Claws Mail addin for Tomboy). + Optionally, the user can choose to add a marker string for the + Tomboy Reminder addin into the note. + To achieve this, this example demonstrates how to talk to other + programs via D-Bus, as well as how to query additional information + from the user using direct GTK+ programming. + +* main/Open-Tomboy-Notes + This example opens all Tomboy notes which link to any of the currently + selected messages, and thus completes the round trip from the + "Create-Tomboy-Note" example. + +* compose/Macro-Expansion + Demonstrates how to do text expansion in the compose window + mail body editor diff --git a/src/plugins/python/examples/auto/compose_any b/src/plugins/python/examples/auto/compose_any @@ -0,0 +1,50 @@ +import re + +# Strip prefixes in subject +# +# When replying to a reply, Claws Mail strips the "Re: " reply +# marker of the subject line before prefixing with its own. +# Claws Mail also knows several localized variants from various +# mailers, such as "Aw: " (German Outlook), "Odp: " (Polish Outlook) +# and so on. +# +# However, it doesn't know all, and as of 3.8.1, adding new ones +# is not possible via a config option. +# +# This function is there to add new markers. It will also strip +# an already messed up original subject line, provided that all +# prefixes are defined below. +# So, for example, replying to a mail with +# Subject: R: Re: R: Re: Aw: R: Re: Old topic +# will result in +# Subject: Re: Old topic +# +# This is a slightly adapted version of a script provided +# by Michael Gmelin and Slavko on Claws Mail's users mailing list. +def strip_subject_prefixes(): + # A list of prefixes to strip during reply. Add the ones that are + # interesting for you here. + prefixes = ["Re", "R", "Odp", "Aw"] + + # Build up regex to match unwanted prefixes + prefix_string = "|".join(prefixes) + regex_str = r"^(Re|Fwd|Fw):( (%s):)+" % prefix_string + + # Get a string with those prefixes stripped + new_subject = re.sub(regex_str, r"\1:", clawsmail.compose_window.get_subject()) + + # Set this string to be the new subject + clawsmail.compose_window.set_subject(new_subject) + + # Normally, when the subject or body is modified, the mail gets marked + # as modified, wich results in a popup dialog when the compose window is + # just closed. We don't want to treat the automatic modification of the + # subject line from above to trigger such a popup, so we override the + # modification marker. + # Note that this affects only for the modifications done so far. As + # soon as the user starts to modify the mail manually, it will be + # set again. + clawsmail.compose_window.set_modified(False) + + +strip_subject_prefixes() +\ No newline at end of file diff --git a/src/plugins/python/examples/auto/shutdown b/src/plugins/python/examples/auto/shutdown @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +import pygtk +pygtk.require('2.0') +import gtk + +# stuff that only needs to be cleaned up if claws mail is not exiting anyways +if not clawsmail.is_exiting(): + + # cleanup menus and actions that have been added in the startup script + try: + ui_manager = clawsmail.get_mainwindow_ui_manager() + for merge_id in mainwindow_merge_ids: + ui_manager.remove_ui(merge_id) + except NameError: + pass + try: + group = clawsmail.get_mainwindow_action_group() + for action in mainwindow_actions: + group.remove_action(action) + except NameError: + pass + diff --git a/src/plugins/python/examples/auto/startup b/src/plugins/python/examples/auto/startup @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +import pygtk +pygtk.require('2.0') +import gtk + +# lists to store information for cleanup in the shutdown script +mainwindow_merge_ids = [] +mainwindow_actions = [] + + +# function definitions + +def add_python_documentation_menu_item(): + # Adds a Help -> Python API documentation menu item + def pydoc_cb(action): + # callback for "Python API documentation" action + import os, tempfile, subprocess, pydoc + working_directory = os.getcwd() # store current working directory + os.chdir(tempfile.gettempdir()) # switch to a temporary directory + pydoc.writedoc(clawsmail) # write out API documentation to $TEMP/clawsmail.html + subprocess.Popen(["xdg-open", "clawsmail.html"]) # start html viewer in the background + os.chdir(working_directory) # switch back to original working directory + + global mainwindow_merge_ids + global mainwindow_actions + + # create "Python API documentation" menu item + group = clawsmail.get_mainwindow_action_group() + ui_manager = clawsmail.get_mainwindow_ui_manager() + action = gtk.Action("pydoc", "Python API documentation", None, None) + action.connect("activate", pydoc_cb) + group.add_action(action) + merge_id = ui_manager.new_merge_id() + ui_manager.add_ui(merge_id, "/Menu/Help", "pydoc", "pydoc", gtk.UI_MANAGER_MENUITEM, True) + mainwindow_merge_ids.append(merge_id) + mainwindow_actions.append(action) + +def add_mark_thread_read_menu_item(): + # Adds an Edit -> Mark thread as read menu item + def thread_read_cb(action): + # callback for "Mark thread as read" action + selected_messages = clawsmail.get_summaryview_selected_message_list() + group = clawsmail.get_mainwindow_action_group() + group.get_action("Edit/SelectThread").activate() + group.get_action("Message/Mark/MarkRead").activate() + clawsmail.summaryview_select_messages(selected_messages) + + global mainwindow_merge_ids + global mainwindow_actions + + # create "Mark thread read" menu item + group = clawsmail.get_mainwindow_action_group() + ui_manager = clawsmail.get_mainwindow_ui_manager() + action = gtk.Action("ThreadRead", "Mark thread as read", None, None) + action.connect("activate", thread_read_cb) + group.add_action_with_accel(action, None) + merge_id = ui_manager.new_merge_id() + ui_manager.add_ui(merge_id, "/Menu/Edit", "ThreadRead", "ThreadRead", gtk.UI_MANAGER_MENUITEM, False) + mainwindow_merge_ids.append(merge_id) + mainwindow_actions.append(action) + +def add_dbus_interface(): + # exports an interface to Claws Mail on the session D-Bus + # + # Example invokation to trigger an update of the summary view from the command line: + # dbus-send --session --type=method_call --dest=org.ClawsMail.PythonPlugin /org/ClawsMail/PythonPlugin org.ClawsMail.PythonPlugin.MainWindow.TriggerGtkAction string:'View/UpdateSummary' + try: + import dbus + import dbus.service + from dbus.mainloop.glib import DBusGMainLoop + except ImportError: + print 'Cannot setup D-Bus interface: D-Bus Python bindings not available.' + return None + + class ClawsMailService(dbus.service.Object): + @dbus.service.method("org.ClawsMail.PythonPlugin.MainWindow", in_signature='s', out_signature='') + def TriggerGtkAction(self, action_path): + action = clawsmail.get_mainwindow_action_group().get_action(action_path) + if action: + action.activate() + else: + print 'No such action:', action_path + + loop = DBusGMainLoop(set_as_default=True) + session_bus = dbus.SessionBus() + name = dbus.service.BusName("org.ClawsMail.PythonPlugin", session_bus) + object = ClawsMailService(session_bus, '/org/ClawsMail/PythonPlugin') + return name + +# call the functions that have been defined above, or comment the functions that you want to omit +add_python_documentation_menu_item() +add_mark_thread_read_menu_item() +dbus_interface = add_dbus_interface() diff --git a/src/plugins/python/examples/compose/Macro-Expansion b/src/plugins/python/examples/compose/Macro-Expansion @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# If the cursor in the compose window body is on a word +# that is listed in this table as a key (element before the colon), +# this word is replaced with the value (element after the colon). +# To add key/value pairs, just add more lines. +replacement_table = { + "dsm" : "Dear Sir/Madam,\n", + "th" : "Thanks,\nHolger", + "blake" : "And did those feet in ancient time\nWalk upon England's mountains green?\nAnd was the holy Lamb of God\nOn England's pleasant pastures seen?", +} + +# helper function to get the current word under the cursor +def get_current_word(buffer): + start = buffer.get_iter_at_mark(buffer.get_insert()) + end = buffer.get_iter_at_mark(buffer.get_insert()) + if not start.starts_word(): + start.backward_word_start() + if not end.ends_word(): + end.forward_word_end() + return (start.get_text(end), start, end) + + +buffer = clawsmail.compose_window.text.get_buffer() # get text buffer of body editor +(current_word, start, end) = get_current_word(buffer) # get current word under the cursor +if current_word in replacement_table: # if current word is a key in the replacement table... + buffer.delete(start, end) # delete the current word + buffer.insert(start, replacement_table[current_word]) # and insert the replacement in its place diff --git a/src/plugins/python/examples/main/Create-Tomboy-Note b/src/plugins/python/examples/main/Create-Tomboy-Note @@ -0,0 +1,130 @@ +# -*- Mode: python -*- + +import dbus +import gtk + +def add_note(msg): + + def get_reminder(desc): + get_reminder.reminder = None + + # callback functions for the GUI + def no_reminder(button, ww): + get_reminder.reminder = "" + ww.destroy() + + def date_time_cb(button, ww, cal, cb, hours, minutes): + ymd = list(cal.get_date()) + ymd[1] += 1 + get_reminder.reminder = "/".join([str(v) for v in ymd]) + if cb.get_active(): + get_reminder.reminder += "".join([" at ", "%02d" % hours.get_value_as_int(), ":", "%02d" % minutes.get_value_as_int()]) + ww.destroy() + + def day_selected(cal, exp): + ymd = list(cal.get_date()) + ymd[1] += 1 + exp.set_label("/".join(str(vv) for vv in ymd)) + + def custom(button, ww, entry): + get_reminder.reminder = entry.get_text() + ww.destroy() + + + # Check if the user wants a reminder in a dialog box + win = gtk.Window() + win.set_title("Reminder") + win.connect("destroy", gtk.main_quit) + win.set_position(gtk.WIN_POS_CENTER) + table = gtk.Table(2,7) + win.add(table) + table.attach(gtk.Label(desc), 0, 2, 0, 1) + # no reminder + button = gtk.Button("No reminder") + button.connect("clicked", no_reminder, win) + table.attach(button, 0, 1, 1, 2, xoptions=gtk.FILL, yoptions=0, ypadding=4) + table.attach(gtk.HSeparator(), 0, 2, 2, 3) + # date / time reminder + button = gtk.Button("Date/Time") + table.attach(button, 0, 1, 3, 4, xoptions=gtk.FILL, yoptions=gtk.EXPAND | gtk.FILL, ypadding=4) + hbox = gtk.HBox() + table.attach(hbox, 1, 2, 3, 4) + cal = gtk.Calendar() + exp = gtk.Expander() + day_selected(cal, exp) + cal.connect("day-selected", day_selected, exp) + exp.add(cal) + hbox.pack_start(exp) + cb = gtk.CheckButton("at") + hbox.pack_start(cb, False, False) + hours = gtk.SpinButton(gtk.Adjustment(12.0, 0.0, 24.0, 1.0, 5.0, 0.0)) + hours.set_numeric(True) + hours.set_wrap(True) + hbox.pack_start(hours, False, False) + hbox.pack_start(gtk.Label(":"), False, False) + minutes = gtk.SpinButton(gtk.Adjustment(0.0, 0.0, 59.0, 1.0, 5.0, 0.0)) + minutes.set_numeric(True) + minutes.set_wrap(True) + hbox.pack_start(minutes, False, False) + button.connect("clicked", date_time_cb, win, cal, cb, hours, minutes) + # custom + button = gtk.Button("custom") + table.attach(button, 0, 1, 4, 5, xoptions=gtk.FILL, yoptions=0, ypadding=4) + entry = gtk.Entry() + button.connect("clicked", custom, win, entry) + table.attach(entry, 1, 2, 4, 5) + + # "Show note" toggle option + table.attach(gtk.HSeparator(), 0, 2, 5, 6) + cb = gtk.CheckButton("Show note") + table.attach(cb, 0, 2, 6, 7) + + win.show_all() + win.present() + gtk.main() + return (get_reminder.reminder, cb.get_active()) + + title = msg.Subject + # set up contents: opening tag + content = ["<note-content>"] + # title + content.append(title) + content.append("\n\n") + # reminder if wanted + (reminder, show_note) = get_reminder("\n".join([msg.From, msg.Subject])) + if reminder == None: + return + if reminder: + content.extend(["!", reminder, "\n"]) + # link back to email + msgid = msg.MessageID + if msgid[0] != "<": + msgid = "<" + msgid + if msgid[-1] != ">": + msgid += ">" + msgid = msgid.replace("<", "&lt;").replace(">", "&gt;") + link = '<link:cm-mail uri="%s/%s">%s</link:cm-mail>' % (clawsmail.get_folderview_selected_folder().get_identifier(), msgid, msg.Subject) + content.append(link) + #closing tag + content.append("</note-content>") + + # create a tomboy note + bus = dbus.SessionBus() + rc = bus.get_object('org.gnome.Tomboy', '/org/gnome/Tomboy/RemoteControl') + rc_iface = dbus.Interface(rc, dbus_interface='org.gnome.Tomboy.RemoteControl') + uri = rc_iface.CreateNamedNote(title) + rc_iface.SetNoteContentsXml(uri, "".join(content)) + rc_iface.DisplayNote(uri) + if not show_note: + rc_iface.HideNote(uri) + + +# iterate over all notes +for msg in clawsmail.get_summaryview_selected_message_list(): + add_note(msg) + + + + + + diff --git a/src/plugins/python/examples/main/Mass-mail b/src/plugins/python/examples/main/Mass-mail @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +# Send the same mail to a list of people, one at a time. +# As this is normal Python code, the message contents and +# the addresses could also come from an external source +# (such as a file, or a database). +addresses = ["mail1@example.tld", "mail2@example.tld", "mail3@example.tld"] + +for address in addresses: + # The argument for the constructor is an email address on which + # the sending account is selected. + # It's also possible to use the default constructor without arguments, + # in which case the same rules as on a menu click one "New message" + # are applied. + cw = clawsmail.ComposeWindow("berndth@gmx.de") + + # Add a recipient. There are also add_Cc and add_Bcc functions. + cw.add_To(address) + + # Set the subject of the message + cw.set_subject("Mass mail") + + # For the message body, access to the GtkTextView is granted in ComposeWindow.text. + buffer = cw.text.get_buffer() + buffer.set_text("This is an automatic message") + + # Access to the GtkUIManager is also provided, look for "send later" action + action = None + for action_group in cw.ui_manager.get_action_groups(): + for action in action_group.list_actions(): + action = action_group.get_action("Message/SendLater") + if action: + break + if action: + break; + if action: + action.activate() + +# Finally, the action group of the main window can be used to send the messages out +# Comment this for now, to not actually send stuff during testing +#clawsmail.get_mainwindow_action_group().get_action("Message/SendQueue").activate() diff --git a/src/plugins/python/examples/main/Open-Tomboy-Notes b/src/plugins/python/examples/main/Open-Tomboy-Notes @@ -0,0 +1,25 @@ +# -*- Mode: python -*- + +import re + +import dbus +import gtk + +# collect message ids of all selected messages +msgids = set() +for msg in clawsmail.get_summaryview_selected_message_list(): + msgid = msg.MessageID.replace("<", "&lt;").replace(">", "&gt;") + msgids.add(msgid) + +# setup D-Bus interface object +bus = dbus.SessionBus() +rc = bus.get_object('org.gnome.Tomboy', '/org/gnome/Tomboy/RemoteControl') +rc_iface = dbus.Interface(rc, dbus_interface='org.gnome.Tomboy.RemoteControl') + +# iterate over all Tomboy notes +for uri in rc_iface.ListAllNotes(): + contents = rc_iface.GetNoteContentsXml(uri) + # Check if message id is in a link to claws mail + for msgid in msgids: + if re.search(r'<link:cm-mail uri=".*?%s.*?">' % msgid, contents) != None: + rc_iface.DisplayNote(uri) diff --git a/src/plugins/python/examples/main/Print-action-names-to-stdout b/src/plugins/python/examples/main/Print-action-names-to-stdout @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +action_group = clawsmail.get_mainwindow_action_group() +for action in action_group.list_actions(): + print action.get_name() diff --git a/src/plugins/python/examples/main/Recusively-mark-messages-as-read b/src/plugins/python/examples/main/Recusively-mark-messages-as-read @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +# Define the function to deal with each folder +def deal_with_folder(folder): + # Get actions for selecting all messages, and marking the selection as read + action_group = clawsmail.get_mainwindow_action_group(); + select_all_action = action_group.get_action("Edit/SelectAll") + mark_read_action = action_group.get_action("Message/Mark/MarkRead") + + # Select given folder + clawsmail.folderview_select_folder(folder) + + # Search for messages with age greater than 28 days + clawsmail.quicksearch_search("ag 28", clawsmail.QUICK_SEARCH_EXTENDED) + + # Mark all messages in the search result as read + select_all_action.activate() + mark_read_action.activate() + + +# Get selected folder +root = clawsmail.get_folderview_selected_folder() + +# Get a tree of subfolders. The argument could also be a string of a mailbox name, +# or left out for a list of mailbox trees. +tree = clawsmail.get_folder_tree(root) + +# Call above function for all folders. +tree.traverse(deal_with_folder) + +# Clear the quicksearch widget again +clawsmail.quicksearch_clear() + +# Change back to original folder +clawsmail.folderview_select_folder(root)