[gnome-keyring] [gcr] Add 'Export Certificate' right click in cert widget.



commit 5f50b09b92d8cc29a73100c1242a1c1893d6559f
Author: Stef Walter <stefw collabora co uk>
Date:   Tue Mar 8 18:12:42 2011 +0100

    [gcr] Add 'Export Certificate' right click in cert widget.
    
    Users can right click on the certificate widget, and export the
    certificate to DER or PEM format.

 gcr/Makefile.am                 |    1 +
 gcr/gcr-certificate-exporter.c  |  566 +++++++++++++++++++++++++++++++++++++++
 gcr/gcr-certificate-exporter.h  |   73 +++++
 gcr/gcr-certificate-renderer.c  |   79 ++++++-
 gcr/gcr-display-view.c          |   68 +++++
 gcr/gcr-renderer.c              |    9 +
 gcr/gcr-renderer.h              |    6 +
 gcr/tests/ui-test-certificate.c |   23 +-
 8 files changed, 813 insertions(+), 12 deletions(-)
---
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 2201f85..568d36f 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -66,6 +66,7 @@ libgcr GCR_VERSION_SUFFIX@_la_SOURCES = \
 	gcr-certificate.c gcr-certificate.h \
 	gcr-certificate-chain.c gcr-certificate-chain.h \
 	gcr-certificate-renderer.c gcr-certificate-renderer.h \
+	gcr-certificate-exporter.c gcr-certificate-exporter.h \
 	gcr-certificate-widget.c gcr-certificate-widget.h \
 	gcr-display-scrolled.c gcr-display-scrolled.h \
 	gcr-display-view.c gcr-display-view.h \
