[Commits] Makefile.am NONE 1.1.2.1 clawsmailmodule.c NONE 1.1.2.1 clawsmailmodule.h NONE 1.1.2.1 composewindowtype.c NONE 1.1.2.1 composewindowtype.h NONE 1.1.2.1 foldertype.c NONE 1.1.2.1 foldertype.h NONE 1.1.2.1 messageinfotype.c NONE 1.1.2.1 messageinfotype.h NONE 1.1.2.1 nodetype.c NONE 1.1.2.1 nodetype.h NONE 1.1.2.1 placeholder.txt 1.1.2.1 NONE python-hooks.c NONE 1.1.2.1 python-hooks.h NONE 1.1.2.1 python-shell.c NONE 1.1.2.1 python-shell.h NONE 1.1.2.1 python_plugin.c NONE 1.1.2.1

colin at claws-mail.org colin at claws-mail.org
Sun Feb 17 22:22:05 CET 2013


Update of /home/claws-mail/claws/src/plugins/python
In directory srv:/tmp/cvs-serv7104/src/plugins/python

Added Files:
      Tag: gtk2
	Makefile.am clawsmailmodule.c clawsmailmodule.h 
	composewindowtype.c composewindowtype.h foldertype.c 
	foldertype.h messageinfotype.c messageinfotype.h nodetype.c 
	nodetype.h python-hooks.c python-hooks.h python-shell.c 
	python-shell.h python_plugin.c 
Removed Files:
      Tag: gtk2
	placeholder.txt 
Log Message:
2013-02-17 [colin]	3.9.0cvs75

	* src/plugins/Makefile.am
	* src/plugins/archive/Makefile.am
	* src/plugins/clamd/Makefile.am
	* src/plugins/clamd/clamav_plugin.c
	* src/plugins/clamd/clamav_plugin.h
	* src/plugins/clamd/clamav_plugin_gtk.c
	* src/plugins/clamd/placeholder.txt
	* src/plugins/clamd/libclamd/Makefile.am
	* src/plugins/clamd/libclamd/clamd-plugin.c
	* src/plugins/clamd/libclamd/clamd-plugin.h
	* src/plugins/fetchinfo/Makefile.am
	* src/plugins/fetchinfo/fetchinfo_plugin.c
	* src/plugins/fetchinfo/fetchinfo_plugin.h
	* src/plugins/fetchinfo/fetchinfo_plugin_gtk.c
	* src/plugins/fetchinfo/placeholder.txt
	* src/plugins/gdata/Makefile.am
	* src/plugins/gdata/cm_gdata_contacts.c
	* src/plugins/gdata/cm_gdata_contacts.h
	* src/plugins/gdata/cm_gdata_prefs.c
	* src/plugins/gdata/cm_gdata_prefs.h
	* src/plugins/gdata/gdata_plugin.c
	* src/plugins/gdata/gdata_plugin.h
	* src/plugins/gdata/placeholder.txt
	* src/plugins/geolocation/placeholder.txt
	* src/plugins/gtkhtml2_viewer/placeholder.txt
	* src/plugins/mailmbox/Makefile.am
	* src/plugins/mailmbox/carray.c
	* src/plugins/mailmbox/carray.h
	* src/plugins/mailmbox/chash.c
	* src/plugins/mailmbox/chash.h
	* src/plugins/mailmbox/clist.c
	* src/plugins/mailmbox/clist.h
	* src/plugins/mailmbox/mailimf.c
	* src/plugins/mailmbox/mailimf.h
	* src/plugins/mailmbox/mailimf_types.c
	* src/plugins/mailmbox/mailimf_types.h
	* src/plugins/mailmbox/mailimf_types_helper.c
	* src/plugins/mailmbox/mailimf_types_helper.h
	* src/plugins/mailmbox/mailimf_write.c
	* src/plugins/mailmbox/mailimf_write.h
	* src/plugins/mailmbox/maillock.c
	* src/plugins/mailmbox/maillock.h
	* src/plugins/mailmbox/mailmbox.c
	* src/plugins/mailmbox/mailmbox.h
	* src/plugins/mailmbox/mailmbox_folder.c
	* src/plugins/mailmbox/mailmbox_folder.h
	* src/plugins/mailmbox/mailmbox_gtk.deps
	* src/plugins/mailmbox/mailmbox_parse.c
	* src/plugins/mailmbox/mailmbox_parse.h
	* src/plugins/mailmbox/mailmbox_types.c
	* src/plugins/mailmbox/mailmbox_types.h
	* src/plugins/mailmbox/mmapstring.c
	* src/plugins/mailmbox/mmapstring.h
	* src/plugins/mailmbox/placeholder.txt
	* src/plugins/mailmbox/plugin.c
	* src/plugins/mailmbox/plugin_gtk.c
	* src/plugins/mailmbox/plugin_gtk.h
	* src/plugins/newmail/Makefile.am
	* src/plugins/newmail/newmail.c
	* src/plugins/newmail/placeholder.txt
	* src/plugins/notification/Makefile.am
	* src/plugins/notification/claws.def
	* src/plugins/notification/notification_banner.c
	* src/plugins/notification/notification_banner.h
	* src/plugins/notification/notification_command.c
	* src/plugins/notification/notification_command.h
	* src/plugins/notification/notification_core.c
	* src/plugins/notification/notification_core.h
	* src/plugins/notification/notification_foldercheck.c
	* src/plugins/notification/notification_foldercheck.h
	* src/plugins/notification/notification_hotkeys.c
	* src/plugins/notification/notification_hotkeys.h
	* src/plugins/notification/notification_indicator.c
	* src/plugins/notification/notification_indicator.h
	* src/plugins/notification/notification_lcdproc.c
	* src/plugins/notification/notification_lcdproc.h
	* src/plugins/notification/notification_pixbuf.c
	* src/plugins/notification/notification_pixbuf.h
	* src/plugins/notification/notification_plugin.c
	* src/plugins/notification/notification_plugin.h
	* src/plugins/notification/notification_popup.c
	* src/plugins/notification/notification_popup.h
	* src/plugins/notification/notification_prefs.c
	* src/plugins/notification/notification_prefs.h
	* src/plugins/notification/notification_trayicon.c
	* src/plugins/notification/notification_trayicon.h
	* src/plugins/notification/placeholder.txt
	* src/plugins/notification/plugin.def
	* src/plugins/notification/raw_claws_mail_logo_64x64.h
	* src/plugins/notification/version.rc
	* src/plugins/pdf_viewer/Makefile.am
	* src/plugins/pdf_viewer/doc_index.xpm
	* src/plugins/pdf_viewer/doc_index_close.xpm
	* src/plugins/pdf_viewer/doc_info.xpm
	* src/plugins/pdf_viewer/first_arrow.xpm
	* src/plugins/pdf_viewer/last_arrow.xpm
	* src/plugins/pdf_viewer/left_arrow.xpm
	* src/plugins/pdf_viewer/placeholder.txt
	* src/plugins/pdf_viewer/poppler_viewer.c
	* src/plugins/pdf_viewer/poppler_viewer.h
	* src/plugins/pdf_viewer/right_arrow.xpm
	* src/plugins/pdf_viewer/rotate_left.xpm
	* src/plugins/pdf_viewer/rotate_right.xpm
	* src/plugins/pdf_viewer/zoom_fit.xpm
	* src/plugins/pdf_viewer/zoom_in.xpm
	* src/plugins/pdf_viewer/zoom_out.xpm
	* src/plugins/pdf_viewer/zoom_width.xpm
	* src/plugins/perl/Makefile.am
	* src/plugins/perl/perl_gtk.c
	* src/plugins/perl/perl_gtk.h
	* src/plugins/perl/perl_plugin.c
	* src/plugins/perl/perl_plugin.h
	* src/plugins/perl/placeholder.txt
	* src/plugins/python/Makefile.am
	* src/plugins/python/clawsmailmodule.c
	* src/plugins/python/clawsmailmodule.h
	* src/plugins/python/composewindowtype.c
	* src/plugins/python/composewindowtype.h
	* src/plugins/python/foldertype.c
	* src/plugins/python/foldertype.h
	* src/plugins/python/messageinfotype.c
	* src/plugins/python/messageinfotype.h
	* src/plugins/python/nodetype.c
	* src/plugins/python/nodetype.h
	* src/plugins/python/placeholder.txt
	* src/plugins/python/python-hooks.c
	* src/plugins/python/python-hooks.h
	* src/plugins/python/python-shell.c
	* src/plugins/python/python-shell.h
	* src/plugins/python/python_plugin.c
	* src/plugins/vcalendar/Makefile.in
		Add some plugins (clamd, fetchinfo, gdata, mailmbox, newmail,
		notification, pdf_viewer, perl, python). Notification not yet
		enabled because it has too much autoconf switches for my taste.

--- NEW FILE: python-shell.c ---
/*
 * Copyright (c) 2008-2009  Christian Hammond
 * Copyright (c) 2008-2009  David Trowbridge
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include <gdk/gdkkeysyms.h>
#include <string.h>

#include "python-hooks.h"
#include "python-shell.h"

#define MAX_HISTORY_LENGTH 20

#define PARASITE_PYTHON_SHELL_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_PYTHON_SHELL, \
                                 ParasitePythonShellPrivate))

typedef struct
{
    GtkWidget *textview;

    GtkTextMark *scroll_mark;
    GtkTextMark *line_start_mark;

    GQueue *history;
    GList *cur_history_item;

    GString *pending_command;
    gboolean in_block;

} ParasitePythonShellPrivate;

enum
{
    LAST_SIGNAL
};


/* Widget functions */
static void parasite_python_shell_finalize(GObject *obj);

/* Python integration */
static void parasite_python_shell_write_prompt(GtkWidget *python_shell);
static char *parasite_python_shell_get_input(GtkWidget *python_shell);

/* Callbacks */
static gboolean parasite_python_shell_key_press_cb(GtkWidget *textview,
                                                   GdkEventKey *event,
                                                   GtkWidget *python_shell);


static GtkVBoxClass *parent_class = NULL;
//static guint signals[LAST_SIGNAL] = {0};

G_DEFINE_TYPE(ParasitePythonShell, parasite_python_shell, GTK_TYPE_VBOX);


static void
parasite_python_shell_class_init(ParasitePythonShellClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    object_class->finalize = parasite_python_shell_finalize;

    g_type_class_add_private(klass, sizeof(ParasitePythonShellPrivate));
}

static void
parasite_python_shell_init(ParasitePythonShell *python_shell)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
    GtkWidget *swin;
    GtkTextBuffer *buffer;
    GtkTextIter iter;
    PangoFontDescription *font_desc;

    priv->history = g_queue_new();

    gtk_box_set_spacing(GTK_BOX(python_shell), 6);

    swin = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_show(swin);
    gtk_box_pack_start(GTK_BOX(python_shell), swin, TRUE, TRUE, 0);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin),
                                        GTK_SHADOW_IN);

    priv->textview = gtk_text_view_new();
    gtk_widget_show(priv->textview);
    gtk_container_add(GTK_CONTAINER(swin), priv->textview);
    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(priv->textview), TRUE);
    gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(priv->textview), 3);
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(priv->textview), 3);
    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(priv->textview), 3);

    g_signal_connect(G_OBJECT(priv->textview), "key_press_event",
                     G_CALLBACK(parasite_python_shell_key_press_cb),
                     python_shell);

    /* Make the textview monospaced */
    font_desc = pango_font_description_from_string("monospace");
    pango_font_description_set_size(font_desc, 10 * PANGO_SCALE);
    gtk_widget_modify_font(priv->textview, font_desc);
    pango_font_description_free(font_desc);

    /* Create the end-of-buffer mark */
    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
    gtk_text_buffer_get_end_iter(buffer, &iter);
    priv->scroll_mark = gtk_text_buffer_create_mark(buffer, "scroll_mark",
                                                    &iter, FALSE);

    /* Create the beginning-of-line mark */
    priv->line_start_mark = gtk_text_buffer_create_mark(buffer,
                                                        "line_start_mark",
                                                        &iter, TRUE);

    /* Register some tags */
    gtk_text_buffer_create_tag(buffer, "stdout", NULL);
    gtk_text_buffer_create_tag(buffer, "stderr",
                               "foreground", "red",
                               "paragraph-background", "#FFFFE0",
                               NULL);
    gtk_text_buffer_create_tag(buffer, "prompt",
                               "foreground", "blue",
                               NULL);

    parasite_python_shell_write_prompt(GTK_WIDGET(python_shell));
}

static void
parasite_python_shell_finalize(GObject *python_shell)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);

    g_queue_free(priv->history);
}

static void
parasite_python_shell_log_stdout(const char *text, gpointer python_shell)
{
    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
                                      text, "stdout");
}

static void
parasite_python_shell_log_stderr(const char *text, gpointer python_shell)
{
    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
                                      text, "stderr");
}

static void
parasite_python_shell_write_prompt(GtkWidget *python_shell)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
    GtkTextBuffer *buffer =
        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
    GtkTextIter iter;
    const char *prompt = (priv->pending_command == NULL ? ">>> " : "... ");

    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
                                      prompt, "prompt");

    gtk_text_buffer_get_end_iter(buffer, &iter);
    gtk_text_buffer_move_mark(buffer, priv->line_start_mark, &iter);
}

