[eog-plugins] Add Postasa plugin allowing uploads to PicasaWeb
- From: Felix Riemann <friemann src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [eog-plugins] Add Postasa plugin allowing uploads to PicasaWeb
- Date: Wed, 10 Feb 2010 19:23:40 +0000 (UTC)
commit 6fb64e0ae158c3a580376044610e4e4312e488fe
Author: Richard Schwarting <aquarichy gmail com>
Date: Wed Feb 10 19:16:14 2010 +0100
Add Postasa plugin allowing uploads to PicasaWeb
This new plugin supports uploading images to PicasaWeb using libgdata.
It requires the yet unreleased libgdata-0.6.0, but it should be out in
time. See bug #600190.
configure.ac | 26 +-
plugins/postasa/Makefile.am | 43 ++
plugins/postasa/eog-postasa-plugin.c | 824 +++++++++++++++++++++++++
plugins/postasa/eog-postasa-plugin.h | 78 +++
plugins/postasa/postasa-config.xml | 155 +++++
plugins/postasa/postasa-uploads.xml | 171 +++++
plugins/postasa/postasa.eog-plugin.desktop.in | 9 +
7 files changed, 1302 insertions(+), 4 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 146937e..a1b7415 100644
--- a/configure.ac
+++ b/configure.ac
@@ -60,9 +60,9 @@ EOG_HAS_PYTHON=1
AC_MSG_CHECKING([which plugins to build])
-ALL_PLUGINS="postr map fit-to-width exif-display send-by-mail"
-USEFUL_PLUGINS="postr map fit-to-width exif-display send-by-mail"
-DEFAULT_PLUGINS="postr map fit-to-width exif-display send-by-mail"
+ALL_PLUGINS="postr map fit-to-width exif-display send-by-mail postasa"
+USEFUL_PLUGINS="postr map fit-to-width exif-display send-by-mail postasa"
+DEFAULT_PLUGINS="postr map fit-to-width exif-display send-by-mail postasa"
PYTHON_ALL_PLUGINS="slideshowshuffle pythonconsole"
PYTHON_USEFUL_PLUGINS="slideshowshuffle pythonconsole"
@@ -82,7 +82,7 @@ AC_ARG_WITH([plugins],
[AS_HELP_STRING([--with-plugins=LIST],
[Comma-separated list specifying the plugins to build.
Available: map, exif-display, fit-to-width,
- postr, pythonconsole, slideshowshuffle,
+ postr, postasa, pythonconsole, slideshowshuffle,
as well as the aliases default,
all, and really-all])],
[plugins=$with_plugins],
@@ -289,6 +289,23 @@ then
fi
fi
+# *******
+# Postasa
+# *******
+plugin_defined postasa
+if test "$?" = 1
+then
+ PKG_CHECK_MODULES(POSTASA,
+ [ libgdata >= 0.6.0 ],
+ [],[have_postasa=no])
+ AC_SUBST(POSTASA_LIBS)
+ AC_SUBST(POSTASA_CFLAGS)
+ if test "x$have_postasa" = "xno"
+ then
+ undef_plugin postasa "libgdata was not found"
+ fi
+fi
+
# ***
# Map
# ***
@@ -356,6 +373,7 @@ plugins/exif-display/Makefile
plugins/send-by-mail/Makefile
plugins/slideshowshuffle/Makefile
plugins/postr/Makefile
+plugins/postasa/Makefile
plugins/pythonconsole/Makefile
po/Makefile.in])
diff --git a/plugins/postasa/Makefile.am b/plugins/postasa/Makefile.am
new file mode 100644
index 0000000..657a75a
--- /dev/null
+++ b/plugins/postasa/Makefile.am
@@ -0,0 +1,43 @@
+# Postasa plugin
+plugindir = $(libdir)/eog/plugins
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(EOG_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(POSTASA_CFLAGS) \
+ -DEOG_LOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
+ -DEOG_PLUGINDIR=\""$(plugindir)"\"
+
+plugin_LTLIBRARIES = libpostasa.la
+
+libpostasa_la_SOURCES = \
+ eog-postasa-plugin.h \
+ eog-postasa-plugin.c
+
+libpostasa_la_LDFLAGS = \
+ $(PLUGIN_LIBTOOL_FLAGS) \
+ $(EOG_LIBS)
+
+libpostasa_la_LIBADD = $(EOG_LIBS) \
+ $(POSTASA_LIBS)
+
+
+# Plugin Info
+
+plugin_in_files = postasa.eog-plugin.desktop.in
+
+UI_FILES = postasa-config.xml postasa-uploads.xml default.png
+ui_DATA = $(UI_FILES)
+uidir = $(plugindir)/postasa
+
+%.eog-plugin: %.eog-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(AM_V_GEN)$(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.eog-plugin.desktop.in=.eog-plugin)
+
+EXTRA_DIST = $(plugin_in_files) $(UI_FILES)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/postasa/eog-postasa-plugin.c b/plugins/postasa/eog-postasa-plugin.c
new file mode 100644
index 0000000..f7b646e
--- /dev/null
+++ b/plugins/postasa/eog-postasa-plugin.c
@@ -0,0 +1,824 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Postasa -- PicasaWeb Uploader plugin
+ *
+ * Copyright (C) 2009 The Free Software Foundation
+ *
+ * Author: Richard Schwarting <aquarichy gmail com>
+ * Initially based on Postr code by: Lucas Rocha
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "eog-postasa-plugin.h"
+
+#include <gmodule.h>
+#include <glib/gi18n-lib.h>
+
+#include <eog/eog-debug.h>
+#include <eog/eog-thumb-view.h>
+#include <eog/eog-image.h>
+
+#include <gdata/gdata.h>
+
+#define WINDOW_DATA_KEY "EogPostasaWindowData"
+#define MENU_PATH "/MainMenu/ToolsMenu/ToolsOps_2"
+
+#define GTKBUILDER_CONFIG_FILE EOG_PLUGINDIR"/postasa/postasa-config.xml"
+#define GTKBUILDER_UPLOAD_FILE EOG_PLUGINDIR"/postasa/postasa-uploads.xml"
+#define DEFAULT_THUMBNAIL EOG_PLUGINDIR"/postasa/default.png"
+
+EOG_PLUGIN_REGISTER_TYPE(EogPostasaPlugin, eog_postasa_plugin)
+
+/**
+ * _EogPostasaPluginPrivate:
+ *
+ * Private data for the Postasa EoG plugin
+ **/
+struct _EogPostasaPluginPrivate
+{
+ EogWindow *eog_window;
+ GDataPicasaWebService *service;
+ GCancellable *login_cancellable;
+
+ /* TODO: consider using GConf to store the username in; perhaps not the password */
+ GtkDialog *login_dialog;
+ GtkEntry *username_entry;
+ GtkEntry *password_entry;
+ GtkLabel *login_message;
+ GtkButton *login_button;
+ GtkButton *cancel_button;
+ gboolean uploads_pending;
+
+ GtkWindow *uploads_window;
+ GtkTreeView *uploads_view;
+ GtkListStore *uploads_store;
+};
+
+/**
+ * WindowData:
+ *
+ * Data we'll associate with the window, describing the action group
+ * as well as the plugin, so we'll be able to access it even from the
+ * #GtkActionEntry's callback.
+ **/
+typedef struct
+{
+ GtkActionGroup *ui_action_group;
+ guint ui_id;
+ EogPostasaPlugin *plugin;
+} WindowData;
+
+/**
+ * PicasaWebUploadFileAsyncData:
+ *
+ * Small chunk of data for use by our asynchronous PicasaWeb upload
+ * API. It describes the position in the upload window's tree view, for
+ * cancellation purposes, and the the image file that we want to
+ * upload.
+ *
+ * TODO: remove this and the async API when we get a proper upload
+ * async method into libgdata.
+ **/
+typedef struct
+{
+ GtkTreeIter *iter;
+ GFile *imgfile;
+} PicasaWebUploadFileAsyncData;
+
+static void picasaweb_upload_cb (GtkAction *action, EogWindow *window);
+static GtkWidget *login_get_dialog (EogPostasaPlugin *plugin);
+static gboolean login_dialog_close (EogPostasaPlugin *plugin);
+
+/**
+ * action_entries:
+ *
+ * Describes the #GtkActionEntry representing the Postasa upload menu
+ * item and it's callback.
+ **/
+static const GtkActionEntry action_entries[] =
+{
+ { "RunPostasa",
+ "postasa",
+ N_("Upload to PicasaWeb"),
+ NULL,
+ N_("Upload your pictures to PicasaWeb"),
+ G_CALLBACK (picasaweb_upload_cb) }
+};
+
+/*** Uploads Dialog ***/
+
+/**
+ * uploads_cancel_row:
+ *
+ * Function on the upload list store to cancel a specified upload.
+ * This is intended to be called by foreach functions.
+ *
+ * There's a small chance that the #GCancellable cancellation might not
+ * occur before the upload is completed, in which case we do not
+ * change the row's status to "Cancelled".
+ **/
+static gboolean
+uploads_cancel_row (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, EogPostasaPlugin *plugin)
+{
+ GCancellable *cancellable;
+ gint status;
+
+ gtk_tree_model_get (model, iter, 4, &cancellable, -1);
+ g_cancellable_cancel (cancellable);
+ /* we let picasaweb_upload_async_cb() handle setting a Cancelled message */
+
+ return FALSE;
+}
+
+/**
+ * uploads_cancel_cb:
+ *
+ * Obtains the current selection and calls an API to cancel each one.
+ **/
+static void
+uploads_cancel_cb (GtkWidget *cancel_button, EogPostasaPlugin *plugin)
+{
+ GtkTreeSelection *selection;
+ selection = gtk_tree_view_get_selection (plugin->priv->uploads_view); /* don't unref */
+
+ gtk_tree_selection_selected_foreach (selection, (GtkTreeSelectionForeachFunc)uploads_cancel_row, plugin);
+}
+
+/**
+ * uploads_cancel_all_cb:
+ *
+ * Calls an API on every row to cancel its upload.
+ **/
+static void
+uploads_cancel_all_cb (GtkWidget *cancel_all_button, EogPostasaPlugin *plugin)
+{
+ gtk_tree_model_foreach (GTK_TREE_MODEL (plugin->priv->uploads_store), (GtkTreeModelForeachFunc)uploads_cancel_row, plugin);
+}
+
+/**
+ * uploads_get_dialog:
+ *
+ * Returns the a #GtkWindow representing the Uploads window. If it
+ * has not already been created, it creates it. The Uploads window is
+ * set to be hidden instead of destroyed when closed, to avoid having
+ * to recreate it and re-parse the UI file, etc.
+ **/
+static GtkWindow *
+uploads_get_dialog (EogPostasaPlugin *plugin)
+{
+ GtkBuilder *builder;
+ GError *error = NULL;
+ GtkButton *cancel_button;
+ GtkButton *cancel_all_button;
+
+ if (plugin->priv->uploads_window == NULL) {
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_file (builder, GTKBUILDER_UPLOAD_FILE, &error);
+ if (error != NULL) {
+ g_warning ("Couldn't load Postasa uploads UI file:%d:%s", error->code, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ /* note: do not unref gtk_builder_get_object() returns */
+ plugin->priv->uploads_window = GTK_WINDOW (gtk_builder_get_object (builder, "uploads_window"));
+ plugin->priv->uploads_view = GTK_TREE_VIEW (gtk_builder_get_object (builder, "uploads_view"));
+ plugin->priv->uploads_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "uploads_store"));
+
+ cancel_button = GTK_BUTTON (gtk_builder_get_object (builder, "cancel_button"));
+ cancel_all_button = GTK_BUTTON (gtk_builder_get_object (builder, "cancel_all_button"));
+
+ /* TODO: can't set expand = TRUE when packing cells into columns via glade-3/GtkBuilder apparently?
+ bgo #602152 So for now, we take them, clear them out, and remap them. Ugh. Better solutions welcome. */
+ GtkTreeViewColumn *file_col = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "file_col"));
+ GtkCellRenderer *thumbnail_cell = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "thumbnail_cell"));
+ GtkCellRenderer *filepath_cell = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "filepath_cell"));
+ gtk_tree_view_column_clear (file_col);
+ gtk_tree_view_column_pack_start (file_col, thumbnail_cell, FALSE);
+ gtk_tree_view_column_pack_end (file_col, filepath_cell, TRUE);
+ gtk_tree_view_column_add_attribute (file_col, thumbnail_cell, "pixbuf", 0);
+ gtk_tree_view_column_add_attribute (file_col, filepath_cell, "text", 1);
+ GtkTreeViewColumn *progress_col = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "progress_col"));
+ GtkCellRenderer *progress_cell = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "progress_cell"));
+ gtk_tree_view_column_clear (progress_col);
+ gtk_tree_view_column_pack_end (progress_col, progress_cell, TRUE);
+ gtk_tree_view_column_add_attribute (progress_col, progress_cell, "pulse", 3);
+ gtk_tree_view_column_add_attribute (progress_col, progress_cell, "text", 5);
+
+ g_object_unref (builder);
+
+ g_signal_connect (G_OBJECT (cancel_button), "clicked", G_CALLBACK (uploads_cancel_cb), plugin);
+ g_signal_connect (G_OBJECT (cancel_all_button), "clicked", G_CALLBACK (uploads_cancel_all_cb), plugin);
+ g_signal_connect (G_OBJECT (plugin->priv->uploads_window), "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), plugin);
+ }
+
+ return plugin->priv->uploads_window;
+}
+
+typedef struct {
+ EogPostasaPlugin *plugin;
+ GtkTreeIter iter;
+} PulseData;
+
+static gboolean
+pulse (PulseData *data)
+{
+ gint status;
+ GCancellable *cancellable;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (data->plugin->priv->uploads_store), &(data->iter), 3, &status, 4, &cancellable, -1);
+
+ if (0 <= status && status < G_MAXINT && g_cancellable_is_cancelled (cancellable) == FALSE) {
+ /* TODO: consider potential for races and how g_timeout_add works wrt threading; none seen in testing, though */
+ gtk_list_store_set (data->plugin->priv->uploads_store, &(data->iter), 3, status+1, -1);
+ return TRUE;
+ } else {
+ /* either we've failed, <0, or we're done, G_MAX_INT */
+ g_slice_free (PulseData, data);
+ return FALSE;
+ }
+}
+
+/**
+ * uploads_add_entry:
+ *
+ * Adds a new row to the Uploads tree view for an #EogImage to upload.
+ * The row stores the upload's #GCancellable and returns.
+ *
+ * Returns: a #GtkTreeIter that should be freed with g_slice_free()
+ **/
+static GtkTreeIter *
+uploads_add_entry (EogPostasaPlugin *plugin, EogImage *image, GCancellable *cancellable)
+{
+ GtkWindow *uploads_window;
+ GdkPixbuf *thumbnail_pixbuf;
+ GdkPixbuf *scaled_pixbuf;
+ gchar *size, *uri;
+ GtkTreeIter *iter;
+ GError *error = NULL;
+
+ /* display the Uploads window got from the plugin */
+ uploads_window = uploads_get_dialog (plugin);
+ gtk_widget_show_all (GTK_WIDGET (uploads_window));
+
+ /* obtain the data describing the upload */
+ /* TODO: submit patch with documentaiton for eog_image_get_*,
+ particularly what needs unrefing */
+ uri = eog_image_get_uri_for_display (image);
+ thumbnail_pixbuf = eog_image_get_thumbnail (image);
+ if (GDK_IS_PIXBUF (thumbnail_pixbuf)) {
+ scaled_pixbuf = gdk_pixbuf_scale_simple (thumbnail_pixbuf, 32, 32, GDK_INTERP_BILINEAR);
+ g_object_unref (thumbnail_pixbuf);
+ } else {
+ scaled_pixbuf = gdk_pixbuf_new_from_file (DEFAULT_THUMBNAIL, &error);
+ }
+ size = g_strdup_printf ("%luKB", eog_image_get_bytes (image) / 1024);
+ iter = g_slice_new0 (GtkTreeIter);
+
+ /* insert the data into the upload's list store */
+ gtk_list_store_insert_with_values (plugin->priv->uploads_store, iter, 0,
+ 0, scaled_pixbuf,
+ 1, uri,
+ 2, size,
+ 3, 50, /* upload status: set to G_MAXINT when done, to 0 to not start */
+ 4, cancellable,
+ 5, N_("Uploading..."),
+ -1); /* TODO: where should cancellabe, scaled_pixbuf be unref'd? don't worry about it since
+ they'll exist until EoG exits anyway? or in eog_postasa_plugin_finalize()? */
+ g_free (uri);
+ g_free (size);
+ g_object_unref (scaled_pixbuf);
+
+ /* Set the progress bar to pulse every 50ms; freed in pulse() when upload is no longer in progress */
+ PulseData *data; /* just needs to be freed with g_slice_free() */
+ data = g_slice_new0 (PulseData);
+ data->plugin = plugin;
+ data->iter = *iter;
+ g_timeout_add (50, (GSourceFunc)pulse, data);
+
+ return iter;
+}
+
+static void
+free_picasaweb_upload_file_async_data (PicasaWebUploadFileAsyncData *data)
+{
+ g_object_unref (data->imgfile);
+ g_slice_free (GtkTreeIter, data->iter);
+ g_slice_free (PicasaWebUploadFileAsyncData, data);
+}
+
+/**
+ * picasaweb_upload_async_cb:
+ *
+ * Handles completion of the image's asynchronous upload to PicasaWeb.
+ *
+ * If the #GAsyncResults indicates success, we'll update the
+ * treeview's row for the given upload indicating this. Elsewise, if
+ * it wasn't cancelled, then we report an error.
+ *
+ * NOTE: we also clean up the #PicasaWebUploadFileAsyncData here.
+ *
+ * TODO: we don't yet make the progress bar throb, how do we do that?
+ *
+ **/
+static void
+picasaweb_upload_async_cb (EogPostasaPlugin *plugin, GAsyncResult *res, PicasaWebUploadFileAsyncData *data)
+{
+ GCancellable* cancellable;
+ gchar *filename;
+ GError *error = NULL; /* TODO: make sure to clear all set errors */
+
+ if (g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res)) == TRUE) {
+ gtk_list_store_set (plugin->priv->uploads_store, data->iter, 3, G_MAXINT, 5, N_("Uploaded"), -1);
+ } else {
+ gtk_tree_model_get (GTK_TREE_MODEL (plugin->priv->uploads_store), data->iter, 4, &cancellable, -1);
+ if (g_cancellable_is_cancelled (cancellable) == TRUE) {
+ gtk_list_store_set (plugin->priv->uploads_store, data->iter, 3, -1, 5, N_("Cancelled"), -1);
+ } else {
+ g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), &error);
+ gtk_list_store_set (plugin->priv->uploads_store, data->iter, 3, -1, 5, error ? error->message : N_("Failed"), -1);
+ g_clear_error (&error);
+ }
+ }
+
+ free_picasaweb_upload_file_async_data (data);
+}
+
+/*** PicasaWeb ***/
+
+/**
+ * tmp_picasaweb_upload_async:
+ *
+ * Temporary solution to provide asynchronous uploading and stop
+ * blocking the UI until gdata_picasaweb_service_upload_file_async()
+ * becomes available (bgo #600262). This method does the synchronous
+ * file upload, but is run asynchronously via
+ * g_simple_async_result_run_in_thread().
+ *
+ * This sets up a minimal #GDataPicasaWebFile entry, using the
+ * basename of the filepath for the file's title (which is not the
+ * caption, but might be something we would consider doing). The
+ * image file and the minimal entry are then uploaded to PicasaWeb's
+ * default album of "Drop Box". In the future, we might consider
+ * adding an Album Chooser to the Preferences/login window, but only
+ * if there's demand.
+ **/
+static void
+tmp_picasaweb_upload_async (GSimpleAsyncResult *result, GObject *source_object, GCancellable *cancellable)
+{
+ GDataPicasaWebFile *new_file = NULL;
+ EogPostasaPlugin *plugin = EOG_POSTASA_PLUGIN (source_object);
+ GDataPicasaWebFile *file_entry;
+ PicasaWebUploadFileAsyncData *data;
+ gchar *filepath;
+ gchar *filename;
+ GError *error = NULL;
+
+ data = (PicasaWebUploadFileAsyncData*)g_async_result_get_user_data (G_ASYNC_RESULT (result));
+
+ /* get filename to set image title */
+ file_entry = gdata_picasaweb_file_new (NULL);
+ filename = g_file_get_basename (data->imgfile);
+ gdata_entry_set_title (GDATA_ENTRY (file_entry), filename);
+ g_free (filename);
+
+ new_file = gdata_picasaweb_service_upload_file (plugin->priv->service, NULL /* Uploading to Drop Box */, file_entry,
+ data->imgfile, cancellable, &error);
+ g_object_unref (file_entry);
+
+ if (new_file == NULL || error) {
+ if (g_cancellable_is_cancelled (cancellable) == FALSE) {
+ g_simple_async_result_set_from_error (result, error);
+ g_clear_error (&error); /* we can clear this, because set_from_error() does a g_error_copy() */
+ }
+ } else {
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ }
+
+ if (new_file != NULL)
+ g_object_unref (new_file);
+}
+
+
+/**
+ * picasaweb_upload_files:
+ *
+ * This obtains the list of selected images in EoG (selected in the
+ * thumbview), sets up asynchronous uploads through
+ * tmp_picasaweb_upload_async() in their own thread and instigates
+ * them.
+
+ * This attempts to upload the selected files. It provides a message
+ * near the end indicating the number successfully uploaded, and any
+ * error messages encountered along the way.
+ *
+ * TODO: once gdata_picasaweb_service_upload_file_async() is available
+ * from libgdata, simplify this as possible.
+ **/
+static void
+picasaweb_upload_files (EogPostasaPlugin *plugin)
+{
+ EogWindow *window;
+ GtkWidget *thumbview;
+ GList *images, *node;
+ EogImage *image;
+ GFile *imgfile;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+ PicasaWebUploadFileAsyncData *data;
+
+ if (gdata_service_is_authenticated (GDATA_SERVICE (plugin->priv->service)) == FALSE) {
+ g_warning ("PicasaWeb could not be authenticated. Aborting upload.");
+ return;
+ }
+
+ window = plugin->priv->eog_window;
+ thumbview = eog_window_get_thumb_view (window); /* do not unref */
+ images = eog_thumb_view_get_selected_images (EOG_THUMB_VIEW (thumbview)); /* need to use g_list_free() */
+
+ for (node = g_list_first (images); node != NULL; node = node->next) {
+ image = (EogImage *) node->data;
+ cancellable = g_cancellable_new (); /* TODO: this gets attached to the image's list row; free with row */
+
+ imgfile = eog_image_get_file (image); /* unref this */
+
+ data = g_slice_new0(PicasaWebUploadFileAsyncData); /* freed by picasaweb_upload_async_cb() or below */
+ data->imgfile = g_file_dup (imgfile); /* unref'd in free_picasaweb_upload_file_async_data() */
+ data->iter = uploads_add_entry (plugin, image, cancellable); /* freed with data */
+
+ if (g_file_query_exists (imgfile, cancellable)) {
+ /* TODO: want to replace much of this with gdata_picasaweb_service_upload_file_async when that's avail */
+ result = g_simple_async_result_new (G_OBJECT (plugin), (GAsyncReadyCallback)picasaweb_upload_async_cb,
+ data, tmp_picasaweb_upload_async); /* TODO: should this be freed? where? */
+ g_simple_async_result_run_in_thread (result, tmp_picasaweb_upload_async, 0, cancellable);
+ } else {
+ /* TODO: consider setting a proper error and passing it in the data through GSimpleAsyncResult's thread */
+ gtk_list_store_set (plugin->priv->uploads_store, data->iter, 3, -1, 5, "File not found", -1);
+ free_picasaweb_upload_file_async_data (data);
+ }
+ g_object_unref (imgfile);
+ }
+ g_list_free (images);
+}
+
+/**
+ * picasaweb_login_async_cb:
+ *
+ * Handles the result of the asynchronous
+ * gdata_service_authenticate_async() operation, called by our
+ * picasaweb_login_cb(). Upon success, it switches "Cancel" to
+ * "Close". Regardless of the response, it re-enables the Login
+ * button (which is desensitised during the login attempt).
+ **/
+static void
+picasaweb_login_async_cb (GDataPicasaWebService *service, GAsyncResult *result, EogPostasaPlugin *plugin)
+{
+ GError *error = NULL;
+ gchar *message;
+ gboolean success = FALSE;
+
+ success = gdata_service_authenticate_finish (GDATA_SERVICE (service), result, &error);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (plugin->priv->login_button), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (plugin->priv->username_entry), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (plugin->priv->password_entry), TRUE);
+
+ if (success == FALSE || error != NULL) {
+ message = g_strdup_printf (N_("Login failed. %s"), error->message);
+ gtk_label_set_text (plugin->priv->login_message, message);
+ g_free (message);
+ } else {
+ gtk_label_set_text (plugin->priv->login_message, N_("Logged in successully."));
+ gtk_button_set_label (plugin->priv->cancel_button, N_("Close"));
+ login_dialog_close (plugin);
+ }
+}
+
+/**
+ * picasaweb_login_cb:
+ *
+ * Handles "clicked" for the Login button. Attempts to use input from
+ * the username and password entries to authenticate. It does this
+ * asynchronously, leaving it to @picasaweb_login_async_cb to handle
+ * the result. It disables the Login button while authenticating
+ * (re-enabled when done) and ensures the Close/Cancel button says
+ * Cancel (which, incidentally, will call g_cancellable_cancel on the
+ * provided #GCancellable.
+ **/
+static void
+picasaweb_login_cb (GtkWidget *login_button, gpointer _plugin)
+{
+ EogPostasaPlugin *plugin = EOG_POSTASA_PLUGIN (_plugin);
+
+ gtk_button_set_label (plugin->priv->cancel_button, N_("Cancel"));
+ gtk_widget_set_sensitive (login_button, FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (plugin->priv->username_entry), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (plugin->priv->password_entry), FALSE);
+
+ /* TODO: want to handle passwords more securely */
+ gtk_label_set_text (plugin->priv->login_message, N_("Logging in..."));
+ g_cancellable_reset (plugin->priv->login_cancellable);
+ gdata_service_authenticate_async (GDATA_SERVICE (plugin->priv->service),
+ gtk_entry_get_text (plugin->priv->username_entry),
+ gtk_entry_get_text (plugin->priv->password_entry),
+ plugin->priv->login_cancellable, (GAsyncReadyCallback)picasaweb_login_async_cb, plugin);
+}
+
+/**
+ * picasaweb_upload_cb:
+ *
+ * This checks that we are authenticated (popping up the login window
+ * if we're not) and, if we are, moves on to upload the files.
+ **/
+static void
+picasaweb_upload_cb (GtkAction *action,
+ EogWindow *window)
+{
+ WindowData *data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ EogPostasaPlugin *plugin = data->plugin;
+
+ if (gdata_service_is_authenticated (GDATA_SERVICE (plugin->priv->service)) == TRUE) {
+ picasaweb_upload_files (plugin);
+ } else {
+ /* when the dialog closes, it checks if this is set to see if it should upload anything */
+ plugin->priv->uploads_pending = TRUE;
+
+ login_get_dialog (plugin);
+ gtk_label_set_text (plugin->priv->login_message, N_("Please log in to continue upload."));
+ gtk_window_present (GTK_WINDOW (plugin->priv->login_dialog));
+ }
+}
+
+
+/*** Login Dialog ***/
+
+/**
+ * login_dialog_close:
+ *
+ * Closes the login dialog, used for closing the window or cancelling
+ * login. This will also cancel any authentication in progress. If
+ * the login dialog was prompted by an upload attempt, it will resume
+ * the upload attempt.
+ **/
+static gboolean
+login_dialog_close (EogPostasaPlugin *plugin)
+{
+ /* abort the authentication attempt if in progress and we're cancelling */
+ g_cancellable_cancel (plugin->priv->login_cancellable);
+ gtk_widget_hide (GTK_WIDGET (plugin->priv->login_dialog));
+
+ if (plugin->priv->uploads_pending == TRUE) {
+ plugin->priv->uploads_pending = FALSE;
+ picasaweb_upload_files (plugin);
+ }
+
+ return TRUE;
+}
+
+/**
+ * login_dialog_cancel_button_cb:
+ *
+ * Handles clicks on the Cancel/Close button.
+ **/
+static gboolean
+login_dialog_cancel_button_cb (GtkWidget *cancel_button, EogPostasaPlugin *plugin)
+{
+ return login_dialog_close (plugin);
+}
+
+/**
+ * login_dialog_delete_event_cb:
+ *
+ * Handles other attempts to close the dialog. (e.g. window manager)
+ **/
+static gboolean
+login_dialog_delete_event_cb (GtkWidget *widget, GdkEvent *event, gpointer *_plugin)
+{
+ return login_dialog_close (EOG_POSTASA_PLUGIN (_plugin));
+}
+
+/**
+ * login_get_dialog:
+ *
+ * Retrieves the login dialog. If it has not yet been constructed, it
+ * does so. If the user is already authenticated, it populates the
+ * username and password boxes with the relevant values.
+ **/
+static GtkWidget *
+login_get_dialog (EogPostasaPlugin *plugin)
+{
+ GtkBuilder *builder;
+ GError *error = NULL;
+
+ if (plugin->priv->login_dialog == NULL) {
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_file (builder, GTKBUILDER_CONFIG_FILE, &error);
+ if (error != NULL) {
+ g_warning ("Couldn't load Postasa configuration UI file:%d:%s", error->code, error->message);
+ g_error_free (error);
+ }
+
+ /* do not unref gtk_builder_get_object() returns */
+ plugin->priv->username_entry = GTK_ENTRY (gtk_builder_get_object (builder, "username_entry"));
+ plugin->priv->password_entry = GTK_ENTRY (gtk_builder_get_object (builder, "password_entry"));
+ plugin->priv->login_dialog = GTK_DIALOG (gtk_builder_get_object (builder, "postasa_login_dialog"));
+ plugin->priv->cancel_button = GTK_BUTTON (gtk_builder_get_object (builder, "cancel_button"));
+ plugin->priv->login_button = GTK_BUTTON (gtk_builder_get_object (builder, "login_button"));
+ plugin->priv->login_message = GTK_LABEL (gtk_builder_get_object (builder, "login_message"));
+
+ g_object_unref (builder);
+
+ g_signal_connect (G_OBJECT (plugin->priv->login_button), "clicked", G_CALLBACK (picasaweb_login_cb), plugin);
+ g_signal_connect (G_OBJECT (plugin->priv->cancel_button), "clicked", G_CALLBACK (login_dialog_cancel_button_cb), plugin);
+ g_signal_connect (G_OBJECT (plugin->priv->login_dialog), "delete-event", G_CALLBACK (login_dialog_delete_event_cb), plugin);
+
+ if (gdata_service_is_authenticated (GDATA_SERVICE (plugin->priv->service))) {
+ gtk_entry_set_text (plugin->priv->username_entry, gdata_service_get_username (GDATA_SERVICE (plugin->priv->service)));
+ gtk_entry_set_text (plugin->priv->password_entry, gdata_service_get_password (GDATA_SERVICE (plugin->priv->service)));
+ }
+ }
+
+ return GTK_WIDGET (plugin->priv->login_dialog);
+}
+
+
+/*** EogPlugin Functions ***/
+
+/**
+ * free_window_data:
+ *
+ * This handles destruction of the #WindowData we define for this plugin.
+ **/
+static void
+free_window_data (WindowData *data)
+{
+ eog_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (data != NULL);
+ g_object_unref (data->ui_action_group);
+ g_free (data);
+}
+
+/**
+ * impl_create_config_dialog:
+ *
+ * Plugin hook for obtaining the configure/preferences dialog. In
+ * this case, it's just our Login dialog. In the future, it might
+ * include an album chooser, if there's demand.
+ **/
+static GtkWidget *
+impl_create_config_dialog (EogPlugin *_plugin)
+{
+ EogPostasaPlugin *plugin = EOG_POSTASA_PLUGIN (_plugin);
+ return GTK_WIDGET (login_get_dialog (plugin));
+}
+
+/**
+ * impl_activate:
+ *
+ * Plugin hook for plugin activation. Creates #WindowData for the
+ * #EogPostasaPlugin that gets associated with the window and defines
+ * some UI.
+ **/
+static void
+impl_activate (EogPlugin *_plugin,
+ EogWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+ EogPostasaPlugin *plugin = EOG_POSTASA_PLUGIN (_plugin);
+
+ eog_debug (DEBUG_PLUGINS);
+
+ plugin->priv->eog_window = window;
+
+ data = g_new (WindowData, 1); /* free'd by free_window_data() when window object is destroyed */
+ data->plugin = EOG_POSTASA_PLUGIN (plugin); /* circular references, fun */
+ data->ui_action_group = gtk_action_group_new ("EogPostasaPluginActions"); /* freed with WindowData in free_window_data() */
+ gtk_action_group_set_translation_domain (data->ui_action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (data->ui_action_group, action_entries, G_N_ELEMENTS (action_entries), window);
+
+ manager = eog_window_get_ui_manager (window); /* do not unref */
+ gtk_ui_manager_insert_action_group (manager, data->ui_action_group, -1);
+ data->ui_id = gtk_ui_manager_new_merge_id (manager);
+ gtk_ui_manager_add_ui (manager, data->ui_id, MENU_PATH, "RunPostasa", "RunPostasa", GTK_UI_MANAGER_MENUITEM, FALSE);
+
+ g_object_set_data_full (G_OBJECT (window), WINDOW_DATA_KEY, data, (GDestroyNotify) free_window_data);
+}
+
+/**
+ * impl_deactivate:
+ *
+ * Plugin hook for plugin deactivation. Removes UI and #WindowData
+ **/
+static void
+impl_deactivate (EogPlugin *plugin,
+ EogWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+
+ eog_debug (DEBUG_PLUGINS);
+
+ manager = eog_window_get_ui_manager (window);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ gtk_ui_manager_remove_ui (manager, data->ui_id);
+ gtk_ui_manager_remove_action_group (manager, data->ui_action_group);
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); /* TODO: does doing this interfere with the GDestroyNotify
+ set in impl_activate()? It's how it was done in Postr... */
+}
+
+/**
+ * impl_update_ui:
+ *
+ * Plugin hook for updating the UI. I don't think we do anything of use in here.
+ **/
+static void
+impl_update_ui (EogPlugin *plugin,
+ EogWindow *window)
+{
+}
+
+
+/*** GObject Functions ***/
+
+/**
+ * eog_postasa_plugin_init:
+ *
+ * Object initialisation method. Sets up the (unauthenticated)
+ * PicasaWeb service, a #GCancellable for login, and sets the
+ * uploads_pending flag to %FALSE.
+ **/
+static void
+eog_postasa_plugin_init (EogPostasaPlugin *plugin)
+{
+ eog_debug_message (DEBUG_PLUGINS, "EogPostasaPlugin initializing");
+
+ plugin->priv = G_TYPE_INSTANCE_GET_PRIVATE (plugin, EOG_TYPE_POSTASA_PLUGIN, EogPostasaPluginPrivate);
+ plugin->priv->service = gdata_picasaweb_service_new ("EogPostasa"); /* unref'd in eog_postasa_plugin_finalize() */
+ plugin->priv->login_cancellable = g_cancellable_new (); /* unref'd in eog_postasa_plugin_finalize() */
+ plugin->priv->uploads_pending = FALSE;
+}
+
+/**
+ * eog_postasa_plugin_finalize:
+ *
+ * Cleans up the #EogPostasaPlugin object, unref'ing its #GDataPicasaWebService and #GCancellable.
+ **/
+static void
+eog_postasa_plugin_finalize (GObject *_plugin)
+{
+ EogPostasaPlugin *plugin = EOG_POSTASA_PLUGIN (_plugin);
+
+ eog_debug_message (DEBUG_PLUGINS, "EogPostasaPlugin finalizing");
+
+ g_object_unref (plugin->priv->service);
+ g_object_unref (plugin->priv->login_cancellable);
+ if (G_IS_OBJECT (plugin->priv->uploads_store))
+ /* we check in case the upload window was never created */
+ g_object_unref (plugin->priv->uploads_store);
+
+ G_OBJECT_CLASS (eog_postasa_plugin_parent_class)->finalize (_plugin);
+}
+
+/**
+ * eog_postasa_plugin_class_init:
+ *
+ * Plugin class initialisation. Binds class hooks to actual implementations.
+ **/
+static void
+eog_postasa_plugin_class_init (EogPostasaPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ EogPluginClass *plugin_class = EOG_PLUGIN_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (EogPostasaPluginPrivate));
+
+ object_class->finalize = eog_postasa_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_update_ui;
+ plugin_class->create_configure_dialog = impl_create_config_dialog;
+}
diff --git a/plugins/postasa/eog-postasa-plugin.h b/plugins/postasa/eog-postasa-plugin.h
new file mode 100644
index 0000000..06e81e5
--- /dev/null
+++ b/plugins/postasa/eog-postasa-plugin.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Postasa -- PicasaWeb Uploader plugin
+ *
+ * Copyright (C) 2009 The Free Software Foundation
+ *
+ * Author: Richard Schwarting <aquarichy gmail com>
+ * Initially based on Postr code by: Lucas Rocha
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_POSTASA_PLUGIN_H__
+#define __EOG_POSTASA_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <eog/eog-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define EOG_TYPE_POSTASA_PLUGIN (eog_postasa_plugin_get_type ())
+#define EOG_POSTASA_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_POSTASA_PLUGIN, EogPostasaPlugin))
+#define EOG_POSTASA_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_POSTASA_PLUGIN, EogPostasaPluginClass))
+#define EOG_IS_POSTASA_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_POSTASA_PLUGIN))
+#define EOG_IS_POSTASA_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_POSTASA_PLUGIN))
+#define EOG_POSTASA_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_POSTASA_PLUGIN, EogPostasaPluginClass))
+
+/* Private structure type */
+typedef struct _EogPostasaPluginPrivate EogPostasaPluginPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _EogPostasaPlugin EogPostasaPlugin;
+
+struct _EogPostasaPlugin
+{
+ EogPlugin parent_instance;
+ EogPostasaPluginPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _EogPostasaPluginClass EogPostasaPluginClass;
+
+struct _EogPostasaPluginClass
+{
+ EogPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType eog_postasa_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_eog_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __EOG_POSTASA_PLUGIN_H__ */
diff --git a/plugins/postasa/postasa-config.xml b/plugins/postasa/postasa-config.xml
new file mode 100644
index 0000000..4eee067
--- /dev/null
+++ b/plugins/postasa/postasa-config.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="postasa_login_dialog">
+ <property name="height_request">150</property>
+ <property name="border_width">5</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog_vbox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkVBox" id="login_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">True</property>
+ <property name="ypad">6</property>
+ <property name="label" translatable="yes">PicasaWeb Login</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="username_hbox">
+ <property name="width_request">300</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="username_label">
+ <property name="width_request">60</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Username</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="username_entry">
+ <property name="width_request">240</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="password_hbox">
+ <property name="width_request">300</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="width_request">60</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Password</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="width_request">240</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="login_message">
+ <property name="visible">True</property>
+ <property name="ypad">7</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="button_box">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="login_button">
+ <property name="label" translatable="yes">Login</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">cancel_button</action-widget>
+ <action-widget response="-1">login_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/postasa/postasa-uploads.xml b/plugins/postasa/postasa-uploads.xml
new file mode 100644
index 0000000..0c9db90
--- /dev/null
+++ b/plugins/postasa/postasa-uploads.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkListStore" id="uploads_store">
+ <columns>
+ <!-- column-name thumbnail -->
+ <column type="GdkPixbuf"/>
+ <!-- column-name filepath -->
+ <column type="gchararray"/>
+ <!-- column-name size -->
+ <column type="gchararray"/>
+ <!-- column-name upload_status -->
+ <column type="guint"/>
+ <!-- column-name cancellable -->
+ <column type="GObject"/>
+ <!-- column-name upload_message -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkWindow" id="uploads_window">
+ <property name="width_request">550</property>
+ <property name="height_request">250</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="uploads_label">
+ <property name="height_request">0</property>
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="ypad">10</property>
+ <property name="label" translatable="yes">Uploads:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="resize_mode">queue</property>
+ <child>
+ <object class="GtkTreeView" id="uploads_view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">uploads_store</property>
+ <child>
+ <object class="GtkTreeViewColumn" id="file_col">
+ <property name="title">File</property>
+ <property name="expand">True</property>
+ <child>
+ <object class="GtkCellRendererPixbuf" id="thumbnail_cell"/>
+ <attributes>
+ <attribute name="pixbuf">0</attribute>
+ </attributes>
+ </child>
+ <child>
+ <object class="GtkCellRendererText" id="filepath_cell">
+ <property name="ellipsize">start</property>
+ </object>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="size_col">
+ <property name="sizing">fixed</property>
+ <property name="fixed_width">60</property>
+ <property name="title">Size</property>
+ <child>
+ <object class="GtkCellRendererText" id="size_cell"/>
+ <attributes>
+ <attribute name="text">2</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="progress_col">
+ <property name="min_width">160</property>
+ <property name="title">Status</property>
+ <child>
+ <object class="GtkCellRendererProgress" id="progress_cell"/>
+ <attributes>
+ <attribute name="pulse">3</attribute>
+ <attribute name="text">5</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">10</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel Selected</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_all_button">
+ <property name="label" translatable="yes">Cancel All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">10</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">10</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/postasa/postasa.eog-plugin.desktop.in b/plugins/postasa/postasa.eog-plugin.desktop.in
new file mode 100644
index 0000000..51f1080
--- /dev/null
+++ b/plugins/postasa/postasa.eog-plugin.desktop.in
@@ -0,0 +1,9 @@
+[Eog Plugin]
+Module=postasa
+IAge=2
+_Name=PicasaWeb Uploader
+Icon=postasa
+_Description=Upload your pictures to PicasaWeb
+Authors=Richard Schwarting <aquarichy gmail com>
+Copyright=Copyright © 2009 Richard Schwarting
+Website=http://www.gnome.org/projects/eog
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]