diff --git a/gcr/gcr-certificate-exporter.c b/gcr/gcr-certificate-exporter.c
new file mode 100644
index 0000000..115b030
--- /dev/null
+++ b/gcr/gcr-certificate-exporter.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr-certificate.h"
+#include "gcr-certificate-exporter.h"
+
+#include "egg/egg-openssl.h"
+
+#include <glib/gi18n-lib.h>
+
+enum {
+	PROP_0,
+	PROP_CERTIFICATE,
+	PROP_LABEL,
+	PROP_TRANSIENT_FOR
+};
+
+struct _GcrCertificateExporterPrivate {
+
+	/* Setup stuff */
+	GcrCertificate *certificate;
+	gchar *label;
+	GtkWindow *transient_for;
+
+	/* Used during operation */
+	GtkFileChooser *chooser_dialog;
+	GFile *output_file;
+	GByteArray *buffer;
+	guint buffer_at;
+
+	/* Async stuff */
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+	GCancellable *cancellable;
+	GError *error;
+	gboolean completed;
+};
+
+static const gchar *BAD_FILENAME_CHARS = "/\\<>|?*";
+
+/* Forward declarations */
+static void _gcr_certificate_exporter_async_result_init (GAsyncResultIface *iface);
+static void write_to_outputstream (GcrCertificateExporter *self, GOutputStream *os);
+
+G_DEFINE_TYPE_WITH_CODE (GcrCertificateExporter, _gcr_certificate_exporter, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, _gcr_certificate_exporter_async_result_init));
+
+typedef void (*PrepareDataFunc) (GcrCertificateExporter *self);
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static void
+prepare_data_for_der (GcrCertificateExporter *self)
+{
+	gconstpointer data;
+	gsize n_data;
+
+	data = gcr_certificate_get_der_data (self->pv->certificate, &n_data);
+	g_return_if_fail (data);
+
+	self->pv->buffer = g_byte_array_new ();
+	g_byte_array_append (self->pv->buffer, data, n_data);
+}
+
+static void
+prepare_data_for_pem (GcrCertificateExporter *self)
+{
+	gconstpointer data;
+	gpointer encoded;
+	gsize n_data, n_encoded;
+
+	data = gcr_certificate_get_der_data (self->pv->certificate, &n_data);
+	g_return_if_fail (data);
+
+	self->pv->buffer = g_byte_array_new ();
+
+	encoded = egg_openssl_pem_write (data, n_data,
+	                                 g_quark_from_static_string ("CERTIFICATE"),
+	                                 NULL, &n_encoded);
+
+	g_byte_array_append (self->pv->buffer, encoded, n_encoded);
+	g_free (encoded);
+}
+
+static void
+complete_async_result (GcrCertificateExporter *self)
+{
+	g_assert (self->pv->callback);
+	g_assert (!self->pv->completed);
+
+	if (self->pv->chooser_dialog)
+		gtk_widget_hide (GTK_WIDGET (self->pv->chooser_dialog));
+
+	self->pv->completed = TRUE;
+	(self->pv->callback) (G_OBJECT (self), G_ASYNC_RESULT (self),
+	                      self->pv->user_data);
+}
+
+static void
+on_outputstream_write_ready (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (user_data);
+	GOutputStream *os = G_OUTPUT_STREAM (source);
+	gssize written;
+
+	written = g_output_stream_write_finish (os, res, &self->pv->error);
+
+	if (self->pv->error) {
+		complete_async_result (self);
+		return;
+	}
+
+	g_return_if_fail (written >= 0);
+	g_return_if_fail (written <= self->pv->buffer->len - self->pv->buffer_at);
+	self->pv->buffer_at += written;
+
+	/* Write next bit, or finished */
+	write_to_outputstream (self, os);
+}
+
+static void
+on_outputstream_closed (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (user_data);
+	g_output_stream_close_finish (G_OUTPUT_STREAM (source), res, &self->pv->error);
+	complete_async_result (self);
+}
+
+static void
+write_to_outputstream (GcrCertificateExporter *self, GOutputStream *os)
+{
+	gtk_widget_hide (GTK_WIDGET (self->pv->chooser_dialog));
+	g_assert (GTK_IS_WIDGET (self->pv->chooser_dialog));
+
+	/* Are we all done? */
+	g_assert (self->pv->buffer_at <= self->pv->buffer->len);
+	if (self->pv->buffer_at == self->pv->buffer->len) {
+		g_output_stream_close_async (os, G_PRIORITY_DEFAULT,
+		                             self->pv->cancellable,
+		                             on_outputstream_closed, self);
+		return;
+	}
+
+	g_output_stream_write_async (os, self->pv->buffer->data + self->pv->buffer_at,
+	                             self->pv->buffer->len - self->pv->buffer_at,
+	                             G_PRIORITY_DEFAULT, self->pv->cancellable,
+	                             on_outputstream_write_ready, self);
+}
+
+static void
+on_replace_file_ready (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (user_data);
+	GFile *file = G_FILE (source);
+	GFileOutputStream *os;
+
+	os = g_file_replace_finish (file, res, &self->pv->error);
+
+	if (self->pv->error) {
+		complete_async_result (self);
+		return;
+	}
+
+	write_to_outputstream (self, G_OUTPUT_STREAM (os));
+}
+
+static void
+on_replace_dialog_response (GtkDialog *dialog, gint response_id, gpointer user_data)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (user_data);
+
+	if (response_id == GTK_RESPONSE_ACCEPT) {
+		g_file_replace_async (self->pv->output_file, NULL, FALSE, G_FILE_CREATE_NONE,
+		                      G_PRIORITY_DEFAULT, self->pv->cancellable,
+		                      on_replace_file_ready, self);
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+on_cancel_replace_dialog (GCancellable *cancellable, gpointer user_data)
+{
+	gtk_widget_destroy (user_data);
+}
+
+static void
+on_create_file_ready (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (user_data);
+	GFileOutputStream *os;
+	GtkWidget *dialog;
+
+	os = g_file_create_finish (self->pv->output_file, res, &self->pv->error);
+
+	/* Try again this time replacing the file */
+	if (g_error_matches (self->pv->error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
+		g_clear_error (&self->pv->error);
+
+		dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (self->pv->chooser_dialog),
+		     GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
+		     GTK_BUTTONS_NONE, _("<b>A file already exists with this name.</b>\n\nDo you want to replace it with a new file?"));
+		gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+		                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		                        _("_Replace"), GTK_RESPONSE_ACCEPT, NULL);
+
+		g_signal_connect (dialog, "response",
+		                  G_CALLBACK (on_replace_dialog_response), self);
+		if (self->pv->cancellable)
+			g_cancellable_connect (self->pv->cancellable,
+			                       G_CALLBACK (on_cancel_replace_dialog),
+			                       g_object_ref (dialog), g_object_unref);
+		gtk_widget_show (dialog);
+
+		return;
+	}
+
+	if (self->pv->error) {
+		complete_async_result (self);
+		return;
+	}
+
+	write_to_outputstream (self, G_OUTPUT_STREAM (os));
+}
+
+static void
+on_chooser_dialog_response (GtkDialog *dialog, gint response_id, gpointer user_data)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (user_data);
+	GtkFileFilter *filter;
+	PrepareDataFunc prepare_data;
+
+	if (response_id != GTK_RESPONSE_ACCEPT) {
+		g_set_error (&self->pv->error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+		             _("The operation was cancelled."));
+		complete_async_result (self);
+		return;
+	}
+
+	if (self->pv->output_file)
+		g_object_unref (self->pv->output_file);
+	self->pv->output_file = gtk_file_chooser_get_file (self->pv->chooser_dialog);
+	g_return_if_fail (self->pv->output_file);
+
+	filter = gtk_file_chooser_get_filter (self->pv->chooser_dialog);
+	prepare_data = g_object_get_data (G_OBJECT (filter), "prepare-data-func");
+	g_assert (prepare_data);
+
+	if (self->pv->buffer)
+		g_byte_array_free (self->pv->buffer, TRUE);
+	self->pv->buffer = NULL;
+	self->pv->buffer_at = 0;
+
+	/* Prepare the for writing out */
+	(prepare_data) (self);
+
+	/* Try to open the file */
+	g_file_create_async (self->pv->output_file, G_FILE_CREATE_NONE, G_PRIORITY_DEFAULT,
+	                     self->pv->cancellable, on_create_file_ready,
+	                     self);
+}
+
+static void
+on_cancel_chooser_dialog (GCancellable *cancellable, gpointer user_data)
+{
+	GtkDialog *dialog = GTK_DIALOG (user_data);
+	gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL);
+}
+
+static void
+exporter_display_chooser (GcrCertificateExporter *self)
+{
+	GtkFileFilter* filter;
+	GtkWidget *dialog;
+	gchar *filename;
+
+	g_assert (!self->pv->chooser_dialog);
+
+	dialog = gtk_file_chooser_dialog_new (_("Export certificate"),
+	                     NULL, GTK_FILE_CHOOSER_ACTION_SAVE,
+	                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                     GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+	                     NULL);
+
+	self->pv->chooser_dialog = g_object_ref_sink(dialog);
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+	                                 GTK_RESPONSE_ACCEPT);
+	gtk_file_chooser_set_local_only (self->pv->chooser_dialog, FALSE);
+
+	filter = gtk_file_filter_new ();
+	gtk_file_filter_set_name (filter, _("Certificate files"));
+	gtk_file_filter_add_mime_type (filter, "application/x-x509-ca-cert");
+	gtk_file_filter_add_mime_type (filter, "application/x-x509-user-cert");
+	gtk_file_filter_add_mime_type (filter, "application/pkix-cert");
+	gtk_file_filter_add_pattern (filter, "*.cer");
+	gtk_file_filter_add_pattern (filter, "*.crt");
+	g_object_set_data (G_OBJECT (filter), "prepare-data-func", prepare_data_for_der);
+	gtk_file_chooser_add_filter (self->pv->chooser_dialog, filter);
+	gtk_file_chooser_set_filter (self->pv->chooser_dialog, filter);
+
+	filter = gtk_file_filter_new ();
+	gtk_file_filter_set_name (filter, _("PEM files"));
+	gtk_file_filter_add_mime_type (filter, "text/plain");
+	gtk_file_filter_add_pattern (filter, "*.pem");
+	g_object_set_data (G_OBJECT (filter), "prepare-data-func", prepare_data_for_pem);
+	gtk_file_chooser_add_filter (self->pv->chooser_dialog, filter);
+
+	filename = g_strconcat (self->pv->label, ".crt", NULL);
+	g_strdelimit (filename, BAD_FILENAME_CHARS, '_');
+	gtk_file_chooser_set_current_name (self->pv->chooser_dialog, filename);
+	g_free (filename);
+
+	g_signal_connect (self->pv->chooser_dialog, "response",
+	                  G_CALLBACK (on_chooser_dialog_response), self);
+	if (self->pv->cancellable)
+		g_cancellable_connect (self->pv->cancellable,
+		                       G_CALLBACK (on_cancel_chooser_dialog), self, NULL);
+
+	gtk_widget_show (GTK_WIDGET (self->pv->chooser_dialog));
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static void
+_gcr_certificate_exporter_init (GcrCertificateExporter *self)
+{
+	self->pv = (G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_CERTIFICATE_EXPORTER,
+	                                         GcrCertificateExporterPrivate));
+}
+
+static void
+_gcr_certificate_exporter_dispose (GObject *obj)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (obj);
+
+	if (self->pv->certificate)
+		g_object_unref (self->pv->certificate);
+	self->pv->certificate = NULL;
+
+	if (self->pv->cancellable)
+		g_object_unref (self->pv->cancellable);
+	self->pv->cancellable = NULL;
+
+	G_OBJECT_CLASS (_gcr_certificate_exporter_parent_class)->dispose (obj);
+}
+
+static void
+_gcr_certificate_exporter_finalize (GObject *obj)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (obj);
+
+	g_free (self->pv->label);
+
+	/*
+	 * Should have been freed in _export_finish, which holds a ref to self
+	 * so this should never be reached without being finished.
+	 */
+	g_assert (!self->pv->chooser_dialog);
+	g_assert (!self->pv->output_file);
+	g_assert (!self->pv->buffer);
+
+	g_clear_error (&self->pv->error);
+
+	G_OBJECT_CLASS (_gcr_certificate_exporter_parent_class)->finalize (obj);
+}
+
+static void
+_gcr_certificate_exporter_set_property (GObject *obj, guint prop_id, const GValue *value,
+                                        GParamSpec *pspec)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (obj);
+	GcrCertificate *cert;
+
+	switch (prop_id) {
+	case PROP_CERTIFICATE:
+		cert = g_value_dup_object (value);
+		if (self->pv->certificate)
+			g_object_unref (self->pv->certificate);
+		self->pv->certificate = cert;
+		g_object_notify (G_OBJECT (self), "certificate");
+		break;
+	case PROP_LABEL:
+		g_free (self->pv->label);
+		self->pv->label = g_value_dup_string (value);
+		g_object_notify (obj, "label");
+		break;
+	case PROP_TRANSIENT_FOR:
+		self->pv->transient_for = g_value_get_object (value);
+		g_object_notify (obj, "transient-for");
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_certificate_exporter_get_property (GObject *obj, guint prop_id, GValue *value,
+                                        GParamSpec *pspec)
+{
+	GcrCertificateExporter *self = GCR_CERTIFICATE_EXPORTER (obj);
+
+	switch (prop_id) {
+	case PROP_CERTIFICATE:
+		g_value_set_object (value, self->pv->certificate);
+		break;
+	case PROP_LABEL:
+		g_value_take_string (value, self->pv->label);
+		break;
+	case PROP_TRANSIENT_FOR:
+		g_value_set_object (value, self->pv->transient_for);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_certificate_exporter_class_init (GcrCertificateExporterClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	_gcr_certificate_exporter_parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GcrCertificateExporterPrivate));
+
+	gobject_class->dispose = _gcr_certificate_exporter_dispose;
+	gobject_class->finalize = _gcr_certificate_exporter_finalize;
+	gobject_class->set_property = _gcr_certificate_exporter_set_property;
+	gobject_class->get_property = _gcr_certificate_exporter_get_property;
+
+	g_object_class_install_property (gobject_class, PROP_CERTIFICATE,
+	           g_param_spec_object ("certificate", "Certificate", "Certificate to display.",
+	                               GCR_TYPE_CERTIFICATE, G_PARAM_READWRITE));
+
+	g_object_class_install_property (gobject_class, PROP_LABEL,
+	           g_param_spec_string ("label", "Label", "Label of certificate.",
+	                                _("Certificate"), G_PARAM_READWRITE));
+
+	g_object_class_install_property (gobject_class, PROP_TRANSIENT_FOR,
+	           g_param_spec_object ("transient-for", "Transient For", "Transient for this Window",
+	                                GTK_TYPE_WINDOW, G_PARAM_READWRITE));
+}
+
+static GObject*
+_gcr_certificate_exporter_get_source_object (GAsyncResult *result)
+{
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_EXPORTER (result), NULL);
+	return G_OBJECT (result);
+}
+
+static gpointer
+_gcr_certificate_exporter_get_user_data (GAsyncResult *result)
+{
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_EXPORTER (result), NULL);
+	return GCR_CERTIFICATE_EXPORTER (result)->pv->user_data;
+}
+
+static void
+_gcr_certificate_exporter_async_result_init (GAsyncResultIface *iface)
+{
+	iface->get_source_object = _gcr_certificate_exporter_get_source_object;
+	iface->get_user_data = _gcr_certificate_exporter_get_user_data;
+}
+
+/* -----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+GcrCertificateExporter*
+_gcr_certificate_exporter_new (GcrCertificate *certificate, const gchar *label,
+                               GtkWindow *transient_for)
+{
+	return g_object_new (GCR_TYPE_CERTIFICATE_EXPORTER,
+	                     "certificate", certificate,
+	                     "label", label,
+	                     "transient-for", transient_for,
+	                     NULL);
+}
+
+void
+_gcr_certificate_exporter_export_async (GcrCertificateExporter *self,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data)
+{
+	g_return_if_fail (GCR_IS_CERTIFICATE_EXPORTER (self));
+	g_return_if_fail (callback);
+
+	/* Must not have already started */
+	g_return_if_fail (!self->pv->callback);
+	g_return_if_fail (!self->pv->cancellable);
+
+	self->pv->callback = callback;
+	self->pv->user_data = user_data;
+	if (cancellable)
+		self->pv->cancellable = g_object_ref (cancellable);
+
+	exporter_display_chooser (self);
+
+	/* Matching in export_finish */
+	g_object_ref (self);
+}
+
+gboolean
+_gcr_certificate_exporter_export_finish (GcrCertificateExporter *self,
+                                         GAsyncResult *result,
+                                         GError **error)
+{
+	gboolean ret = TRUE;
+
+	g_return_val_if_fail (G_ASYNC_RESULT (self) == result, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (self->pv->completed, FALSE);
+
+	/* Cleanup all the operation stuff */
+	self->pv->callback = NULL;
+
+	if (self->pv->chooser_dialog)
+		g_object_unref (self->pv->chooser_dialog);
+	self->pv->chooser_dialog = NULL;
+
+	if (self->pv->output_file)
+		g_object_unref (self->pv->output_file);
+	self->pv->output_file = NULL;
+
+	if (self->pv->buffer)
+		g_byte_array_free (self->pv->buffer, TRUE);
+	self->pv->buffer = NULL;
+	self->pv->buffer_at = 0;
+
+	self->pv->completed = FALSE;
+
+	if (self->pv->error) {
+		g_propagate_error (error, self->pv->error);
+		ret = FALSE;
+	}
+
+	/* Matches in export_async */
+	g_object_unref (self);
+	return ret;
+}
diff --git a/gcr/gcr-certificate-exporter.h b/gcr/gcr-certificate-exporter.h
new file mode 100644
index 0000000..5f7a9cf
--- /dev/null
+++ b/gcr/gcr-certificate-exporter.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#if !defined (__GCR_H_INSIDE__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#ifndef __GCR_CERTIFICATE_EXPORTER_H__
+#define __GCR_CERTIFICATE_EXPORTER_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gcr-types.h"
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_CERTIFICATE_EXPORTER               (_gcr_certificate_exporter_get_type ())
+#define GCR_CERTIFICATE_EXPORTER(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_CERTIFICATE_EXPORTER, GcrCertificateExporter))
+#define GCR_CERTIFICATE_EXPORTER_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_CERTIFICATE_EXPORTER, GcrCertificateExporterClass))
+#define GCR_IS_CERTIFICATE_EXPORTER(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_CERTIFICATE_EXPORTER))
+#define GCR_IS_CERTIFICATE_EXPORTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_CERTIFICATE_EXPORTER))
+#define GCR_CERTIFICATE_EXPORTER_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_CERTIFICATE_EXPORTER, GcrCertificateExporterClass))
+
+typedef struct _GcrCertificateExporter GcrCertificateExporter;
+typedef struct _GcrCertificateExporterClass GcrCertificateExporterClass;
+typedef struct _GcrCertificateExporterPrivate GcrCertificateExporterPrivate;
+
+struct _GcrCertificateExporter {
+	GObject parent;
+	GcrCertificateExporterPrivate *pv;
+};
+
+struct _GcrCertificateExporterClass {
+	GObjectClass parent_class;
+};
+
+GType                     _gcr_certificate_exporter_get_type          (void);
+
+GcrCertificateExporter*   _gcr_certificate_exporter_new               (GcrCertificate *certificate,
+                                                                       const gchar *label,
+                                                                       GtkWindow *transient_for);
+
+void                      _gcr_certificate_exporter_export_async      (GcrCertificateExporter *self,
+                                                                       GCancellable *cancellable,
+                                                                       GAsyncReadyCallback callback,
+                                                                       gpointer user_data);
+
+gboolean                  _gcr_certificate_exporter_export_finish     (GcrCertificateExporter *self,
+                                                                       GAsyncResult *result,
+                                                                       GError **error);
+
+G_END_DECLS
+
+#endif /* __GCR_CERTIFICATE_EXPORTER_H__ */
diff --git a/gcr/gcr-certificate-renderer.c b/gcr/gcr-certificate-renderer.c
index 073d944..760df1e 100644
--- a/gcr/gcr-certificate-renderer.c
+++ b/gcr/gcr-certificate-renderer.c
@@ -20,6 +20,7 @@
 #include "config.h"
 
 #include "gcr-certificate.h"
+#include "gcr-certificate-exporter.h"
 #include "gcr-certificate-renderer.h"
 #include "gcr-display-view.h"
 #include "gcr-icons.h"
@@ -174,6 +175,62 @@ on_parsed_dn_part (guint index, GQuark oid, const guchar *value,
 	g_free (display);
 }
 
+static gboolean
+on_delete_unref_dialog (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+	g_object_unref (widget);
+	return FALSE;
+}
+
+static void
+on_export_completed (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	GtkWindow *parent = GTK_WINDOW (user_data);
+	GcrCertificateExporter *exporter = GCR_CERTIFICATE_EXPORTER (source);
+	GError *error = NULL;
+	GtkWidget *dialog;
+
+	if (!_gcr_certificate_exporter_export_finish (exporter, result, &error)) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			dialog = gtk_message_dialog_new_with_markup (parent,
+				  GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
+				  GTK_BUTTONS_OK, "<big>%s</big>\n\n%s",
+				  _("Couldn't export the certificate."),
+				  error->message);
+			gtk_widget_show (dialog);
+			g_signal_connect (dialog, "delete-event",
+					  G_CALLBACK (on_delete_unref_dialog), NULL);
+		}
+	}
+
+	/* Matches ref in on_certificate_export */
+	if (parent)
+		g_object_unref (parent);
+}
+
+static void
+on_certificate_export (GtkMenuItem *menuitem, gpointer user_data)
+{
+	GcrCertificateRenderer *self = GCR_CERTIFICATE_RENDERER (user_data);
+	GcrCertificateExporter *exporter;
+	gchar *label;
+	GtkWidget *parent;
+
+	label = calculate_label (self, NULL);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (menuitem));
+	if (parent && !GTK_IS_WINDOW (parent))
+		parent = NULL;
+
+	exporter = _gcr_certificate_exporter_new (self->pv->certificate, label,
+	                                          GTK_WINDOW (parent));
+
+	g_free (label);
+
+	_gcr_certificate_exporter_export_async (exporter, NULL, on_export_completed,
+	                                        parent ? g_object_ref (parent) : NULL);
+}
+
 /* -----------------------------------------------------------------------------
  * OBJECT
  */