static void
parasite_python_shell_process_line(GtkWidget *python_shell)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);

    char *command = parasite_python_shell_get_input(python_shell);
    char last_char;

    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
                                      "\n", NULL);

    if (*command != '\0')
    {
        /* Save this command in the history. */
        g_queue_push_head(priv->history, command);
        priv->cur_history_item = NULL;

        if (g_queue_get_length(priv->history) > MAX_HISTORY_LENGTH)
            g_free(g_queue_pop_tail(priv->history));
    }

    last_char = command[MAX(0, strlen(command) - 1)];

    if (last_char == ':' || last_char == '\\' ||
        (priv->in_block && g_ascii_isspace(command[0])))
    {
        printf("in block.. %c, %d, %d\n",
               last_char, priv->in_block,
               g_ascii_isspace(command[0]));
        /* This is a multi-line expression */
        if (priv->pending_command == NULL)
            priv->pending_command = g_string_new(command);
        else
            g_string_append(priv->pending_command, command);

        g_string_append_c(priv->pending_command, '\n');

        if (last_char == ':')
            priv->in_block = TRUE;
    }
    else
    {
        if (priv->pending_command != NULL)
        {
            g_string_append(priv->pending_command, command);
            g_string_append_c(priv->pending_command, '\n');

            /* We're not actually leaking this. It's in the history. */
            command = g_string_free(priv->pending_command, FALSE);
        }
        parasite_python_run(command,
                            parasite_python_shell_log_stdout,
                            parasite_python_shell_log_stderr,
                            python_shell);
        if (priv->pending_command != NULL)
        {
            /* Now do the cleanup. */
            g_free(command);
            priv->pending_command = NULL;
            priv->in_block = FALSE;
        }
    }
    parasite_python_shell_write_prompt(python_shell);
}

static void
parasite_python_shell_replace_input(GtkWidget *python_shell,
                                    const char *text)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);

    GtkTextBuffer *buffer =
        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
    GtkTextIter start_iter;
    GtkTextIter end_iter;

    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
                                     priv->line_start_mark);
    gtk_text_buffer_get_end_iter(buffer, &end_iter);

    gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
    gtk_text_buffer_insert(buffer, &end_iter, text, -1);
}

static char *
parasite_python_shell_get_input(GtkWidget *python_shell)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
    GtkTextBuffer *buffer =
        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
    GtkTextIter start_iter;
    GtkTextIter end_iter;

    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
                                     priv->line_start_mark);
    gtk_text_buffer_get_end_iter(buffer, &end_iter);

    return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
}

static const char *
parasite_python_shell_get_history_back(GtkWidget *python_shell)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);

    if (priv->cur_history_item == NULL)
    {
        priv->cur_history_item = g_queue_peek_head_link(priv->history);

        if (priv->cur_history_item == NULL)
            return "";
    }
    else if (priv->cur_history_item->next != NULL)
        priv->cur_history_item = priv->cur_history_item->next;

    return (const char *)priv->cur_history_item->data;
}

static const char *
parasite_python_shell_get_history_forward(GtkWidget *python_shell)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);

    if (priv->cur_history_item == NULL || priv->cur_history_item->prev == NULL)
    {
        priv->cur_history_item = NULL;
        return "";
    }

    priv->cur_history_item = priv->cur_history_item->prev;

    return (const char *)priv->cur_history_item->data;
}

static gboolean
parasite_python_shell_key_press_cb(GtkWidget *textview,
                                   GdkEventKey *event,
                                   GtkWidget *python_shell)
{
    if (event->keyval == GDK_Return)
    {
        parasite_python_shell_process_line(python_shell);
        return TRUE;
    }
    else if (event->keyval == GDK_Up)
    {
        parasite_python_shell_replace_input(python_shell,
            parasite_python_shell_get_history_back(python_shell));
        return TRUE;
    }
    else if (event->keyval == GDK_Down)
    {
        parasite_python_shell_replace_input(python_shell,
            parasite_python_shell_get_history_forward(python_shell));
        return TRUE;
    }
    else if (event->string != NULL)
    {
        ParasitePythonShellPrivate *priv =
            PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
        GtkTextBuffer *buffer =
            gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
        GtkTextMark *insert_mark = gtk_text_buffer_get_insert(buffer);
        GtkTextMark *selection_mark =
            gtk_text_buffer_get_selection_bound(buffer);
        GtkTextIter insert_iter;
        GtkTextIter selection_iter;
        GtkTextIter start_iter;
        gint cmp_start_insert;
        gint cmp_start_select;
        gint cmp_insert_select;

        gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
                                         priv->line_start_mark);
        gtk_text_buffer_get_iter_at_mark(buffer, &insert_iter, insert_mark);
        gtk_text_buffer_get_iter_at_mark(buffer, &selection_iter,
                                         selection_mark);

        cmp_start_insert = gtk_text_iter_compare(&start_iter, &insert_iter);
        cmp_start_select = gtk_text_iter_compare(&start_iter, &selection_iter);
        cmp_insert_select = gtk_text_iter_compare(&insert_iter,
                                                  &selection_iter);

        if (cmp_start_insert == 0 && cmp_start_select == 0 &&
            (event->keyval == GDK_BackSpace ||
             event->keyval == GDK_Left))
        {
            return TRUE;
        }
        if (cmp_start_insert <= 0 && cmp_start_select <= 0)
        {
            return FALSE;
        }
        else if (cmp_start_insert > 0 && cmp_start_select > 0)
        {
            gtk_text_buffer_place_cursor(buffer, &start_iter);
        }
        else if (cmp_insert_select < 0)
        {
            gtk_text_buffer_move_mark(buffer, insert_mark, &start_iter);
        }
        else if (cmp_insert_select > 0)
        {
            gtk_text_buffer_move_mark(buffer, selection_mark, &start_iter);
        }
    }

    return FALSE;
}

GtkWidget *
parasite_python_shell_new(void)
{
    return g_object_new(PARASITE_TYPE_PYTHON_SHELL, NULL);
}

void
parasite_python_shell_append_text(ParasitePythonShell *python_shell,
                                  const char *str,
                                  const char *tag)
{
    ParasitePythonShellPrivate *priv =
        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);

    GtkTextIter end;
    GtkTextBuffer *buffer =
        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
    GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);

    gtk_text_buffer_get_end_iter(buffer, &end);
    gtk_text_buffer_move_mark(buffer, mark, &end);
    gtk_text_buffer_insert_with_tags_by_name(buffer, &end, str, -1, tag, NULL);
    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(priv->textview), mark,
                                 0, TRUE, 0, 1);
}

void
parasite_python_shell_focus(ParasitePythonShell *python_shell)
{
   gtk_widget_grab_focus(PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell)->textview);
}

// vim: set et ts=4:

--- NEW FILE: clawsmailmodule.c ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009-2012 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "clawsmailmodule.h"

#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>

#include "nodetype.h"
#include "composewindowtype.h"
#include "foldertype.h"
#include "messageinfotype.h"

#include <pygobject.h>
#include <pygtk/pygtk.h>

#include "main.h"
#include "mainwindow.h"
#include "summaryview.h"
#include "quicksearch.h"
#include "toolbar.h"
#include "prefs_common.h"
#include "common/tags.h"

#include <glib.h>

static PyObject *cm_module = NULL;

PyObject* get_gobj_from_address(gpointer addr)
{
  GObject *obj;

  if (!G_IS_OBJECT(addr))
      return NULL;

  obj = G_OBJECT(addr);

  if (!obj)
      return NULL;

  return pygobject_new(obj);
}

static PyObject* private_wrap_gobj(PyObject *self, PyObject *args)
{
    void *addr;

    if (!PyArg_ParseTuple(args, "l", &addr))
        return NULL;

    return get_gobj_from_address(addr);
}

static PyObject *get_mainwindow_action_group(PyObject *self, PyObject *args)
{
  MainWindow *mainwin;

  mainwin =  mainwindow_get_mainwindow();
  if(mainwin)
    return get_gobj_from_address(mainwin->action_group);
  else
    return NULL;
}

static PyObject *get_mainwindow_ui_manager(PyObject *self, PyObject *args)
{
  MainWindow *mainwin;

  mainwin =  mainwindow_get_mainwindow();
  if(mainwin)
    return get_gobj_from_address(mainwin->ui_manager);
  else
    return NULL;
}

static PyObject *get_folderview_selected_folder(PyObject *self, PyObject *args)
{
  MainWindow *mainwin;

  mainwin =  mainwindow_get_mainwindow();
  if(mainwin && mainwin->folderview) {
    FolderItem *item;
    item = folderview_get_selected_item(mainwin->folderview);
    if(item)
      return clawsmail_folder_new(item);
  }
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *folderview_select_folder(PyObject *self, PyObject *args)
{
  MainWindow *mainwin;

  mainwin =  mainwindow_get_mainwindow();
  if(mainwin && mainwin->folderview) {
    FolderItem *item;
    PyObject *folder;
    folder = PyTuple_GetItem(args, 0);
    if(!folder)
      return NULL;
    Py_INCREF(folder);
    item = clawsmail_folder_get_item(folder);
    Py_DECREF(folder);
    if(item)
      folderview_select(mainwin->folderview, item);
  }
  Py_INCREF(Py_None);
  return Py_None;
}

static gboolean setup_folderitem_node(GNode *item_node, GNode *item_parent, PyObject **pyparent)
{
  PyObject *pynode, *children;
  int retval, n_children, i_child;
  PyObject *folder;

  /* create a python node for the folderitem node */
  pynode = clawsmail_node_new(cm_module);
  if(!pynode)
    return FALSE;

  /* store Folder in pynode */
  folder = clawsmail_folder_new(item_node->data);
  retval = PyObject_SetAttrString(pynode, "data", folder);
  Py_DECREF(folder);
  if(retval == -1) {
    Py_DECREF(pynode);
    return FALSE;
  }

  if(pyparent && *pyparent) {
    /* add this node to the parent's childs */
    children = PyObject_GetAttrString(*pyparent, "children");
    retval = PyList_Append(children, pynode);
    Py_DECREF(children);

    if(retval == -1) {
      Py_DECREF(pynode);
      return FALSE;
    }
  }
  else if(pyparent) {
    *pyparent = pynode;
    Py_INCREF(pynode);
  }

  /* call this function recursively for all children of the new node */
  n_children = g_node_n_children(item_node);
  for(i_child = 0; i_child < n_children; i_child++) {
    if(!setup_folderitem_node(g_node_nth_child(item_node, i_child), item_node, &pynode)) {
      Py_DECREF(pynode);
      return FALSE;
    }
  }

  Py_DECREF(pynode);
  return TRUE;
}

static PyObject* get_folder_tree_from_account_name(const char *str)
{
  PyObject *result;
  GList *walk;

  result = Py_BuildValue("[]");
  if(!result)
    return NULL;

  for(walk = folder_get_list(); walk; walk = walk->next) {
    Folder *folder = walk->data;
    if((!str || !g_strcmp0(str, folder->name)) && folder->node) {
      PyObject *root;
      int n_children, i_child, retval;

      /* create root nodes */
      root = clawsmail_node_new(cm_module);
      if(!root) {
        Py_DECREF(result);
        return NULL;
      }

      n_children = g_node_n_children(folder->node);
      for(i_child = 0; i_child < n_children; i_child++) {
        if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
          Py_DECREF(root);
          Py_DECREF(result);
          return NULL;
        }
      }
      retval = PyList_Append(result, root);
      Py_DECREF(root);
      if(retval == -1) {
        Py_DECREF(result);
        return NULL;
      }
    }
  }
  return result;
}

static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
{
  PyObject *result;
  GList *walk;

  for(walk = folder_get_list(); walk; walk = walk->next) {
    Folder *folder = walk->data;
    if(folder->node) {
      GNode *root_node;

      root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
      if(!root_node)
        continue;

      result = NULL;
      if(!setup_folderitem_node(root_node, NULL, &result))
        return NULL;
      else
        return result;
    }
  }

  PyErr_SetString(PyExc_LookupError, "Folder not found");
  return NULL;
}

static PyObject* get_folder_tree(PyObject *self, PyObject *args)
{
  PyObject *arg;
  PyObject *result;
  int retval;

  Py_INCREF(Py_None);
  arg = Py_None;
  retval = PyArg_ParseTuple(args, "|O", &arg);
  Py_DECREF(Py_None);
  if(!retval)
    return NULL;

  /* calling possibilities:
   * nothing, the mailbox name in a string, a Folder object */

  /* no arguments: build up a list of folder trees */
  if(PyTuple_Size(args) == 0) {
    result = get_folder_tree_from_account_name(NULL);
  }
  else if(PyString_Check(arg)){
    const char *str;
    str = PyString_AsString(arg);
    if(!str)
      return NULL;

    result = get_folder_tree_from_account_name(str);
  }
  else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
    result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
  }
  else {
    PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a mailbox string or a Folder object.");
    return NULL;
  }

  return result;
}

static PyObject* quicksearch_search(PyObject *self, PyObject *args)
{
  const char *string;
  int searchtype;
  QuickSearch *qs;
  MainWindow *mainwin;

  /* must be given exactly one argument, which is a string */
  searchtype = prefs_common.summary_quicksearch_type;
  if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
    return NULL;

  mainwin = mainwindow_get_mainwindow();
  if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
    PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
    return NULL;
  }

  qs = mainwin->summaryview->quicksearch;
  quicksearch_set(qs, searchtype, string);

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
{
  QuickSearch *qs;
  MainWindow *mainwin;

  mainwin = mainwindow_get_mainwindow();
  if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
    PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
    return NULL;
  }

  qs = mainwin->summaryview->quicksearch;
  quicksearch_set(qs, prefs_common.summary_quicksearch_type, "");

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
{
  PyObject *olist;
  MainWindow *mainwin;
  Py_ssize_t size, iEl;
  GSList *msginfos;

  mainwin = mainwindow_get_mainwindow();
  if(!mainwin || !mainwin->summaryview) {
    PyErr_SetString(PyExc_LookupError, "SummaryView not found");
    return NULL;
  }

  if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
    PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
    return NULL;
  }

  msginfos = NULL;
  size = PyList_Size(olist);
  for(iEl = 0; iEl < size; iEl++) {
    PyObject *element = PyList_GET_ITEM(olist, iEl);

    if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
      PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
      return NULL;
    }

    msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
  }

  summary_unselect_all(mainwin->summaryview);
  summary_select_by_msg_list(mainwin->summaryview, msginfos);
  g_slist_free(msginfos);

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
{
  MainWindow *mainwin;
  GSList *list, *walk;
  PyObject *result;

  mainwin = mainwindow_get_mainwindow();
  if(!mainwin || !mainwin->summaryview) {
    PyErr_SetString(PyExc_LookupError, "SummaryView not found");
    return NULL;
  }

  result = Py_BuildValue("[]");
  if(!result)
    return NULL;

  list = summary_get_selected_msg_list(mainwin->summaryview);
  for(walk = list; walk; walk = walk->next) {
    PyObject *msg;
    msg = clawsmail_messageinfo_new(walk->data);
    if(PyList_Append(result, msg) == -1) {
      Py_DECREF(result);
      return NULL;
    }
  }
  g_slist_free(list);

  return result;
}

static PyObject* is_exiting(PyObject *self, PyObject *args)
{
  if(claws_is_exiting())
    Py_RETURN_TRUE;
  else
    Py_RETURN_FALSE;
}

static PyObject* get_tags(PyObject *self, PyObject *args)
{
  Py_ssize_t num_tags;
  PyObject *tags_tuple;
  GSList *tags_list;

  tags_list = tags_get_list();
  num_tags = g_slist_length(tags_list);

  tags_tuple = PyTuple_New(num_tags);
  if(tags_tuple != NULL) {
    Py_ssize_t iTag;
    PyObject *tag_object;
    GSList *walk;

    iTag = 0;
    for(walk = tags_list; walk; walk = walk->next) {
      tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
      if(tag_object == NULL) {
        Py_DECREF(tags_tuple);
        return NULL;
      }
      PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
    }
  }

  g_slist_free(tags_list);

  return tags_tuple;
}

static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
{
  int retval;
  const char *tag_str;

  retval = PyArg_ParseTuple(args, "s", &tag_str);
  if(!retval)
    return NULL;

  if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
    /* tag name is reserved, raise KeyError */
    PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
    return NULL;
  }

  tags_add_tag(tag_str);

  Py_RETURN_NONE;
}

static PyObject* delete_tag(PyObject *self, PyObject *args)
{
  int retval;
  const char *tag_str;
  gint tag_id;
  MainWindow *mainwin;

  retval = PyArg_ParseTuple(args, "s", &tag_str);
  if(!retval)
    return NULL;

  tag_id = tags_get_id_for_str(tag_str);
  if(tag_id == -1) {
    PyErr_SetString(PyExc_KeyError, "Tag does not exist");
    return NULL;
  }

  tags_remove_tag(tag_id);

  /* update display */
  mainwin = mainwindow_get_mainwindow();
  if(mainwin)
    summary_redisplay_msg(mainwin->summaryview);

  Py_RETURN_NONE;
}


static PyObject* rename_tag(PyObject *self, PyObject *args)
{
  int retval;
  const char *old_tag_str;
  const char *new_tag_str;
  gint tag_id;
  MainWindow *mainwin;

  retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
  if(!retval)
    return NULL;

  if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
    PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
    return NULL;
  }

  tag_id = tags_get_id_for_str(old_tag_str);
  if(tag_id == -1) {
    PyErr_SetString(PyExc_KeyError, "Tag does not exist");
    return NULL;
  }

  tags_update_tag(tag_id, new_tag_str);

  /* update display */
  mainwin = mainwindow_get_mainwindow();
  if(mainwin)
    summary_redisplay_msg(mainwin->summaryview);

  Py_RETURN_NONE;
}

static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
{
  Py_ssize_t size, iEl;
  FolderItem *folderitem;
  
  *list = NULL;

  folderitem = clawsmail_folder_get_item(folder);
  if(!folderitem) {
    PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
    return FALSE;
  }

  size = PyList_Size(messagelist);
  for(iEl = 0; iEl < size; iEl++) {
    PyObject *element = PyList_GET_ITEM(messagelist, iEl);
    MsgInfo *msginfo;

    if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
      PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
      return FALSE;
    }
    
    msginfo = clawsmail_messageinfo_get_msginfo(element);
    if(!msginfo) {
      PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
      return FALSE;
    }
   
    procmsg_msginfo_set_to_folder(msginfo, folderitem);
    *list = g_slist_prepend(*list, msginfo);
  }
 
  return TRUE;
}

static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
{
  PyObject *messagelist;
  PyObject *folder;
  int retval;
  GSList *list = NULL;
  
  retval = PyArg_ParseTuple(args, "O!O!",
    &PyList_Type, &messagelist,
    clawsmail_folder_get_type_object(), &folder);
  if(!retval )
    return NULL;  

  folder_item_update_freeze();
  
  if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
    goto err;
  
  if(move)   
    procmsg_move_messages(list);
  else
    procmsg_copy_messages(list);
      
  folder_item_update_thaw();
  g_slist_free(list);
  Py_RETURN_NONE;

err:
  folder_item_update_thaw();
  g_slist_free(list);
  return NULL;
}

static PyObject* move_messages(PyObject *self, PyObject *args)
{
  return move_or_copy_messages(self, args, TRUE);
}


static PyObject* copy_messages(PyObject *self, PyObject *args)
{
  return move_or_copy_messages(self, args, FALSE);
}

static PyMethodDef ClawsMailMethods[] = {
    /* public */
    {"get_mainwindow_action_group",  get_mainwindow_action_group, METH_NOARGS,
     "get_mainwindow_action_group() - get action group of main window menu\n"
     "\n"
     "Returns the gtk.ActionGroup for the main window."},

    {"get_mainwindow_ui_manager",  get_mainwindow_ui_manager, METH_NOARGS, 
     "get_mainwindow_ui_manager() - get ui manager of main window\n"
     "\n"
     "Returns the gtk.UIManager for the main window."},

    {"get_folder_tree",  get_folder_tree, METH_VARARGS,
     "get_folder_tree([root]) - get a folder tree\n"
     "\n"
     "Without arguments, get a list of folder trees for all mailboxes.\n"
     "\n"
     "If the optional root argument is a string, it is supposed to be a\n"
     "mailbox name. The function then returns a tree of folders of that mailbox.\n"
     "\n"
     "If the optional root argument is a clawsmail.Folder, the function\n"
     "returns a tree of subfolders with the given folder as root element.\n"
     "\n"
     "In any case, a tree consists of elements of the type clawsmail.Node."},

    {"get_folderview_selected_folder",  get_folderview_selected_folder, METH_NOARGS,
     "get_folderview_selected_folder() - get selected folder in folderview\n"
     "\n"
     "Returns the currently selected folder as a clawsmail.Folder."},
    {"folderview_select_folder",  folderview_select_folder, METH_VARARGS,
     "folderview_select_folder(folder) - select folder in folderview\n"
     "\n"
     "Takes an argument of type clawsmail.Folder, and selects the corresponding folder."},

    {"quicksearch_search", quicksearch_search, METH_VARARGS,
     "quicksearch_search(string [, type]) - perform a quicksearch\n"
     "\n"
     "Perform a quicksearch of the given string. The optional type argument can be\n"
     "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
     "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
     "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
     "string corresponding to the type."},

    {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
     "quicksearch_clear() - clear the quicksearch"},

    {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
     "get_summaryview_selected_message_list() - get selected message list\n"
     "\n"
     "Get a list of clawsmail.MessageInfo objects of the current selection."},

    {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
     "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
     "\n"
     "Select a list of clawsmail.MessageInfo objects in the summary view."},

    {"is_exiting", is_exiting, METH_NOARGS,
     "is_exiting() - test whether Claws Mail is currently exiting\n"
     "\n"
     "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
     "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
     "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
     "return False."},

    {"move_messages", move_messages, METH_VARARGS,
     "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
     "\n"
     "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
     "The target_folder argument has to be a clawsmail.Folder object."},

    {"copy_messages", copy_messages, METH_VARARGS,
     "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
     "\n"
     "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
     "The target_folder argument has to be a clawsmail.Folder object."},

    {"get_tags", get_tags, METH_NOARGS,
     "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
     "\n"
     "Get a tuple of strings representing all tags that are defined in Claws Mail."},

    {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
     "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
     "\n"
     "This function creates the given tag if it does not exist yet.\n"
     "It is not an error if the tag already exists. In this case, this function does nothing.\n"
     "However, if a reserved tag name is chosen, a ValueError exception is raised."},

    {"delete_tag", delete_tag, METH_VARARGS,
     "delete_tag(tag) - delete a tag\n"
     "\n"
     "This function deletes an existing tag.\n"
     "Raises a KeyError exception if the tag does not exist."},

    {"rename_tag", rename_tag, METH_VARARGS,
     "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
     "\n"
     "This function renames an existing tag.\n"
     "Raises a KeyError exception if the tag does not exist.\n"
     "Raises a ValueError exception if the old or new tag name is a reserved name."},

     /* private */
    {"__gobj", private_wrap_gobj, METH_VARARGS,
     "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
     "\n"
     "For internal usage only."},

    {NULL, NULL, 0, NULL}
};

static void initmiscstuff(PyObject *module)
{
  PyObject *dict;
  PyObject *res;
  const char *cmd =
      "QUICK_SEARCH_SUBJECT = 0\n"
      "QUICK_SEARCH_FROM = 1\n"
      "QUICK_SEARCH_TO = 2\n"
      "QUICK_SEARCH_EXTENDED = 3\n"
      "QUICK_SEARCH_MIXED = 4\n"
      "QUICK_SEARCH_TAG = 5\n"
      "\n";
  dict = PyModule_GetDict(module);
  res = PyRun_String(cmd, Py_file_input, dict, dict);
  Py_XDECREF(res);
}


void claws_mail_python_init(void)
{
  if (!Py_IsInitialized())
      Py_Initialize();

  /* create module */
  cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
      "This module can be used to access some of Claws Mail's data structures\n"
      "in order to extend or modify the user interface or automate repetitive tasks.\n"
      "\n"
      "Whenever possible, the interface works with standard GTK+ widgets\n"
      "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
      "to find out about all possible options.\n"
      "\n"
      "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
      "If you're missing something specific, try contacting the author.");

  /* initialize classes */
  initnode(cm_module);
  initcomposewindow(cm_module);
  initfolder(cm_module);
  initmessageinfo(cm_module);

  /* initialize misc things */
  initmiscstuff(cm_module);

  PyRun_SimpleString("import clawsmail\n");
  PyRun_SimpleString("clawsmail.compose_window = None\n");
}


void put_composewindow_into_module(Compose *compose)
{
  PyObject *pycompose;

  pycompose = clawsmail_compose_new(cm_module, compose);
  PyObject_SetAttrString(cm_module, "compose_window", pycompose);
  Py_DECREF(pycompose);
}

--- NEW FILE: python_plugin.c ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>

#include <Python.h>

#include "common/hooks.h"
#include "common/plugin.h"
#include "common/version.h"
#include "common/utils.h"
#include "gtk/menu.h"
#include "main.h"
#include "mainwindow.h"
#include "prefs_toolbar.h"

#include "python-shell.h"
#include "python-hooks.h"
#include "clawsmailmodule.h"

#define PYTHON_SCRIPTS_BASE_DIR "python-scripts"
#define PYTHON_SCRIPTS_MAIN_DIR "main"
#define PYTHON_SCRIPTS_COMPOSE_DIR "compose"
#define PYTHON_SCRIPTS_AUTO_DIR "auto"
#define PYTHON_SCRIPTS_AUTO_STARTUP "startup"
#define PYTHON_SCRIPTS_AUTO_SHUTDOWN "shutdown"
#define PYTHON_SCRIPTS_AUTO_COMPOSE "compose_any"
#define PYTHON_SCRIPTS_ACTION_PREFIX "Tools/PythonScripts/"