@@ -291,7 +348,7 @@ gcr_certificate_renderer_class_init (GcrCertificateRendererClass *klass)
 }
 
 static void
-gcr_certificate_renderer_real_render (GcrRenderer *renderer, GcrViewer *viewer)
+gcr_certificate_renderer_render (GcrRenderer *renderer, GcrViewer *viewer)
 {
 	GcrCertificateRenderer *self;
 	gconstpointer data, value;
@@ -461,9 +518,27 @@ gcr_certificate_renderer_real_render (GcrRenderer *renderer, GcrViewer *viewer)
 }
 
 static void
+gcr_certificate_renderer_populate_popup (GcrRenderer *self, GcrViewer *viewer,
+                                         GtkMenu *menu)
+{
+	GtkWidget *item;
+
+	item = gtk_separator_menu_item_new ();
+	gtk_widget_show (item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+
+	item = gtk_menu_item_new_with_label ("Export Certificate...");
+	gtk_widget_show (item);
+	g_signal_connect_data (item, "activate", G_CALLBACK (on_certificate_export),
+	                       g_object_ref (self), (GClosureNotify)g_object_unref, 0);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+}
+
+static void
 gcr_renderer_iface_init (GcrRendererIface *iface)
 {
-	iface->render = gcr_certificate_renderer_real_render;
+	iface->render = gcr_certificate_renderer_render;
+	iface->populate_popup = gcr_certificate_renderer_populate_popup;
 }
 
 /* -----------------------------------------------------------------------------
diff --git a/gcr/gcr-display-view.c b/gcr/gcr-display-view.c
index df55b26..f5fb2ec 100644
--- a/gcr/gcr-display-view.c
+++ b/gcr/gcr-display-view.c
@@ -62,6 +62,7 @@ struct _GcrDisplayViewPrivate {
 	GtkTextTag *content_tag;
 	GtkTextTag *heading_tag;
 	GtkTextTag *monospace_tag;
+	GcrDisplayItem *current_item;
 
 	gboolean have_measurements;
 	gint minimal_width;
@@ -347,6 +348,29 @@ lookup_display_item (GcrDisplayView *self, GcrRenderer *renderer)
 	return item;
 }
 
+static GcrDisplayItem*
+find_item_at_iter (GcrDisplayView *self, GtkTextIter *iter)
+{
+	GHashTableIter hi;
+	GcrDisplayItem *item;
+	gpointer value;
+	GtkTextIter start, end;
+
+	g_hash_table_iter_init (&hi, self->pv->items);
+	while (g_hash_table_iter_next (&hi, NULL, &value)) {
+		item = value;
+
+		gtk_text_buffer_get_iter_at_mark (self->pv->buffer, &start, item->beginning);
+		gtk_text_buffer_get_iter_at_mark (self->pv->buffer, &end, item->ending);
+
+		if (gtk_text_iter_compare (iter, &start) >= 0 &&
+		    gtk_text_iter_compare (iter, &end) < 0)
+			return item;
+	}
+
+	return NULL;
+}
+
 static void
 on_renderer_data_changed (GcrRenderer *renderer, GcrViewer *self)
 {
@@ -514,6 +538,32 @@ _gcr_display_view_realize (GtkWidget *widget)
 		style_display_item (widget, value);
 }
 
+static gboolean
+_gcr_display_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
+{
+	GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+	GcrDisplayView *self = GCR_DISPLAY_VIEW (widget);
+	GcrDisplayItem *item;
+	gboolean handled = FALSE;
+	GtkTextIter iter;
+	gint x, y;
+
+	if (GTK_WIDGET_CLASS (_gcr_display_view_parent_class)->button_press_event)
+		handled = GTK_WIDGET_CLASS (_gcr_display_view_parent_class)->button_press_event (
+				widget, event);
+
+	if (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)) {
+		gtk_text_view_window_to_buffer_coords (text_view, GTK_TEXT_WINDOW_TEXT,
+		                                       event->x, event->y, &x, &y);
+		gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+
+		item = find_item_at_iter (self, &iter);
+		self->pv->current_item = item;
+	}
+
+	return handled;
+}
+
 #if GTK_CHECK_VERSION (2,91,0)
 
 static gboolean
@@ -583,10 +633,25 @@ _gcr_display_get_preferred_width (GtkWidget *widget, gint *minimal_width,
 #endif /* GTK 3.x */
 
 static void
+_gcr_display_view_populate_popup (GtkTextView *text_view, GtkMenu *menu)
+{
+	GcrDisplayView *self = GCR_DISPLAY_VIEW (text_view);
+
+	if (GTK_TEXT_VIEW_CLASS (_gcr_display_view_parent_class)->populate_popup)
+		GTK_TEXT_VIEW_CLASS (_gcr_display_view_parent_class)->populate_popup (text_view, menu);
+
+	/* Ask the current renderer to add menu items */
+	if (self->pv->current_item)
+		gcr_renderer_popuplate_popup (self->pv->current_item->renderer,
+		                              GCR_VIEWER (self), menu);
+}
+
+static void
 _gcr_display_view_class_init (GcrDisplayViewClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+	GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
 
 	_gcr_display_view_parent_class = g_type_class_peek_parent (klass);
 	g_type_class_add_private (klass, sizeof (GcrDisplayViewPrivate));
@@ -596,6 +661,7 @@ _gcr_display_view_class_init (GcrDisplayViewClass *klass)
 	gobject_class->finalize = _gcr_display_view_finalize;
 
 	widget_class->realize = _gcr_display_view_realize;
+	widget_class->button_press_event = _gcr_display_view_button_press_event;
 
 #if GTK_CHECK_VERSION (3,0,0)
 	widget_class->get_preferred_height = _gcr_display_get_preferred_height;
@@ -607,6 +673,8 @@ _gcr_display_view_class_init (GcrDisplayViewClass *klass)
 #else
 	widget_class->expose_event = _gcr_display_view_expose_event;
 #endif
+
+	text_view_class->populate_popup = _gcr_display_view_populate_popup;
 }
 
 static void
diff --git a/gcr/gcr-renderer.c b/gcr/gcr-renderer.c
index 59ca30a..28bc905 100644
--- a/gcr/gcr-renderer.c
+++ b/gcr/gcr-renderer.c
@@ -89,6 +89,15 @@ gcr_renderer_render (GcrRenderer *self, GcrViewer *viewer)
 }
 
 void