static GSList *menu_id_list = NULL;
static GSList *python_mainwin_scripts_id_list = NULL;
static GSList *python_mainwin_scripts_names = NULL;
static GSList *python_compose_scripts_names = NULL;

static GtkWidget *python_console = NULL;

static guint hook_compose_create;

static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  MainWindow *mainwin;
  GtkToggleAction *action;

  mainwin =  mainwindow_get_mainwindow();
  action = GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwin->action_group, "Tools/ShowPythonConsole"));
  gtk_toggle_action_set_active(action, FALSE);
  return TRUE;
}

static void setup_python_console(void)
{
  GtkWidget *vbox;
  GtkWidget *console;

  python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_size_request(python_console, 600, 400);

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(python_console), vbox);

  console = parasite_python_shell_new();
  gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);

  g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);

  gtk_widget_show_all(python_console);

  parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
}

static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
{
  if(gtk_toggle_action_get_active(action)) {
    if(!python_console)
      setup_python_console();
    gtk_widget_show(python_console);
  }
  else {
    gtk_widget_hide(python_console);
  }
}

static void remove_python_scripts_menus(void)
{
  GSList *walk;
  MainWindow *mainwin;

  mainwin =  mainwindow_get_mainwindow();

  /* toolbar */
  for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
    prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);

  /* ui */
  for(walk = python_mainwin_scripts_id_list; walk; walk = walk->next)
      gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
  g_slist_free(python_mainwin_scripts_id_list);
  python_mainwin_scripts_id_list = NULL;

  /* actions */
  for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
    GtkAction *action;
    gchar *entry;
    entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
    action = gtk_action_group_get_action(mainwin->action_group, entry);
    g_free(entry);
    if(action)
      gtk_action_group_remove_action(mainwin->action_group, action);
    g_free(walk->data);
  }
  g_slist_free(python_mainwin_scripts_names);
  python_mainwin_scripts_names = NULL;

  /* compose scripts */
  for(walk = python_compose_scripts_names; walk; walk = walk->next) {
    prefs_toolbar_unregister_plugin_item(TOOLBAR_COMPOSE, "Python", walk->data);
    g_free(walk->data);
  }
  g_slist_free(python_compose_scripts_names);
  python_compose_scripts_names = NULL;
}

static gchar* extract_filename(const gchar *str)
{
  gchar *filename;

  filename = g_strrstr(str, "/");
  if(!filename || *(filename+1) == '\0') {
    debug_print("Error: Could not extract filename from %s\n", str);
    return NULL;
  }
  filename++;
  return filename;
}

static void run_script_file(const gchar *filename, Compose *compose)
{
  FILE *fp;
  fp = fopen(filename, "r");
  if(!fp) {
    g_print("Error: Could not open file '%s'\n", filename);
    return;
  }
  put_composewindow_into_module(compose);
  PyRun_SimpleFile(fp, filename);
  fclose(fp);
}

static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
{
  gchar *auto_filepath;

  /* execute auto/autofilename, if it exists */
  auto_filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_AUTO_DIR, G_DIR_SEPARATOR_S, autofilename, NULL);
  if(file_exist(auto_filepath, FALSE))
    run_script_file(auto_filepath, compose);
  g_free(auto_filepath);
}

static void python_mainwin_script_callback(GtkAction *action, gpointer data)
{
  char *filename;

  filename = extract_filename(data);
  if(!filename)
    return;
  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_MAIN_DIR, G_DIR_SEPARATOR_S, filename, NULL);
  run_script_file(filename, NULL);
  g_free(filename);
}

typedef struct _ComposeActionData ComposeActionData;
struct _ComposeActionData {
  gchar *name;
  Compose *compose;
};

static void python_compose_script_callback(GtkAction *action, gpointer data)
{
  char *filename;
  ComposeActionData *dat = (ComposeActionData*)data;

  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S, dat->name, NULL);
  run_script_file(filename, dat->compose);

  g_free(filename);
}

static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
{
	gchar *script;
	script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
	python_mainwin_script_callback(NULL, script);
	g_free(script);
}

static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
{
  gchar *filename;

  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S,
      item_name, NULL);
  run_script_file(filename, (Compose*)parent);
  g_free(filename);
}

static void make_sure_script_directory_exists(const gchar *subdir)
{
  char *dir;
  dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, subdir, NULL);
  if(!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
    if(g_mkdir(dir, 0777) != 0)
      debug_print("Python plugin: Could not create directory '%s'\n", dir);
  }
  g_free(dir);
}

static void make_sure_directories_exist(void)
{
  make_sure_script_directory_exists("");
  make_sure_script_directory_exists(PYTHON_SCRIPTS_MAIN_DIR);
  make_sure_script_directory_exists(PYTHON_SCRIPTS_COMPOSE_DIR);
  make_sure_script_directory_exists(PYTHON_SCRIPTS_AUTO_DIR);
}

static void migrate_scripts_out_of_base_dir(void)
{
  char *base_dir;
  GDir *dir;
  const char *filename;
  gchar *dest_dir;

  base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, NULL);
  dir = g_dir_open(base_dir, 0, NULL);
  g_free(base_dir);
  if(!dir)
    return;

  dest_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_MAIN_DIR, NULL);
  if(!g_file_test(dest_dir, G_FILE_TEST_IS_DIR)) {
    if(g_mkdir(dest_dir, 0777) != 0) {
      g_free(dest_dir);
      g_dir_close(dir);
      return;
    }
  }

  while((filename = g_dir_read_name(dir)) != NULL) {
    gchar *filepath;
    filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, filename, NULL);
    if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) {
      gchar *dest_file;
      dest_file = g_strconcat(dest_dir, G_DIR_SEPARATOR_S, filename, NULL);
      if(move_file(filepath, dest_file, FALSE) == 0)
        g_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
      else
        g_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
      g_free(dest_file);
    }
    g_free(filepath);
  }
  g_dir_close(dir);
  g_free(dest_dir);
}


static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
{
  MainWindow *mainwin;
  gint ii;
  GSList *walk;
  GtkActionEntry *entries;

  /* create menu items */
  entries = g_new0(GtkActionEntry, num_entries);
  ii = 0;
  mainwin =  mainwindow_get_mainwindow();
  for(walk = filenames; walk; walk = walk->next) {
    entries[ii].name = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
    entries[ii].label = walk->data;
    entries[ii].callback = G_CALLBACK(python_mainwin_script_callback);
    gtk_action_group_add_actions(mainwin->action_group, &(entries[ii]), 1, (gpointer)entries[ii].name);
    ii++;
  }
  for(ii = 0; ii < num_entries; ii++) {
    guint id;

    python_mainwin_scripts_names = g_slist_prepend(python_mainwin_scripts_names, g_strdup(entries[ii].label));
    MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
        entries[ii].name, GTK_UI_MANAGER_MENUITEM, id)
    python_mainwin_scripts_id_list = g_slist_prepend(python_mainwin_scripts_id_list, GUINT_TO_POINTER(id));

    prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
  }

  g_free(entries);
}


/* this function doesn't really create menu items, but prepares a list that can be used
 * in the compose create hook. It does however register the scripts for the toolbar editor */
static void create_compose_menus_and_items(GSList *filenames)
{
  GSList *walk;
  for(walk = filenames; walk; walk = walk->next) {
    python_compose_scripts_names = g_slist_prepend(python_compose_scripts_names, g_strdup((gchar*)walk->data));
    prefs_toolbar_register_plugin_item(TOOLBAR_COMPOSE, "Python", (gchar*)walk->data, compose_toolbar_callback, NULL);
  }
}

static GtkActionEntry compose_tools_python_actions[] = {
    {"Tools/PythonScripts", NULL, N_("Python scripts") },
};

static void ComposeActionData_destroy_cb(gpointer data)
{
  ComposeActionData *dat = (ComposeActionData*)data;
  g_free(dat->name);
  g_free(dat);
}

static gboolean my_compose_create_hook(gpointer cw, gpointer data)
{
  gint ii;
  GSList *walk;
  GtkActionEntry *entries;
  GtkActionGroup *action_group;
  Compose *compose = (Compose*)cw;
  guint num_entries = g_slist_length(python_compose_scripts_names);

  action_group = gtk_action_group_new("PythonPlugin");
  gtk_action_group_add_actions(action_group, compose_tools_python_actions, 1, NULL);
  entries = g_new0(GtkActionEntry, num_entries);
  ii = 0;
  for(walk = python_compose_scripts_names; walk; walk = walk->next) {
    ComposeActionData *dat;

    entries[ii].name = walk->data;
    entries[ii].label = walk->data;
    entries[ii].callback = G_CALLBACK(python_compose_script_callback);

    dat = g_new0(ComposeActionData, 1);
    dat->name = g_strdup(walk->data);
    dat->compose = compose;

    gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
    ii++;
  }
  gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);

  MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
      "Tools/PythonScripts", GTK_UI_MANAGER_MENU)

  for(ii = 0; ii < num_entries; ii++) {
    MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
        entries[ii].name, GTK_UI_MANAGER_MENUITEM)
  }

  g_free(entries);

  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);

  return FALSE;
}


static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
{
  char *scripts_dir;
  GDir *dir;
  GError *error = NULL;
  const char *filename;
  GSList *filenames = NULL;
  GSList *walk;
  gint num_entries;

  scripts_dir = g_strconcat(get_rc_dir(),
      G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
      G_DIR_SEPARATOR_S, subdir,
      NULL);
  debug_print("Refreshing: %s\n", scripts_dir);

  dir = g_dir_open(scripts_dir, 0, &error);
  g_free(scripts_dir);

  if(!dir) {
    g_print("Could not open directory '%s': %s\n", subdir, error->message);
    g_error_free(error);
    return;
  }

  /* get filenames */
  num_entries = 0;
  while((filename = g_dir_read_name(dir)) != NULL) {
    char *fn;

    fn = g_strdup(filename);
    filenames = g_slist_prepend(filenames, fn);
    num_entries++;
  }
  g_dir_close(dir);

  if(toolbar_type == TOOLBAR_MAIN)
    create_mainwindow_menus_and_items(filenames, num_entries);
  else if(toolbar_type == TOOLBAR_COMPOSE)
    create_compose_menus_and_items(filenames);

  /* cleanup */
  for(walk = filenames; walk; walk = walk->next)
    g_free(walk->data);
  g_slist_free(filenames);
}

static void browse_python_scripts_dir(GtkAction *action, gpointer data)
{
  gchar *uri;
  GdkAppLaunchContext *launch_context;
  GError *error = NULL;
  MainWindow *mainwin;

  mainwin =  mainwindow_get_mainwindow();
  if(!mainwin) {
      debug_print("Browse Python scripts: Problems getting the mainwindow\n");
      return;
  }
  launch_context = gdk_app_launch_context_new();
  gdk_app_launch_context_set_screen(launch_context, gtk_widget_get_screen(mainwin->window));
  uri = g_strconcat("file://", get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, NULL);
  g_app_info_launch_default_for_uri(uri, launch_context, &error);

  if(error) {
      debug_print("Could not open scripts dir browser: '%s'\n", error->message);
      g_error_free(error);
  }

  g_object_unref(launch_context);
  g_free(uri);
}

static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
{
  remove_python_scripts_menus();

  migrate_scripts_out_of_base_dir();

  refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
  refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
}

static GtkToggleActionEntry mainwindow_tools_python_toggle[] = {
    {"Tools/ShowPythonConsole", NULL, N_("Show Python console..."),
        NULL, NULL, G_CALLBACK(show_hide_python_console), FALSE},
};

static GtkActionEntry mainwindow_tools_python_actions[] = {
    {"Tools/PythonScripts", NULL, N_("Python scripts") },
    {"Tools/PythonScripts/Refresh", NULL, N_("Refresh"),
        NULL, NULL, G_CALLBACK(refresh_python_scripts_menus) },
    {"Tools/PythonScripts/Browse", NULL, N_("Browse"),
        NULL, NULL, G_CALLBACK(browse_python_scripts_dir) },
    {"Tools/PythonScripts/---", NULL, "---" },
};

void python_menu_init(void)
{
  MainWindow *mainwin;
  guint id;

  mainwin =  mainwindow_get_mainwindow();

  gtk_action_group_add_toggle_actions(mainwin->action_group, mainwindow_tools_python_toggle, 1, mainwin);
  gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_python_actions, 3, mainwin);

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "ShowPythonConsole",
      "Tools/ShowPythonConsole", GTK_UI_MANAGER_MENUITEM, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "PythonScripts",
      "Tools/PythonScripts", GTK_UI_MANAGER_MENU, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Refresh",
      "Tools/PythonScripts/Refresh", GTK_UI_MANAGER_MENUITEM, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Browse",
      "Tools/PythonScripts/Browse", GTK_UI_MANAGER_MENUITEM, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Separator1",
      "Tools/PythonScripts/---", GTK_UI_MANAGER_SEPARATOR, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  refresh_python_scripts_menus(NULL, NULL);
}

void python_menu_done(void)
{
  MainWindow *mainwin;

  mainwin = mainwindow_get_mainwindow();

  if(mainwin && !claws_is_exiting()) {
    GSList *walk;

    remove_python_scripts_menus();

    for(walk = menu_id_list; walk; walk = walk->next)
      gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/ShowPythonConsole", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Refresh", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Browse", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/---", 0);
  }
}

gint plugin_init(gchar **error)
{
  /* Version check */
  if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9),
			   VERSION_NUMERIC, _("Python"), error))
    return -1;

  /* load hooks */
  hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
  if(hook_compose_create == (guint)-1) {
    *error = g_strdup(_("Failed to register \"compose create hook\" in the Python plugin"));
    return -1;
  }

  /* script directories */
  make_sure_directories_exist();

  /* initialize python interpreter */
  Py_Initialize();

  /* initialize python interactive shell */
  parasite_python_init();

  /* initialize Claws Mail Python module */
  claws_mail_python_init();

  /* load menu options */
  python_menu_init();

  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);

  debug_print("Python plugin loaded\n");

  return 0;
}

gboolean plugin_done(void)
{
  hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);

  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);

  python_menu_done();

  if(python_console) {
    gtk_widget_destroy(python_console);
    python_console = NULL;
  }

  /* finialize python interpreter */
  Py_Finalize();

  debug_print("Python plugin done and unloaded.\n");
  return FALSE;
}

const gchar *plugin_name(void)
{
  return _("Python");
}

const gchar *plugin_desc(void)
{
  return _("This plugin provides Python integration features.\n"
           "\nFor the most up-to-date API documentation, type\n"
           "\n help(clawsmail)\n"
           "\nin the interactive Python console under Tools -> Show Python console.\n"
           "\nThe source distribution of this plugin comes with various example scripts "
           "in the \"examples\" subdirectory. If you wrote a script that you would be "
           "interested in sharing, feel free to send it to me to have it considered "
           "for inclusion in the examples.\n"
           "\nFeedback to <berndth at gmx.de> is welcome.");
}

const gchar *plugin_type(void)
{
  return "GTK2";
}

const gchar *plugin_licence(void)
{
  return "GPL3+";
}

const gchar *plugin_version(void)
{
  return VERSION;
}

struct PluginFeature *plugin_provides(void)
{
  static struct PluginFeature features[] =
    { {PLUGIN_UTILITY, N_("Python integration")},
      {PLUGIN_NOTHING, NULL}};
  return features;
}

--- NEW FILE: composewindowtype.h ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef COMPOSEWINDOWTYPE_H
#define COMPOSEWINDOWTYPE_H

#include <Python.h>

#include "compose.h"

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif

PyMODINIT_FUNC initcomposewindow(PyObject *module);

PyObject* clawsmail_compose_new(PyObject *module, Compose *compose);

#endif /* COMPOSEWINDOWTYPE_H */

--- NEW FILE: messageinfotype.h ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef MESSAGEINFOTYPE_H
#define MESSAGEINFOTYPE_H

#include <Python.h>

#include "procmsg.h"

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif

PyMODINIT_FUNC initmessageinfo(PyObject *module);

PyObject* clawsmail_messageinfo_new(MsgInfo *msginfo);
MsgInfo* clawsmail_messageinfo_get_msginfo(PyObject *self);
PyTypeObject* clawsmail_messageinfo_get_type_object();

#endif /* MESSAGEINFOTYPE_H */

--- NEW FILE: clawsmailmodule.h ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */


#ifndef CLAWSMAILMODULE_H
#define CLAWSMAILMODULE_H

#include <Python.h>
#include <glib.h>

#include "compose.h"


void claws_mail_python_init(void);

PyObject* get_gobj_from_address(gpointer addr);
void put_composewindow_into_module(Compose *compose);

#endif /* CLAWSMAILMODULE_H */

--- NEW FILE: python-shell.h ---
/*
 * Copyright (c) 2008-2009  Christian Hammond
 * Copyright (c) 2008-2009  David Trowbridge
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef _PARASITE_PYTHON_SHELL_H_
#define _PARASITE_PYTHON_SHELL_H_

typedef struct _ParasitePythonShell      ParasitePythonShell;
typedef struct _ParasitePythonShellClass ParasitePythonShellClass;

#include <gtk/gtk.h>

#define PARASITE_TYPE_PYTHON_SHELL (parasite_python_shell_get_type())
#define PARASITE_PYTHON_SHELL(obj) \
		(G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShell))
#define PARASITE_PYTHON_SHELL_CLASS(klass) \
		(G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass))
#define PARASITE_IS_PYTHON_SHELL(obj) \
		(G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_PYTHON_SHELL))
#define PARASITE_IS_PYTHON_SHELL_CLASS(klass) \
		(G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_PYTHON_SHELL))
#define PARASITE_PYTHON_SHELL_GET_CLASS(obj) \
		(G_TYPE_INSTANCE_GET_CLASS ((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass))


struct _ParasitePythonShell
{
	GtkVBox parent_object;

	void (*gtk_reserved1)(void);
	void (*gtk_reserved2)(void);
	void (*gtk_reserved3)(void);
	void (*gtk_reserved4)(void);
};

struct _ParasitePythonShellClass
{
	GtkVBoxClass parent_class;

	void (*gtk_reserved1)(void);
	void (*gtk_reserved2)(void);
	void (*gtk_reserved3)(void);
	void (*gtk_reserved4)(void);
};

G_BEGIN_DECLS

GType parasite_python_shell_get_type(void);

GtkWidget *parasite_python_shell_new(void);
void parasite_python_shell_append_text(ParasitePythonShell *python_shell,
                                       const char *str,
                                       const char *tag);
void parasite_python_shell_focus(ParasitePythonShell *python_shell);

G_END_DECLS

#endif // _PARASITE_PYTHON_SHELL_H_

--- NEW FILE: messageinfotype.c ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009-2012 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>

#include "messageinfotype.h"

#include "common/tags.h"
#include "mainwindow.h"
#include "summaryview.h"

#include <structmember.h>


typedef struct {
    PyObject_HEAD
    PyObject *from;
    PyObject *to;
    PyObject *subject;
    PyObject *msgid;
    PyObject *filepath;
    MsgInfo *msginfo;
} clawsmail_MessageInfoObject;


static void MessageInfo_dealloc(clawsmail_MessageInfoObject* self)
{
  Py_XDECREF(self->from);
  Py_XDECREF(self->to);
  Py_XDECREF(self->subject);
  Py_XDECREF(self->msgid);
  self->ob_type->tp_free((PyObject*)self);
}

static int MessageInfo_init(clawsmail_MessageInfoObject *self, PyObject *args, PyObject *kwds)
{
  Py_INCREF(Py_None);
  self->from = Py_None;

  Py_INCREF(Py_None);
  self->to = Py_None;

  Py_INCREF(Py_None);
  self->subject = Py_None;

  Py_INCREF(Py_None);
  self->msgid = Py_None;

  return 0;
}

static PyObject* MessageInfo_str(PyObject *self)
{
  PyObject *str;
  str = PyString_FromString("MessageInfo: ");
  PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "From"));
  PyString_ConcatAndDel(&str, PyString_FromString(" / "));
  PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "Subject"));
  return str;
}

static PyObject *py_boolean_return_value(gboolean val)
{
  if(val) {
    Py_INCREF(Py_True);
    return Py_True;
  }
  else {
    Py_INCREF(Py_False);
    return Py_False;
  }
}

static PyObject *is_new(PyObject *self, PyObject *args)
{
  return py_boolean_return_value(MSG_IS_NEW(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
}

static PyObject *is_unread(PyObject *self, PyObject *args)
{
  return py_boolean_return_value(MSG_IS_UNREAD(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
}

static PyObject *is_marked(PyObject *self, PyObject *args)
{
  return py_boolean_return_value(MSG_IS_MARKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
}

static PyObject *is_replied(PyObject *self, PyObject *args)
{
  return py_boolean_return_value(MSG_IS_REPLIED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
}

static PyObject *is_locked(PyObject *self, PyObject *args)
{
  return py_boolean_return_value(MSG_IS_LOCKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
}

static PyObject *is_forwarded(PyObject *self, PyObject *args)
{
  return py_boolean_return_value(MSG_IS_FORWARDED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
}

static PyObject* get_tags(PyObject *self, PyObject *args)
{
  GSList *tags_list;
  Py_ssize_t num_tags;
  PyObject *tags_tuple;

  tags_list = ((clawsmail_MessageInfoObject*)self)->msginfo->tags;
  num_tags = g_slist_length(tags_list);

  tags_tuple = PyTuple_New(num_tags);
  if(tags_tuple != NULL) {
    Py_ssize_t iTag;
    PyObject *tag_object;
    GSList *walk;

    iTag = 0;
    for(walk = tags_list; walk; walk = walk->next) {
      tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
      if(tag_object == NULL) {
        Py_DECREF(tags_tuple);
        return NULL;
      }
      PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
    }
  }

  return tags_tuple;
}


static PyObject* add_or_remove_tag(PyObject *self, PyObject *args, gboolean add)
{
  int retval;
  const char *tag_str;
  gint tag_id;
  MsgInfo *msginfo;
  MainWindow *mainwin;

  retval = PyArg_ParseTuple(args, "s", &tag_str);
  if(!retval)
    return NULL;

  tag_id = tags_get_id_for_str(tag_str);
  if(tag_id == -1) {
    PyErr_SetString(PyExc_ValueError, "Tag does not exist");
    return NULL;
  }

  msginfo = ((clawsmail_MessageInfoObject*)self)->msginfo;

  if(!add) {
    /* raise KeyError if tag is not set */
    if(!g_slist_find(msginfo->tags, GINT_TO_POINTER(tag_id))) {
      PyErr_SetString(PyExc_KeyError, "Tag is not set on this message");
      return NULL;
    }
  }

  procmsg_msginfo_update_tags(msginfo, add, tag_id);

  /* update display */
  mainwin = mainwindow_get_mainwindow();
  if(mainwin)
    summary_redisplay_msg(mainwin->summaryview);

  Py_RETURN_NONE;
}



static PyObject* add_tag(PyObject *self, PyObject *args)
{
  return add_or_remove_tag(self, args, TRUE);
}


static PyObject* remove_tag(PyObject *self, PyObject *args)
{
  return add_or_remove_tag(self, args, FALSE);
}


static PyMethodDef MessageInfo_methods[] = {
  {"is_new",  is_new, METH_NOARGS,
   "is_new() - checks if the message is new\n"
   "\n"
   "Returns True if the new flag of the message is set."},

  {"is_unread",  is_unread, METH_NOARGS, 
   "is_unread() - checks if the message is unread\n"
   "\n"
   "Returns True if the unread flag of the message is set."},

  {"is_marked",  is_marked, METH_NOARGS,
   "is_marked() - checks if the message is marked\n"
   "\n"
   "Returns True if the marked flag of the message is set."},

  {"is_replied",  is_replied, METH_NOARGS,
   "is_replied() - checks if the message has been replied to\n"
   "\n"
   "Returns True if the replied flag of the message is set."},

  {"is_locked",  is_locked, METH_NOARGS,
   "is_locked() - checks if the message has been locked\n"
   "\n"
   "Returns True if the locked flag of the message is set."},

  {"is_forwarded",  is_forwarded, METH_NOARGS,
   "is_forwarded() - checks if the message has been forwarded\n"
   "\n"
   "Returns True if the forwarded flag of the message is set."},

  {"get_tags",  get_tags, METH_NOARGS,
   "get_tags() - get message tags\n"
   "\n"
   "Returns a tuple of tags that apply to this message."},

  {"add_tag",  add_tag, METH_VARARGS,
   "add_tag(tag) - add a tag to this message\n"
   "\n"
   "Add a tag to this message. If the tag is already set, nothing is done.\n"
   "If the tag does not exist, a ValueError exception is raised."},

  {"remove_tag",  remove_tag, METH_VARARGS,
   "remove_tag(tag) - remove a tag from this message\n"
   "\n"
   "Remove a tag from this message. If the tag is not set, a KeyError exception is raised.\n"
   "If the tag does not exist, a ValueError exception is raised."},

  {NULL}
};

static PyMemberDef MessageInfo_members[] = {
    {"From", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, from), 0,
     "From - the From header of the message"},

    {"To", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, to), 0,
     "To - the To header of the message"},

    {"Subject", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, subject), 0,
     "Subject - the subject header of the message"},

    {"MessageID", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, msgid), 0,
     "MessageID - the Message-ID header of the message"},

    {"FilePath", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, filepath), 0,
     "FilePath - path and filename of the message"},

    {NULL}
};

static PyTypeObject clawsmail_MessageInfoType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /* ob_size*/
    "clawsmail.MessageInfo",   /* tp_name*/
    sizeof(clawsmail_MessageInfoObject), /* tp_basicsize*/
    0,                         /* tp_itemsize*/
    (destructor)MessageInfo_dealloc, /* tp_dealloc*/
    0,                         /* tp_print*/
    0,                         /* tp_getattr*/
    0,                         /* tp_setattr*/
    0,                         /* tp_compare*/
    0,                         /* tp_repr*/
    0,                         /* tp_as_number*/
    0,                         /* tp_as_sequence*/
    0,                         /* tp_as_mapping*/
    0,                         /* tp_hash */
    0,                         /* tp_call*/
    MessageInfo_str,           /* tp_str*/
    0,                         /* tp_getattro*/
    0,                         /* tp_setattro*/
    0,                         /* tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,        /* tp_flags*/
    "A MessageInfo represents" /* tp_doc */
    "a single message.",
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    MessageInfo_methods,       /* tp_methods */
    MessageInfo_members,       /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)MessageInfo_init,/* tp_init */
    0,                         /* tp_alloc */
    0,                         /* tp_new */
};