+gcr_renderer_popuplate_popup (GcrRenderer *self, GcrViewer *viewer,
+                              GtkMenu *menu)
+{
+	g_return_if_fail (GCR_IS_RENDERER (self));
+	if (GCR_RENDERER_GET_INTERFACE (self)->populate_popup)
+		GCR_RENDERER_GET_INTERFACE (self)->populate_popup (self, viewer, menu);
+}
+
+void
 gcr_renderer_emit_data_changed (GcrRenderer *self)
 {
 	g_return_if_fail (GCR_IS_RENDERER (self));
diff --git a/gcr/gcr-renderer.h b/gcr/gcr-renderer.h
index a9bde34..bf7e04a 100644
--- a/gcr/gcr-renderer.h
+++ b/gcr/gcr-renderer.h
@@ -45,6 +45,8 @@ struct _GcrRendererIface {
 	/* virtual */
 	void (*render) (GcrRenderer *self, GcrViewer *viewer);
 
+	void (*populate_popup) (GcrRenderer *self, GcrViewer *viewer, GtkMenu *menu);
+
 	gpointer dummy1;
 	gpointer dummy2;
 	gpointer dummy3;
@@ -60,6 +62,10 @@ GType                  gcr_renderer_get_type                      (void) G_GNUC_
 void                   gcr_renderer_render                        (GcrRenderer *self,
                                                                    GcrViewer *viewer);
 
+void                   gcr_renderer_popuplate_popup               (GcrRenderer *self,
+                                                                   GcrViewer *viewer,
+                                                                   GtkMenu *menu);
+
 void                   gcr_renderer_emit_data_changed             (GcrRenderer *self);
 
 GcrRenderer*           gcr_renderer_create                        (const gchar *label,
diff --git a/gcr/tests/ui-test-certificate.c b/gcr/tests/ui-test-certificate.c
index 2a181ac..cfb2d8d 100644
--- a/gcr/tests/ui-test-certificate.c
+++ b/gcr/tests/ui-test-certificate.c
@@ -53,13 +53,10 @@ chdir_base_dir (char* argv0)
 }
 
 static void
-on_parser_parsed (GcrParser *parser, gpointer unused)
+on_parser_parsed (GcrParser *parser, gpointer user_data)
 {
 	GcrCertificateWidget *details;
-	GtkDialog *dialog;
-
-	dialog = GTK_DIALOG (gtk_dialog_new ());
-	g_object_ref_sink (dialog);
+	GtkDialog *dialog = GTK_DIALOG (user_data);
 
 	details = g_object_new (GCR_TYPE_CERTIFICATE_WIDGET,
 	                        "attributes", gcr_parser_get_parsed_attributes (parser),
@@ -69,10 +66,6 @@ on_parser_parsed (GcrParser *parser, gpointer unused)
 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (dialog)), GTK_WIDGET (details));
 
 	gtk_container_set_border_width (GTK_CONTAINER (dialog), 20);
-	gtk_dialog_run (dialog);
-	gtk_widget_destroy (GTK_WIDGET (dialog));
-
-	g_object_unref (dialog);
 }
 
 static void
@@ -82,17 +75,27 @@ test_certificate (const gchar *path)
 	GError *err = NULL;
 	guchar *data;
 	gsize n_data;
+	GtkWidget *dialog;
 
 	if (!g_file_get_contents (path, (gchar**)&data, &n_data, NULL))
 		g_error ("couldn't read file: %s", path);
 
+	dialog = gtk_dialog_new ();
+	g_object_ref_sink (dialog);
+
 	parser = gcr_parser_new ();
-	g_signal_connect (parser, "parsed", G_CALLBACK (on_parser_parsed), NULL);
+	g_signal_connect (parser, "parsed", G_CALLBACK (on_parser_parsed), dialog);
 	if (!gcr_parser_parse_data (parser, data, n_data, &err))
 		g_error ("couldn't parse data: %s", err->message);
 
 	g_object_unref (parser);
 	g_free (data);
+
+	gtk_widget_show (dialog);
+	g_signal_connect (dialog, "delete-event", G_CALLBACK (gtk_main_quit), NULL);
+	gtk_main ();
+
+	g_object_unref (dialog);
 }
 
 int



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]