PyMODINIT_FUNC initmessageinfo(PyObject *module)
{
    clawsmail_MessageInfoType.tp_new = PyType_GenericNew;
    if(PyType_Ready(&clawsmail_MessageInfoType) < 0)
        return;

    Py_INCREF(&clawsmail_MessageInfoType);
    PyModule_AddObject(module, "MessageInfo", (PyObject*)&clawsmail_MessageInfoType);
}

#define MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(fis, pms)     \
  do {                                                            \
    if(fis) {                                                     \
      PyObject *str;                                              \
      str = PyString_FromString(fis);                             \
      if(str) {                                                   \
        int retval;                                               \
        retval = PyObject_SetAttrString((PyObject*)ff, pms, str); \
        Py_DECREF(str);                                           \
        if(retval == -1)                                          \
          goto err;                                               \
      }                                                           \
    }                                                             \
  } while(0)

static gboolean update_members(clawsmail_MessageInfoObject *ff, MsgInfo *msginfo)
{
  gchar *filepath;

  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->from, "From");
  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->to, "To");
  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->subject, "Subject");
  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->msgid, "MessageID");

  filepath = procmsg_get_message_file_path(msginfo);
  if(filepath) {
    MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(filepath, "FilePath");
    g_free(filepath);
  }
  else {
    MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER("", "FilePath");
  }

  return TRUE;
err:
  return FALSE;
}

PyObject* clawsmail_messageinfo_new(MsgInfo *msginfo)
{
  clawsmail_MessageInfoObject *ff;

  if(!msginfo)
    return NULL;

  ff = (clawsmail_MessageInfoObject*) PyObject_CallObject((PyObject*) &clawsmail_MessageInfoType, NULL);
  if(!ff)
    return NULL;

  ff->msginfo = msginfo;

  if(update_members(ff, msginfo))
    return (PyObject*)ff;
  else {
    Py_XDECREF(ff);
    return NULL;
  }
}

PyTypeObject* clawsmail_messageinfo_get_type_object()
{
  return &clawsmail_MessageInfoType;
}

MsgInfo* clawsmail_messageinfo_get_msginfo(PyObject *self)
{
  return ((clawsmail_MessageInfoObject*)self)->msginfo;
}

--- placeholder.txt DELETED ---

--- NEW FILE: composewindowtype.c ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>

#include "composewindowtype.h"

#include "clawsmailmodule.h"
#include "foldertype.h"
#include "messageinfotype.h"

#include "mainwindow.h"
#include "account.h"
#include "summaryview.h"

#include <structmember.h>

#include <string.h>

typedef struct {
    PyObject_HEAD
    PyObject *ui_manager;
    PyObject *text;
    PyObject *replyinfo;
    Compose *compose;
} clawsmail_ComposeWindowObject;

static void ComposeWindow_dealloc(clawsmail_ComposeWindowObject* self)
{
  Py_XDECREF(self->ui_manager);
  Py_XDECREF(self->text);
  Py_XDECREF(self->replyinfo);
  self->ob_type->tp_free((PyObject*)self);
}

static void flush_gtk_queue(void)
{
  while(gtk_events_pending())
    gtk_main_iteration();
}

static void store_py_object(PyObject **target, PyObject *obj)
{
  Py_XDECREF(*target);
  if(obj)
  {
    Py_INCREF(obj);
    *target = obj;
  }
  else {
    Py_INCREF(Py_None);
    *target = Py_None;
  }
}

static void composewindow_set_compose(clawsmail_ComposeWindowObject *self, Compose *compose)
{
  self->compose = compose;

  store_py_object(&(self->ui_manager), get_gobj_from_address(compose->ui_manager));
  store_py_object(&(self->text), get_gobj_from_address(compose->text));

  store_py_object(&(self->replyinfo), clawsmail_messageinfo_new(compose->replyinfo));
}

static int ComposeWindow_init(clawsmail_ComposeWindowObject *self, PyObject *args, PyObject *kwds)
{
  MainWindow *mainwin;
  PrefsAccount *ac = NULL;
  FolderItem *item;
  GList* list;
  GList* cur;
  gboolean did_find_compose;
  Compose *compose = NULL;
  const char *ss;
  unsigned char open_window;
  /* if __open_window is set to 0/False,
   * composewindow_set_compose must be called before this object is valid */
  static char *kwlist[] = {"address", "__open_window", NULL};

  ss = NULL;
  open_window = 1;
  PyArg_ParseTupleAndKeywords(args, kwds, "|sb", kwlist, &ss, &open_window);

  if(open_window) {
    mainwin = mainwindow_get_mainwindow();
    item = mainwin->summaryview->folder_item;
    did_find_compose = FALSE;

    if(ss) {
      ac = account_find_from_address(ss, FALSE);
      if (ac && ac->protocol != A_NNTP) {
        compose = compose_new_with_folderitem(ac, item, NULL);
        did_find_compose = TRUE;
      }
    }
    if(!did_find_compose) {
      if (item) {
        ac = account_find_from_item(item);
        if (ac && ac->protocol != A_NNTP) {
          compose = compose_new_with_folderitem(ac, item, NULL);
          did_find_compose = TRUE;
        }
      }

      /* use current account */
      if (!did_find_compose && cur_account && (cur_account->protocol != A_NNTP)) {
        compose = compose_new_with_folderitem(cur_account, item, NULL);
        did_find_compose = TRUE;
      }

      if(!did_find_compose) {
        /* just get the first one */
        list = account_get_list();
        for (cur = list ; cur != NULL ; cur = g_list_next(cur)) {
          ac = (PrefsAccount *) cur->data;
          if (ac->protocol != A_NNTP) {
            compose = compose_new_with_folderitem(ac, item, NULL);
            did_find_compose = TRUE;
          }
        }
      }
    }

    if(!did_find_compose)
      return -1;

    composewindow_set_compose(self, compose);
    gtk_widget_show_all(compose->window);
    flush_gtk_queue();
  }
  return 0;
}

/* this is here because wrapping GTK_EDITABLEs in PyGTK is buggy */
static PyObject* get_python_object_from_gtk_entry(GtkWidget *entry)
{
  return Py_BuildValue("s", gtk_entry_get_text(GTK_ENTRY(entry)));
}

static PyObject* set_gtk_entry_from_python_object(GtkWidget *entry, PyObject *args)
{
  const char *ss;

  if(!PyArg_ParseTuple(args, "s", &ss))
    return NULL;

  gtk_entry_set_text(GTK_ENTRY(entry), ss);

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* ComposeWindow_get_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  return get_python_object_from_gtk_entry(self->compose->subject_entry);
}

static PyObject* ComposeWindow_set_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  PyObject *ret;
  ret = set_gtk_entry_from_python_object(self->compose->subject_entry, args);
  flush_gtk_queue();
  return ret;
}

static PyObject* ComposeWindow_get_from(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  return get_python_object_from_gtk_entry(self->compose->from_name);
}

static PyObject* ComposeWindow_set_from(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  PyObject *ret;
  ret = set_gtk_entry_from_python_object(self->compose->from_name, args);
  flush_gtk_queue();
  return ret;
}

static PyObject* ComposeWindow_add_To(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  const char *ss;

  if(!PyArg_ParseTuple(args, "s", &ss))
    return NULL;

  compose_entry_append(self->compose, ss, COMPOSE_TO, PREF_NONE);

  flush_gtk_queue();

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* ComposeWindow_add_Cc(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  const char *ss;

  if(!PyArg_ParseTuple(args, "s", &ss))
    return NULL;

  compose_entry_append(self->compose, ss, COMPOSE_CC, PREF_NONE);

  flush_gtk_queue();

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* ComposeWindow_add_Bcc(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  const char *ss;

  if(!PyArg_ParseTuple(args, "s", &ss))
    return NULL;

  compose_entry_append(self->compose, ss, COMPOSE_BCC, PREF_NONE);

  flush_gtk_queue();

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* ComposeWindow_attach(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  PyObject *olist;
  Py_ssize_t size, iEl;
  GList *list = NULL;

  if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist))
    return NULL;

  size = PyList_Size(olist);
  for(iEl = 0; iEl < size; iEl++) {
    char *ss;
    PyObject *element = PyList_GET_ITEM(olist, iEl);

    if(!element)
      continue;

    Py_INCREF(element);
    if(!PyArg_Parse(element, "s", &ss)) {
      Py_DECREF(element);
      if(list)
        g_list_free(list);
      return NULL;
    }
    list = g_list_prepend(list, ss);
    Py_DECREF(element);
  }

  compose_attach_from_list(self->compose, list, FALSE);
  g_list_free(list);

  flush_gtk_queue();

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* ComposeWindow_get_header_list(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  GSList *walk;
  PyObject *retval;

  retval = Py_BuildValue("[]");
  for(walk = self->compose->header_list; walk; walk = walk->next) {
    ComposeHeaderEntry *headerentry = walk->data;
    const gchar *header;
    const gchar *text;

    header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo))), 0, -1);
    text = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));

    if(text && strcmp("", text)) {
      PyObject *ee;
      int ok;

      ee = Py_BuildValue("(ss)", header, text);
      ok = PyList_Append(retval, ee);
      Py_DECREF(ee);
      if(ok == -1) {
        Py_DECREF(retval);
        return NULL;
      }
    }
  }
  return retval;
}

static PyObject* ComposeWindow_add_header(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  const char *header;
  const char *text;
  gint num;

  if(!PyArg_ParseTuple(args, "ss", &header, &text))
    return NULL;

  /* add a dummy, and modify it then */
  compose_entry_append(self->compose, "dummy1dummy2dummy3", COMPOSE_TO, PREF_NONE);
  num = g_slist_length(self->compose->header_list);
  if(num > 1) {
    ComposeHeaderEntry *headerentry;
    headerentry = g_slist_nth_data(self->compose->header_list, num-2);
    if(headerentry) {
      GtkEditable *editable;
      gint pos;
      gtk_entry_set_text(GTK_ENTRY(headerentry->entry), text);
      editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo)));
      gtk_editable_delete_text(editable, 0, -1);
      gtk_editable_insert_text(editable, header, -1, &pos);
    }
  }

  flush_gtk_queue();

  Py_INCREF(Py_None);
  return Py_None;
}

/* this is pretty ugly, as the compose struct does not maintain a pointer to the account selection combo */
static PyObject* ComposeWindow_get_account_selection(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  GList *children, *walk;

  children = gtk_container_get_children(GTK_CONTAINER(self->compose->header_table));
  for(walk = children; walk; walk = walk->next) {
    if(GTK_IS_HBOX(walk->data)) {
      GList *children2, *walk2;
      children2 = gtk_container_get_children(GTK_CONTAINER(walk->data));
      for(walk2 = children2; walk2; walk2 = walk2->next) {
        if(GTK_IS_EVENT_BOX(walk2->data)) {
          return get_gobj_from_address(gtk_container_get_children(GTK_CONTAINER(walk2->data))->data);
        }
      }
    }
  }
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* ComposeWindow_save_message_to(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  PyObject *arg;

  if(!PyArg_ParseTuple(args, "O", &arg))
    return NULL;

  if(PyString_Check(arg)) {
    GtkEditable *editable;
    gint pos;

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);

    editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
    gtk_editable_delete_text(editable, 0, -1);
    gtk_editable_insert_text(editable, PyString_AsString(arg), -1, &pos);
  }
  else if(clawsmail_folder_check(arg)) {
    GtkEditable *editable;
    gint pos;

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);

    editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
    gtk_editable_delete_text(editable, 0, -1);
    gtk_editable_insert_text(editable, folder_item_get_identifier(clawsmail_folder_get_item(arg)), -1, &pos);
  }
  else if (arg == Py_None){
    /* turn off checkbutton */
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), FALSE);
  }
  else {
    PyErr_SetString(PyExc_TypeError, "function takes exactly one argument which may be a folder object, a string, or None");
    return NULL;
  }

  flush_gtk_queue();

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* ComposeWindow_set_modified(clawsmail_ComposeWindowObject *self, PyObject *args)
{
  char modified = 0;
  gboolean old_modified;

  if(!PyArg_ParseTuple(args, "b", &modified))
    return NULL;

  old_modified = self->compose->modified;

  self->compose->modified = (modified != 0);

  /* If the modified state changed, rewrite window title.
   * This partly duplicates functionality in compose.c::compose_set_title().
   * While it's nice to not have to modify Claws Mail for this to work,
   * it would be cleaner to export that function in Claws Mail. */
  if((strcmp(gtk_window_get_title(GTK_WINDOW(self->compose->window)), _("Compose message")) != 0) &&
      (old_modified != self->compose->modified)) {
      gchar *str;
      gchar *edited;
      gchar *subject;

      edited = self->compose->modified  ? _(" [Edited]") : "";
      subject = gtk_editable_get_chars(GTK_EDITABLE(self->compose->subject_entry), 0, -1);
      if(subject && strlen(subject))
        str = g_strdup_printf(_("%s - Compose message%s"),
            subject, edited);
      else
        str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
      gtk_window_set_title(GTK_WINDOW(self->compose->window), str);
      g_free(str);
      g_free(subject);
  }

  flush_gtk_queue();

  Py_INCREF(Py_None);
  return Py_None;
}

static PyMethodDef ComposeWindow_methods[] = {
    {"set_subject", (PyCFunction)ComposeWindow_set_subject, METH_VARARGS,
     "set_subject(text) - set subject to text\n"
     "\n"
     "Set the subject to text. text must be a string."},

    {"get_subject", (PyCFunction)ComposeWindow_get_subject, METH_NOARGS,
     "get_subject() - get subject\n"
     "\n"
     "Get a string of the current subject entry."},

    {"set_from", (PyCFunction)ComposeWindow_set_from, METH_VARARGS,
     "set_from(text) - set From header entry to text\n"
     "\n"
     "Set the From header entry to text. text must be a string.\n"
     "Beware: No sanity checking is performed."},

    {"get_from", (PyCFunction)ComposeWindow_get_from, METH_NOARGS,
     "get_from - get From header entry\n"
     "\n"
     "Get a string of the current From header entry."},

    {"add_To",  (PyCFunction)ComposeWindow_add_To,  METH_VARARGS,
     "add_To(text) - append another To header with text\n"
     "\n"
     "Add another header line with the combo box set to To:, and the\n"
     "content set to text."},

    {"add_Cc",  (PyCFunction)ComposeWindow_add_Cc,  METH_VARARGS,
     "add_Cc(text) - append another Cc header with text\n"
     "\n"
     "Add another header line with the combo box set to Cc:, and the\n"
     "content set to text."},

    {"add_Bcc", (PyCFunction)ComposeWindow_add_Bcc, METH_VARARGS,
     "add_Bcc(text) - append another Bcc header with text\n"
     "\n"
     "Add another header line with the combo box set to Bcc:, and the\n"
     "content set to text."},

    {"add_header", (PyCFunction)ComposeWindow_add_header, METH_VARARGS,
     "add_header(headername, text) - add a custom header\n"
     "\n"
     "Adds a custom header with the header set to headername, and the\n"
     "contents set to text."},

    {"get_header_list", (PyCFunction)ComposeWindow_get_header_list, METH_NOARGS,
     "get_header_list() - get list of headers\n"
     "\n"
     "Gets a list of headers that are currently defined in the compose window.\n"
     "The return value is a list of tuples, where the first tuple element is\n"
     "the header name (entry in the combo box) and the second element is the contents."},

    {"attach",  (PyCFunction)ComposeWindow_attach, METH_VARARGS,
     "attach(filenames) - attach a list of files\n"
     "\n"
     "Attach files to the mail. The filenames argument is a list of\n"
     "string of the filenames that are being attached."},

    {"get_account_selection", (PyCFunction)ComposeWindow_get_account_selection, METH_NOARGS,
     "get_account_selection() - get account selection widget\n"
     "\n"
     "Returns the account selection combo box as a gtk.ComboBox"},

    {"save_message_to", (PyCFunction)ComposeWindow_save_message_to, METH_VARARGS,
     "save_message_to(folder) - save message to folder id\n"
     "\n"
     "Set the folder where the sent message will be saved to. folder may be\n"
     "a Folder, a string of the folder identifier (e.g. #mh/foo/bar), or\n"
     "None is which case the message will not be saved at all."},

     {"set_modified", (PyCFunction)ComposeWindow_set_modified, METH_VARARGS,
     "set_modified(bool) - set or unset modification marker of compose window\n"
     "\n"
     "Set or unset the modification marker of the compose window. This marker determines\n"
     "for example whether you get a confirmation dialog when closing the compose window\n"
     "or not.\n"
     "In the usual case, Claws Mail keeps track of the modification status itself.\n"
     "However, there are cases when it might be desirable to overwrite the marker,\n"
     "for example because a compose_any script modifies the body or subject which\n"
     "can be regarded compose window preprocessing and should not trigger a confirmation\n"
     "dialog on close like a manual edit."},

    {NULL}
};

static PyMemberDef ComposeWindow_members[] = {
    {"ui_manager", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, ui_manager), 0,
     "ui_manager - the gtk.UIManager of the compose window"},

    {"text", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, text), 0,
     "text - the gtk.TextView widget of the message body"},

    {"replyinfo", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, replyinfo), 0,
     "replyinfo - The MessageInfo object of the message that is being replied to, or None"},

    {NULL}
};

static PyTypeObject clawsmail_ComposeWindowType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "clawsmail.ComposeWindow", /*tp_name*/
    sizeof(clawsmail_ComposeWindowObject), /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)ComposeWindow_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,        /*tp_flags*/
    /* tp_doc */
    "ComposeWindow objects. Optional argument to constructor: sender account address. ",
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    ComposeWindow_methods,     /* tp_methods */
    ComposeWindow_members,     /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)ComposeWindow_init, /* tp_init */
    0,                         /* tp_alloc */
    0,                         /* tp_new */
};

PyMODINIT_FUNC initcomposewindow(PyObject *module)
{
    clawsmail_ComposeWindowType.tp_new = PyType_GenericNew;
    if(PyType_Ready(&clawsmail_ComposeWindowType) < 0)
        return;

    Py_INCREF(&clawsmail_ComposeWindowType);
    PyModule_AddObject(module, "ComposeWindow", (PyObject*)&clawsmail_ComposeWindowType);
}

PyObject* clawsmail_compose_new(PyObject *module, Compose *compose)
{
  PyObject *class, *dict;
  PyObject *self, *args, *kw;

  if(!compose) {
    Py_INCREF(Py_None);
    return Py_None;
  }

  dict = PyModule_GetDict(module);
  class = PyDict_GetItemString(dict, "ComposeWindow");
  args = Py_BuildValue("()");
  kw = Py_BuildValue("{s:b}", "__open_window", 0);
  self = PyObject_Call(class, args, kw);
  Py_DECREF(args);
  Py_DECREF(kw);
  composewindow_set_compose((clawsmail_ComposeWindowObject*)self, compose);
  return self;
}

--- NEW FILE: python-hooks.c ---
/*
 * Copyright (c) 2008-2009  Christian Hammond
 * Copyright (c) 2008-2009  David Trowbridge
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>

#ifdef ENABLE_PYTHON
#include <Python.h>
#include <pygobject.h>
#include <pygtk/pygtk.h>
#endif // ENABLE_PYTHON

#include <dlfcn.h>

#include <signal.h>

#include "python-hooks.h"


static gboolean python_enabled = FALSE;

#ifdef ENABLE_PYTHON
static GString *captured_stdout = NULL;
static GString *captured_stderr = NULL;


static PyObject *
capture_stdout(PyObject *self, PyObject *args)
{
    char *str = NULL;

    if (!PyArg_ParseTuple(args, "s", &str))
        return NULL;

    g_string_append(captured_stdout, str);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
capture_stderr(PyObject *self, PyObject *args)
{
    char *str = NULL;

    if (!PyArg_ParseTuple(args, "s", &str))
        return NULL;

    g_string_append(captured_stderr, str);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
capture_stdin(PyObject *self, PyObject *args)
{
    /* Return an empty string.
     * This is what read() returns when hitting EOF. */
    return PyString_FromString("");
}

static PyObject *
wrap_gobj(PyObject *self, PyObject *args)
{
    void *addr;
    GObject *obj;

    if (!PyArg_ParseTuple(args, "l", &addr))
        return NULL;

    if (!G_IS_OBJECT(addr))
        return NULL; // XXX

    obj = G_OBJECT(addr);

    if (!obj)
        return NULL; // XXX

    return pygobject_new(obj);
}

static PyMethodDef parasite_python_methods[] = {
    {"capture_stdout", capture_stdout, METH_VARARGS, "Captures stdout"},
    {"capture_stderr", capture_stderr, METH_VARARGS, "Captures stderr"},
    {"capture_stdin", capture_stdin, METH_VARARGS, "Captures stdin"},
    {"gobj", wrap_gobj, METH_VARARGS, "Wraps a C GObject"},
    {NULL, NULL, 0, NULL}
};


static gboolean
is_blacklisted(void)
{
    const char *prgname = g_get_prgname();

    return (!strcmp(prgname, "gimp"));
}
#endif // ENABLE_PYTHON

void
parasite_python_init(void)
{
#ifdef ENABLE_PYTHON
    int res;
    struct sigaction old_sigint;
    PyObject *pygtk;

    if (is_blacklisted())
        return;

    /* This prevents errors such as "undefined symbol: PyExc_ImportError" */
    if (!dlopen(PYTHON_SHARED_LIB, RTLD_NOW | RTLD_GLOBAL))
    {
        g_error("%s\n", dlerror());
        return;
    }

    captured_stdout = g_string_new("");
    captured_stderr = g_string_new("");

    /* Back up and later restore SIGINT so Python doesn't steal it from us. */
    res = sigaction(SIGINT, NULL, &old_sigint);

    if (!Py_IsInitialized())
        Py_Initialize();

    res = sigaction(SIGINT, &old_sigint, NULL);

    Py_InitModule("parasite", parasite_python_methods);
    PyRun_SimpleString(
        "import parasite\n"
        "import sys\n"
        "\n"
        "class StdoutCatcher:\n"
        "    def write(self, str):\n"
        "        parasite.capture_stdout(str)\n"
        "    def flush(self):\n"
        "        pass\n"
        "\n"
        "class StderrCatcher:\n"
        "    def write(self, str):\n"
        "        parasite.capture_stderr(str)\n"
        "    def flush(self):\n"
        "        pass\n"
        "\n"
        "class StdinCatcher:\n"
        "    def readline(self, size=-1):\n"
        "        return parasite.capture_stdin(size)\n"
        "    def read(self, size=-1):\n"
        "        return parasite.capture_stdin(size)\n"
        "    def flush(self):\n"
        "        pass\n"
        "\n"
    );

    if (!pygobject_init(-1, -1, -1))
        return;

    pygtk = PyImport_ImportModule("gtk");

    if (pygtk != NULL)
    {
        PyObject *module_dict = PyModule_GetDict(pygtk);
        PyObject *cobject = PyDict_GetItemString(module_dict, "_PyGtk_API");

        /*
         * This seems to be NULL when we're running a PyGTK program.
         * We really need to find out why.
         */
        if (cobject != NULL)
        {
            if (PyCObject_Check(cobject))
                _PyGtk_API = (struct _PyGtk_FunctionStruct*)
                PyCObject_AsVoidPtr(cobject);
            else {
                PyErr_SetString(PyExc_RuntimeError,
                                "could not find _PyGtk_API object");
                return;
            }
        }
    } else {
        PyErr_SetString(PyExc_ImportError, "could not import gtk");
        return;
    }

    python_enabled = TRUE;
#endif // ENABLE_PYTHON
}

void
parasite_python_run(const char *command,
                    ParasitePythonLogger stdout_logger,
                    ParasitePythonLogger stderr_logger,
                    gpointer user_data)
{
#ifdef ENABLE_PYTHON
    PyGILState_STATE gstate;
    PyObject *module;
    PyObject *dict;
    PyObject *obj;
    const char *cp;

    /* empty string as command is a noop */
    if(!strcmp(command, ""))
      return;

    /* if first non-whitespace character is '#', command is also a noop */
    cp = command;
    while(cp && (*cp != '\0') && g_ascii_isspace(*cp))
      cp++;
    if(cp && *cp == '#')
      return;

    gstate = PyGILState_Ensure();

    module = PyImport_AddModule("__main__");
    dict = PyModule_GetDict(module);

    PyRun_SimpleString("old_stdout = sys.stdout\n"
                       "old_stderr = sys.stderr\n"
                       "old_stdin  = sys.stdin\n"
                       "sys.stdout = StdoutCatcher()\n"
                       "sys.stderr = StderrCatcher()\n"
                       "sys.stdin  = StdinCatcher()\n");

    obj = PyRun_String(command, Py_single_input, dict, dict);
    if(PyErr_Occurred())
      PyErr_Print();
    PyRun_SimpleString("sys.stdout = old_stdout\n"
                       "sys.stderr = old_stderr\n"
                       "sys.stdin = old_stdin\n");

    if (stdout_logger != NULL)
        stdout_logger(captured_stdout->str, user_data);

    if (stderr_logger != NULL)
        stderr_logger(captured_stderr->str, user_data);

    // Print any returned object
    if (obj != NULL && obj != Py_None) {
       PyObject *repr = PyObject_Repr(obj);
       if (repr != NULL) {
           char *string = PyString_AsString(repr);

           stdout_logger(string, user_data);
           stdout_logger("\n", user_data);
        }

        Py_XDECREF(repr);
    }
    Py_XDECREF(obj);

    PyGILState_Release(gstate);
    g_string_erase(captured_stdout, 0, -1);
    g_string_erase(captured_stderr, 0, -1);
#endif // ENABLE_PYTHON
}

gboolean
parasite_python_is_enabled(void)
{
    return python_enabled;
}

// vim: set et sw=4 ts=4:

--- NEW FILE: Makefile.am ---
plugindir = $(pkglibdir)/plugins

plugin_LTLIBRARIES = python_plugin.la

python_plugin_la_SOURCES = \
	clawsmailmodule.c \
	clawsmailmodule.h \
	composewindowtype.c \
	composewindowtype.h \
	foldertype.c \
	foldertype.h \
	messageinfotype.c \
	messageinfotype.h \
	nodetype.c \
	nodetype.h \
	python_plugin.c \
	python-hooks.c \
	python-hooks.h \
	python-shell.c \
	python-shell.h \
	gettext.h

python_plugin_la_LDFLAGS = \
	-avoid-version -module \
	$(GLIB_LIBS) \
	$(GTK_LIBS) \
	$(PYGTK_LIBS) \
	$(PYTHON_LIBS)

INCLUDES = \
	-I$(top_srcdir)/src \
	-I$(top_srcdir)/src/common \
	-I$(top_srcdir)/src/gtk

AM_CPPFLAGS = \
	-Wall \
	$(CLAWS_MAIL_CFLAGS) \
	$(GLIB_CFLAGS) \
	$(GTK_CFLAGS) \
	$(PYTHON_CFLAGS) \
	$(PYGTK_CFLAGS) \
	-DPYTHON_SHARED_LIB="\"$(PYTHON_SHARED_LIB)\"" \
	-DENABLE_PYTHON \
	-fno-strict-aliasing \
	-DLOCALEDIR=\""$(localedir)"\"

--- NEW FILE: nodetype.c ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>

#include "nodetype.h"

#include <structmember.h>

PyMODINIT_FUNC initnode(PyObject *module)
{
  PyObject *dict;
  PyObject *res;
  const char *cmd =
      "class Node(object):\n"
      "    \"\"\"A general purpose tree container type\"\"\"\n"
      "\n"
      "    def __init__(self):\n"
      "        self.data = None\n"
      "        self.children = []\n"
      "\n"
      "    def __str__(self):\n"
      "        return '\\n'.join(self.get_str_list(0))\n"
      "\n"
      "    def get_str_list(self, level):\n"
      "        \"\"\"get_str_list(level) - get a list of string-representations of the tree data\n"
      "        \n"
      "        The nesting of the tree elements is represented by various levels of indentation.\"\"\"\n"
      "        str = []\n"
      "        indent = '  '*level\n"
      "        if self.data:\n"
      "            str.append(indent + self.data.__str__())\n"
      "        else:\n"
      "            str.append(indent + 'None')\n"
      "        for child in self.children:\n"
      "            str.extend(child.get_str_list(level+1))\n"
      "        return str\n"
      "\n"
      "    def traverse(self, callback, arg=None):\n"
      "        \"\"\"traverse(callback [, arg=None]) - traverse the tree\n"
      "        \n"
      "        Traverse the tree, calling the callback function for each node element,\n"
      "        with optional arg as user-data. The expected callback function signature is\n"
      "        callback(node_data [, arg]).\"\"\"\n"
      "        if self.data:\n"
      "            if arg is not None:\n"
      "                callback(self.data, arg)\n"
      "            else:\n"
      "                callback(self.data)\n"
      "        for child in self.children:\n"
      "            child.traverse(callback, arg)\n"
      "\n"
      "    def flat_list(self):\n"
      "        \"\"\"flat_list() - get a flat list of the tree\n"
      "        \n"
      "        Returns a flat list of the tree, disregarding the nesting structure.\"\"\"\n"
      "        flat_list = []\n"
      "        self.traverse(lambda data,list: list.append(data), flat_list)\n"
      "        return flat_list\n"
      "\n";

  dict = PyModule_GetDict(module);

  if(PyDict_GetItemString(dict, "__builtins__") == NULL)
    PyDict_SetItemString(dict, "__builtins__", PyEval_GetBuiltins());

  res = PyRun_String(cmd, Py_file_input, dict, dict);
  Py_XDECREF(res);
}

PyObject* clawsmail_node_new(PyObject *module)
{
  PyObject *class, *dict;

  dict = PyModule_GetDict(module);
  class = PyDict_GetItemString(dict, "Node");
  return PyObject_CallObject(class, NULL);
}

--- NEW FILE: foldertype.h ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef FOLDERTYPE_H
#define FOLDERTYPE_H

#include <Python.h>

#include "folder.h"

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif

PyMODINIT_FUNC initfolder(PyObject *module);

PyObject* clawsmail_folder_new(FolderItem *folderitem);
FolderItem* clawsmail_folder_get_item(PyObject *self);
PyTypeObject* clawsmail_folder_get_type_object();

gboolean clawsmail_folder_check(PyObject *self);

#endif /* FOLDERTYPE_H */

--- NEW FILE: python-hooks.h ---
/*
 * Copyright (c) 2008-2009  Christian Hammond
 * Copyright (c) 2008-2009  David Trowbridge
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef _GTKPARASITE_PYTHON_MODULE_H_
#define _GTKPARASITE_PYTHON_MODULE_H_

#include <glib.h>


typedef void (*ParasitePythonLogger)(const char *text, gpointer user_data);

void parasite_python_init(void);
void parasite_python_run(const char *command,
                         ParasitePythonLogger stdout_logger,
                         ParasitePythonLogger stderr_logger,
                         gpointer user_data);
gboolean parasite_python_is_enabled(void);

#endif // _GTKPARASITE_PYTHON_MODULE_H_

--- NEW FILE: nodetype.h ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef NODETYPE_H
#define NODETYPE_H

#include <Python.h>

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif

PyMODINIT_FUNC initnode(PyObject *module);

PyObject* clawsmail_node_new(PyObject *module);

#endif /* NODETYPE_H */

--- NEW FILE: foldertype.c ---
/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>

#include "foldertype.h"
#include "messageinfotype.h"

#include <structmember.h>


typedef struct {
    PyObject_HEAD
    PyObject *name;
    PyObject *path;
    PyObject *mailbox_name;
    FolderItem *folderitem;
} clawsmail_FolderObject;


static void Folder_dealloc(clawsmail_FolderObject* self)
{
  Py_XDECREF(self->name);
  Py_XDECREF(self->path);
  Py_XDECREF(self->mailbox_name);
  self->ob_type->tp_free((PyObject*)self);
}

#define FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self,fis, pms)    \
  do {                                                              \
    if(fis) {                                                       \
      PyObject *str;                                                \
      str = PyString_FromString(fis);                               \
      if(str) {                                                     \
        int retval;                                                 \
        retval = PyObject_SetAttrString((PyObject*)self, pms, str); \
        Py_DECREF(str);                                             \
        if(retval == -1)                                            \
          goto err;                                                 \
      }                                                             \
    }                                                               \
  } while(0)

static int Folder_init(clawsmail_FolderObject *self, PyObject *args, PyObject *kwds)
{
  const char *ss = NULL;
  FolderItem *folderitem = NULL;
  char create = 0;

  /* optional constructor argument: folderitem id string */
  if(!PyArg_ParseTuple(args, "|sb", &ss, &create))
    return -1;
  
  Py_INCREF(Py_None);
  self->name = Py_None;
  
  Py_INCREF(Py_None);
  self->path = Py_None;

  Py_INCREF(Py_None);
  self->mailbox_name = Py_None;

  if(ss) {
    if(create == 0) {
      folderitem = folder_find_item_from_identifier(ss);
      if(!folderitem) {
        PyErr_SetString(PyExc_ValueError, "A folder with that path does not exist, and the create parameter was False.");
        return -1;
      }
    }
    else {
      folderitem = folder_get_item_from_identifier(ss);
      if(!folderitem) {
        PyErr_SetString(PyExc_IOError, "A folder with that path does not exist, and could not be created.");
        return -1;
      }
    }
  }

  if(folderitem) {
    FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self, folderitem->name, "name");
    FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self, folderitem->path, "path");
    FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self, folderitem->folder->name, "mailbox_name");
    self->folderitem = folderitem;
  }

  return 0;

 err:
  return -1;
}

static PyObject* Folder_str(PyObject *self)
{
  PyObject *str;
  str = PyString_FromString("Folder: ");
  PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "name"));
  return str;
}

static PyObject* Folder_get_identifier(clawsmail_FolderObject *self, PyObject *args)
{
  PyObject *obj;
  gchar *id;
  if(!self->folderitem)
    return NULL;
  id = folder_item_get_identifier(self->folderitem);
  obj = Py_BuildValue("s", id);
  g_free(id);
  return obj;
}

static PyObject* Folder_get_messages(clawsmail_FolderObject *self, PyObject *args)
{
  GSList *msglist, *walk;
  PyObject *retval;
  Py_ssize_t pos;

  if(!self->folderitem)
    return NULL;

  msglist = folder_item_get_msg_list(self->folderitem);
  retval = PyTuple_New(g_slist_length(msglist));
  if(!retval) {
    procmsg_msg_list_free(msglist);
    Py_INCREF(Py_None);
    return Py_None;
  }
  
  for(pos = 0, walk = msglist; walk; walk = walk->next, ++pos) {
    PyObject *msg;
    msg = clawsmail_messageinfo_new(walk->data);
    PyTuple_SET_ITEM(retval, pos, msg);
  }
  procmsg_msg_list_free(msglist);
  
  return retval;
}

static PyMethodDef Folder_methods[] = {
    {"get_identifier", (PyCFunction)Folder_get_identifier, METH_NOARGS,
     "get_identifier() - get identifier\n"
     "\n"
     "Get identifier for folder as a string (e.g. #mh/foo/bar)."},
    {"get_messages", (PyCFunction)Folder_get_messages, METH_NOARGS,
     "get_messages() - get a tuple of messages in folder\n"
     "\n"
     "Get a tuple of MessageInfos for the folder."},
    {NULL}
};

static PyMemberDef Folder_members[] = {
  {"name", T_OBJECT_EX, offsetof(clawsmail_FolderObject, name), 0,
   "name - name of folder"},
  
  {"path", T_OBJECT_EX, offsetof(clawsmail_FolderObject, path), 0,
   "path - path of folder"},
  
  {"mailbox_name", T_OBJECT_EX, offsetof(clawsmail_FolderObject, mailbox_name), 0,
   "mailbox_name - name of the corresponding mailbox"},

  {NULL}
};

static PyTypeObject clawsmail_FolderType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /* ob_size*/
    "clawsmail.Folder",        /* tp_name*/
    sizeof(clawsmail_FolderObject), /* tp_basicsize*/
    0,                         /* tp_itemsize*/
    (destructor)Folder_dealloc, /* tp_dealloc*/
    0,                         /* tp_print*/
    0,                         /* tp_getattr*/
    0,                         /* tp_setattr*/
    0,                         /* tp_compare*/
    0,                         /* tp_repr*/
    0,                         /* tp_as_number*/
    0,                         /* tp_as_sequence*/
    0,                         /* tp_as_mapping*/
    0,                         /* tp_hash */
    0,                         /* tp_call*/
    Folder_str,                /* tp_str*/
    0,                         /* tp_getattro*/
    0,                         /* tp_setattro*/
    0,                         /* tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,        /* tp_flags*/
    "Folder objects.\n\n"      /* tp_doc */
    "The __init__ function takes two optional arguments:\n"
    "folder = Folder(identifier, [create_if_not_existing=False])\n"
    "The identifier is an id string (e.g. '#mh/Mail/foo/bar'),"
    "create_if_not_existing is a boolean expression.",
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Folder_methods,            /* tp_methods */
    Folder_members,            /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Folder_init,     /* tp_init */
    0,                         /* tp_alloc */
    0,                         /* tp_new */
};

PyMODINIT_FUNC initfolder(PyObject *module)
{
    clawsmail_FolderType.tp_new = PyType_GenericNew;
    if(PyType_Ready(&clawsmail_FolderType) < 0)
        return;

    Py_INCREF(&clawsmail_FolderType);
    PyModule_AddObject(module, "Folder", (PyObject*)&clawsmail_FolderType);
}

PyObject* clawsmail_folder_new(FolderItem *folderitem)
{
  clawsmail_FolderObject *ff;
  PyObject *arglist;
  gchar *id;

  if(!folderitem)
    return NULL;

  id = folder_item_get_identifier(folderitem);
  arglist = Py_BuildValue("(s)", id);
  g_free(id);
  ff = (clawsmail_FolderObject*) PyObject_CallObject((PyObject*) &clawsmail_FolderType, arglist);
  Py_DECREF(arglist);
  return (PyObject*)ff;
}

FolderItem* clawsmail_folder_get_item(PyObject *self)
{
  return ((clawsmail_FolderObject*)self)->folderitem;
}

PyTypeObject* clawsmail_folder_get_type_object()
{
  return &clawsmail_FolderType;
}

gboolean clawsmail_folder_check(PyObject *self)
{
  return (PyObject_TypeCheck(self, &clawsmail_FolderType) != 0);
}



More information about the Commits mailing list