[almanah] Added import support



commit 97530ba0ac032ef81892f090030db2aeb971e0ca
Author: Philip Withnall <philip tecnocode co uk>
Date:   Thu Sep 24 16:08:46 2009 +0100

    Added import support
    
    Text files and other Almanah databases can now be imported to the
    current database.

 data/almanah.ui                 |  235 +++++++++++++
 po/Makefile.in.in               |    4 +-
 po/POTFILES.in                  |    1 +
 src/Makefile.am                 |    2 +
 src/definition-manager-window.c |   14 +-
 src/import-dialog.c             |  694 +++++++++++++++++++++++++++++++++++++++
 src/import-dialog.h             |   83 +++++
 src/main-window.c               |   16 +-
 src/main.c                      |    2 +
 src/main.h                      |    1 +
 src/storage-manager.c           |  132 ++++++--
 src/storage-manager.h           |    3 +-
 12 files changed, 1144 insertions(+), 43 deletions(-)
---
diff --git a/data/almanah.ui b/data/almanah.ui
index 7729b6c..6dcba63 100644
--- a/data/almanah.ui
+++ b/data/almanah.ui
@@ -10,6 +10,13 @@
 					</object>
 				</child>
 				<child>
+					<object class="GtkAction" id="almanah_ui_import">
+						<property name="name">file-import</property>
+						<property name="label" translatable="yes">_Importâ?¦</property>
+						<signal name="activate" handler="mw_import_activate_cb"/>
+					</object>
+				</child>
+				<child>
 					<object class="GtkAction" id="almanah_ui_page_setup">
 						<property name="stock-id">gtk-page-setup</property>
 						<property name="name">file-page-setup</property>
@@ -201,6 +208,8 @@
 		<ui>
 			<menubar name="almanah_mw_menu_bar">
 				<menu action="almanah_ui_file">
+					<menuitem action="almanah_ui_import"/>
+					<separator/>
 					<menuitem action="almanah_ui_page_setup"/>
 					<menuitem action="almanah_ui_print_preview"/>
 					<menuitem action="almanah_ui_print"/>
@@ -918,4 +927,230 @@
 			<action-widget response="-5">almanah_ded_ok_button</action-widget><!-- GTK_RESPONSE_OK -->
 		</action-widgets>
 	</object>
+
+	<object class="GtkListStore" id="almanah_id_mode_store">
+		<columns>
+			<column type="gchararray"/><!-- Name -->
+			<column type="guint"/><!-- Type -->
+		</columns>
+	</object>
+
+	<object class="AlmanahImportDialog" id="almanah_import_dialog">
+		<child internal-child="vbox">
+			<object class="GtkVBox" id="almanah_id_internal_vbox">
+				<property name="spacing">5</property>
+				<child>
+					<object class="GtkVBox" id="almanah_id_vbox">
+						<property name="border-width">5</property>
+						<property name="spacing">5</property>
+						<child>
+							<object class="GtkHBox" id="almanah_id_hbox">
+								<property name="spacing">5</property>
+								<child>
+									<object class="GtkLabel" id="almanah_id_mode_label">
+										<property name="xalign">0</property>
+										<property name="label" translatable="yes">Import Mode</property>
+										<accessibility>
+											<relation target="almanah_id_mode_combo_box" type="label-for"/>
+										</accessibility>
+										<attributes>
+											<attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
+										</attributes>
+									</object>
+									<packing>
+										<property name="expand">False</property>
+									</packing>
+								</child>
+								<child>
+									<object class="GtkComboBox" id="almanah_id_mode_combo_box">
+										<signal name="changed" handler="id_mode_combo_box_changed_cb"/>
+										<property name="model">almanah_id_mode_store</property>
+										<accessibility>
+											<relation target="almanah_id_mode_label" type="labelled-by"/>
+										</accessibility>
+										<child>
+											<object class="GtkCellRendererText" id="almanah_id_mode_renderer"/>
+											<attributes>
+												<attribute name="text">0</attribute>
+											</attributes>
+										</child>
+									</object>
+								</child>
+							</object>
+							<packing>
+								<property name="expand">False</property>
+							</packing>
+						</child>
+						<child>
+							<object class="GtkLabel" id="almanah_id_description_label">
+								<property name="wrap">True</property>
+								<attributes>
+									<attribute name="scale" value="0.83"/><!-- PANGO_SCALE_SMALL -->
+								</attributes>
+							</object>
+							<packing>
+								<property name="expand">False</property>
+							</packing>
+						</child>
+						<child>
+							<object class="GtkFileChooserWidget" id="almanah_id_file_chooser">
+								<signal name="selection-changed" handler="id_file_chooser_selection_changed_cb"/>
+								<signal name="file-activated" handler="id_file_chooser_file_activated_cb"/>
+								<property name="action">GTK_FILE_CHOOSER_ACTION_OPEN</property>
+								<property name="select-multiple">False</property>
+							</object>
+						</child>
+					</object>
+				</child>
+				<child internal-child="action_area">
+					<object class="GtkHButtonBox" id="almanah_id_button_box">
+						<child>
+							<object class="GtkButton" id="almanah_id_cancel_button">
+								<property name="use-stock">True</property>
+								<property name="label">gtk-cancel</property>
+							</object>
+						</child>
+						<child>
+							<object class="GtkButton" id="almanah_id_import_button">
+								<property name="use-underline">True</property>
+								<property name="label" translatable="yes">_Import</property>
+								<property name="can-default">True</property>
+								<property name="has-default">True</property>
+							</object>
+						</child>
+					</object>
+				</child>
+			</object>
+		</child>
+		<action-widgets>
+			<action-widget response="-6">almanah_id_cancel_button</action-widget><!-- GTK_RESPONSE_CANCEL -->
+			<action-widget response="-5">almanah_id_import_button</action-widget><!-- GTK_RESPONSE_OK -->
+		</action-widgets>
+	</object>
+
+	<object class="GtkListStore" id="almanah_ird_view_store">
+		<columns>
+			<column type="gchararray"/><!-- View name -->
+		</columns>
+		<data>
+			<row>
+				<col id="0" translatable="yes">Successful Entries</col>
+			</row>
+			<row>
+				<col id="0" translatable="yes">Merged Entries</col>
+			</row>
+			<row>
+				<col id="0" translatable="yes">Failed Entries</col>
+			</row>
+		</data>
+	</object>
+
+	<object class="GtkListStore" id="almanah_ird_results_store">
+		<columns>
+			<column type="guint"/><!-- Day -->
+			<column type="guint"/><!-- Month -->
+			<column type="guint"/><!-- Year -->
+			<column type="gchararray"/><!-- Formatted date -->
+			<column type="guint"/><!-- Status -->
+			<column type="gchararray"/><!-- Message -->
+		</columns>
+	</object>
+
+	<object class="GtkTreeModelFilter" id="almanah_ird_filtered_results_store">
+		<property name="child-model">almanah_ird_results_store</property>
+	</object>
+
+	<object class="AlmanahImportResultsDialog" id="almanah_import_results_dialog">
+		<child internal-child="vbox">
+			<object class="GtkVBox" id="almanah_ird_internal_vbox">
+				<property name="spacing">5</property>
+				<child>
+					<object class="GtkVBox" id="almanah_id_vbox">
+						<property name="border-width">5</property>
+						<property name="spacing">5</property>
+						<child>
+							<object class="GtkComboBox" id="almanah_ird_view_combo_box">
+								<signal name="changed" handler="ird_view_combo_box_changed_cb"/>
+								<property name="model">almanah_ird_view_store</property>
+								<accessibility>
+									<relation target="almanah_ird_results_tree_view" type="label-for"/>
+								</accessibility>
+								<child>
+									<object class="GtkCellRendererText" id="almanah_ird_view_renderer"/>
+									<attributes>
+										<attribute name="text">0</attribute>
+									</attributes>
+								</child>
+							</object>
+							<packing>
+								<property name="expand">False</property>
+							</packing>
+						</child>
+						<child>
+							<object class="GtkScrolledWindow" id="almanah_ird_results_scrolled_window">
+								<property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+								<property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+								<property name="shadow-type">GTK_SHADOW_IN</property>
+								<child>
+									<object class="GtkTreeView" id="almanah_ird_results_tree_view">
+										<property name="model">almanah_ird_filtered_results_store</property>
+										<property name="headers-visible">False</property>
+										<signal name="row-activated" handler="ird_results_tree_view_row_activated_cb"/>
+										<accessibility>
+											<relation target="almanah_ird_view_combo_box" type="labelled-by"/>
+										</accessibility>
+										<child internal-child="accessible">
+											<object class="AtkObject" id="a11y-almanah_ird_results_tree_view">
+												<property name="AtkObject::accessible-name" translatable="yes">Import Results List</property>
+											</object>
+										</child>
+										<child>
+											<object class="GtkTreeViewColumn" id="almanah_ird_results_column">
+												<child>
+													<object class="GtkCellRendererText" id="almanah_ird_results_renderer"/>
+													<attributes>
+														<attribute name="text">3</attribute>
+													</attributes>
+												</child>
+											</object>
+										</child>
+										<child>
+											<object class="GtkTreeViewColumn" id="almanah_ird_results_message_column">
+												<child>
+													<object class="GtkCellRendererText" id="almanah_ird_results_message_renderer"/>
+													<attributes>
+														<attribute name="text">5</attribute>
+													</attributes>
+												</child>
+											</object>
+										</child>
+									</object>
+								</child>
+							</object>
+						</child>
+					</object>
+				</child>
+				<child internal-child="action_area">
+					<object class="GtkHButtonBox" id="almanah_ird_button_box">
+						<child>
+							<object class="GtkButton" id="almanah_ird_view_button">
+								<property name="label" translatable="yes">View Entry</property>
+								<property name="sensitive">False</property>
+								<signal name="clicked" handler="ird_view_button_clicked_cb"/>
+							</object>
+						</child>
+						<child>
+							<object class="GtkButton" id="almanah_ird_close_button">
+								<property name="use-stock">True</property>
+								<property name="label">gtk-close</property>
+							</object>
+						</child>
+					</object>
+				</child>
+			</object>
+		</child>
+		<action-widgets>
+			<action-widget response="-7">almanah_ird_close_button</action-widget><!-- GTK_RESPONSE_CLOSE -->
+		</action-widgets>
+	</object>
 </interface>
diff --git a/po/Makefile.in.in b/po/Makefile.in.in
index 57ef267..c7e8302 100644
--- a/po/Makefile.in.in
+++ b/po/Makefile.in.in
@@ -21,7 +21,7 @@ GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
 PACKAGE = @PACKAGE@
 VERSION = @VERSION@
 
-SHELL = /bin/sh
+SHELL = @SHELL@
 
 srcdir = @srcdir@
 top_srcdir = @top_srcdir@
@@ -56,7 +56,7 @@ ALL_LINGUAS = @ALL_LINGUAS@
 
 PO_LINGUAS=$(shell if test -r $(srcdir)/LINGUAS; then grep -v "^\#" $(srcdir)/LINGUAS; else echo "$(ALL_LINGUAS)"; fi)
 
-USER_LINGUAS=$(shell if test -n "$(LINGUAS)"; then LLINGUAS="$(LINGUAS)"; ALINGUAS="$(ALL_LINGUAS)"; for lang in $$LLINGUAS; do if test -n "`grep ^$$lang$$ $(srcdir)/LINGUAS 2>/dev/null`" -o -n "`echo $$ALINGUAS|tr ' ' '\n'|grep ^$$lang$$`"; then printf "$$lang "; fi; done; fi)
+USER_LINGUAS=$(shell if test -n "$(LINGUAS)"; then LLINGUAS="$(LINGUAS)"; ALINGUAS="$(ALL_LINGUAS)"; for lang in $$LLINGUAS; do if test -n "`grep '^$$lang$$' $(srcdir)/LINGUAS 2>/dev/null`" -o -n "`echo $$ALINGUAS|tr ' ' '\n'|grep '^$$lang$$'`"; then printf "$$lang "; fi; done; fi)
 
 USE_LINGUAS=$(shell if test -n "$(USER_LINGUAS)" -o -n "$(LINGUAS)"; then LLINGUAS="$(USER_LINGUAS)"; else if test -n "$(PO_LINGUAS)"; then LLINGUAS="$(PO_LINGUAS)"; else LLINGUAS="$(ALL_LINGUAS)"; fi; fi; for lang in $$LLINGUAS; do printf "$$lang "; done)
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6e536f5..2ab85e3 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -7,6 +7,7 @@ src/add-definition-dialog.c
 src/events/calendar-appointment.c
 src/events/calendar-task.c
 src/events/f-spot-photo.c
+src/import-dialog.c
 src/interface.c
 src/definition-manager-window.c
 src/definition.c
diff --git a/src/Makefile.am b/src/Makefile.am
index eb99cb0..63fc1f5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -55,6 +55,8 @@ almanah_SOURCES = \
 	definition.h		\
 	date-entry-dialog.c	\
 	date-entry-dialog.h	\
+	import-dialog.c		\
+	import-dialog.h		\
 	definitions/contact.c		\
 	definitions/contact.h		\
 	definitions/file.c		\
diff --git a/src/definition-manager-window.c b/src/definition-manager-window.c
index d507a0c..de80665 100644
--- a/src/definition-manager-window.c
+++ b/src/definition-manager-window.c
@@ -359,20 +359,20 @@ static void
 populate_definition_store (AlmanahDefinitionManagerWindow *self)
 {
 	AlmanahDefinitionManagerWindowPrivate *priv = self->priv;
-	AlmanahDefinition **definitions;
-	guint i = 0;
+	GSList *definitions, *i;
 
 	definitions = almanah_storage_manager_get_definitions (almanah->storage_manager);
-	while (definitions[i] != NULL) {
+	for (i = definitions; i != NULL; i = i->next) {
 		GtkTreeIter iter;
+		AlmanahDefinition *definition = ALMANAH_DEFINITION (i->data);
 
 		gtk_list_store_append (priv->definition_store, &iter);
 		gtk_list_store_set (priv->definition_store, &iter,
-				    0, definitions[i],
-				    1, almanah_definition_get_text (definitions[i]),
+				    0, definition,
+				    1, almanah_definition_get_text (definition),
 				    -1);
 
-		i++;
+		g_object_unref (definition);
 	}
-	g_free (definitions);
+	g_slist_free (definitions);
 }
diff --git a/src/import-dialog.c b/src/import-dialog.c
new file mode 100644
index 0000000..561c1ba
--- /dev/null
+++ b/src/import-dialog.c
@@ -0,0 +1,694 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * Almanah 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.
+ *
+ * Almanah 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 Almanah.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "import-dialog.h"
+#include "interface.h"
+#include "main.h"
+#include "main-window.h"
+
+typedef enum {
+	MODE_TEXT_FILES = 0,
+	MODE_DATABASE
+} ImportMode;
+
+static const struct { ImportMode mode; const gchar *name; } import_modes[] = {
+	{ MODE_TEXT_FILES, N_("Text Files") },
+	{ MODE_DATABASE, N_("Database") }
+};
+
+static void response_cb (GtkDialog *dialog, gint response_id, AlmanahImportDialog *self);
+
+/* GtkBuilder callbacks */
+void id_mode_combo_box_changed_cb (GtkComboBox *combo_box, AlmanahImportDialog *self);
+void id_file_chooser_selection_changed_cb (GtkFileChooser *file_chooser, AlmanahImportDialog *self);
+void id_file_chooser_file_activated_cb (GtkFileChooser *file_chooser, AlmanahImportDialog *self);
+
+struct _AlmanahImportDialogPrivate {
+	GtkComboBox *mode_combo_box;
+	GtkListStore *mode_store;
+	ImportMode current_mode;
+	GtkFileChooser *file_chooser;
+	GtkWidget *import_button;
+	GtkLabel *description_label;
+};
+
+G_DEFINE_TYPE (AlmanahImportDialog, almanah_import_dialog, GTK_TYPE_DIALOG)
+#define ALMANAH_IMPORT_DIALOG_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ALMANAH_TYPE_IMPORT_DIALOG, AlmanahImportDialogPrivate))
+
+static void
+almanah_import_dialog_class_init (AlmanahImportDialogClass *klass)
+{
+	g_type_class_add_private (klass, sizeof (AlmanahImportDialogPrivate));
+}
+
+static void
+almanah_import_dialog_init (AlmanahImportDialog *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_IMPORT_DIALOG, AlmanahImportDialogPrivate);
+	self->priv->current_mode = -1; /* no mode selected */
+
+	g_signal_connect (self, "response", G_CALLBACK (response_cb), self);
+	g_signal_connect (self, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), self);
+	gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+	gtk_window_set_resizable (GTK_WINDOW (self), TRUE);
+	gtk_window_set_title (GTK_WINDOW (self), _("Import Entries"));
+	gtk_window_set_transient_for (GTK_WINDOW (self), GTK_WINDOW (almanah->main_window));
+	gtk_window_set_default_size (GTK_WINDOW (self), 600, 400);
+}
+
+static void
+populate_mode_store (GtkListStore *store)
+{
+	guint i;
+
+	/* Add all the available import modes to the combo box */
+	for (i = 0; i < G_N_ELEMENTS (import_modes); i++) {
+		GtkTreeIter iter;
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter, 0, _(import_modes[i].name), 1, import_modes[i].mode, -1);
+	}
+}
+
+AlmanahImportDialog *
+almanah_import_dialog_new (void)
+{
+	GtkBuilder *builder;
+	AlmanahImportDialog *import_dialog;
+	AlmanahImportDialogPrivate *priv;
+	GError *error = NULL;
+	const gchar *interface_filename = almanah_get_interface_filename ();
+	const gchar *object_names[] = {
+		"almanah_id_mode_store",
+		"almanah_import_dialog",
+		NULL
+	};
+
+	builder = gtk_builder_new ();
+
+	if (gtk_builder_add_objects_from_file (builder, interface_filename, (gchar**) object_names, &error) == FALSE) {
+		/* Show an error */
+		GtkWidget *dialog = gtk_message_dialog_new (NULL,
+				GTK_DIALOG_MODAL,
+				GTK_MESSAGE_ERROR,
+				GTK_BUTTONS_OK,
+				_("UI file \"%s\" could not be loaded"), interface_filename);
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+		gtk_dialog_run (GTK_DIALOG (dialog));
+		gtk_widget_destroy (dialog);
+
+		g_error_free (error);
+		g_object_unref (builder);
+
+		return NULL;
+	}
+
+	gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
+	import_dialog = ALMANAH_IMPORT_DIALOG (gtk_builder_get_object (builder, "almanah_import_dialog"));
+	gtk_builder_connect_signals (builder, import_dialog);
+
+	if (import_dialog == NULL) {
+		g_object_unref (builder);
+		return NULL;
+	}
+
+	priv = import_dialog->priv;
+
+	/* Grab our child widgets */
+	priv->mode_combo_box = GTK_COMBO_BOX (gtk_builder_get_object (builder, "almanah_id_mode_combo_box"));
+	priv->file_chooser = GTK_FILE_CHOOSER (gtk_builder_get_object (builder, "almanah_id_file_chooser"));
+	priv->import_button = GTK_WIDGET (gtk_builder_get_object (builder, "almanah_id_import_button"));
+	priv->mode_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "almanah_id_mode_store"));
+	priv->description_label = GTK_LABEL (gtk_builder_get_object (builder, "almanah_id_description_label"));
+
+	/* Populate the mode combo box */
+	populate_mode_store (priv->mode_store);
+	gtk_combo_box_set_active (priv->mode_combo_box, 0);
+
+	g_object_unref (builder);
+
+	return import_dialog;
+}
+
+/**
+ * set_entry:
+ * @self: an #AlmanahImportDialog
+ * @imported_entry: an #AlmanahEntry created for an entry being imported
+ * @import_source: a string representing the source of the imported entry
+ * @message: return location for an error or informational message, or %NULL; free with g_free()
+ *
+ * Stores the given entry in the database, merging it with any existing one where appropriate.
+ *
+ * @import_source should represent where the imported entry came from, so it could be the filename of a text file which held the entry, or of
+ * the entry's previous database.
+ *
+ * If @message is non-%NULL, error or informational messages can be allocated and returned in it. They will be returned irrespective of the return
+ * value of the function, as some errors can be overcome to result in a successful import. The message should be freed with g_free() by the caller.
+ *
+ * Return value: %ALMANAH_IMPORT_STATUS_MERGED if the entry was merged with an existing one, %ALMANAH_IMPORT_STATUS_IMPORTED if it was imported
+ * without merging, %ALMANAH_IMPORT_STATUS_FAILED otherwise
+ **/
+static AlmanahImportStatus
+set_entry (AlmanahImportDialog *self, AlmanahEntry *imported_entry, const gchar *import_source, gchar **message)
+{
+	GDate entry_date;
+	AlmanahEntry *existing_entry;
+	GtkTextBuffer *existing_buffer, *imported_buffer;
+	GtkTextIter existing_end, imported_start, imported_end;
+	gchar *header_string;
+	GError *error = NULL;
+
+	/* Check to see if there's a conflict first */
+	almanah_entry_get_date (imported_entry, &entry_date);
+	existing_entry = almanah_storage_manager_get_entry (almanah->storage_manager, &entry_date);
+
+	if (existing_entry == NULL) {
+		/* Add the entry to the proper database and return, ignoring failure */
+		almanah_storage_manager_set_entry (almanah->storage_manager, imported_entry);
+		return ALMANAH_IMPORT_STATUS_IMPORTED;
+	}
+
+	/* Load both entries into buffers */
+	imported_buffer = gtk_text_buffer_new (NULL);
+	if (almanah_entry_get_content (imported_entry, imported_buffer, TRUE, &error) == FALSE) {
+		if (message != NULL)
+			*message = g_strdup_printf (_("Error deserializing imported entry into buffer: %s"), (error != NULL) ? error->message : NULL);
+
+		g_error_free (error);
+		g_object_unref (imported_buffer);
+		g_object_unref (existing_entry);
+		return ALMANAH_IMPORT_STATUS_FAILED;
+	}
+
+	/* The two buffers have to use the same tag table for gtk_text_buffer_insert_range() to work */
+	existing_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (imported_buffer));
+	if (almanah_entry_get_content (existing_entry, existing_buffer, FALSE, NULL) == FALSE) {
+		/* Deserialising the existing entry failed; use the imported entry instead */
+		almanah_storage_manager_set_entry (almanah->storage_manager, imported_entry);
+
+		if (message != NULL) {
+			*message = g_strdup_printf (_("Error deserializing existing entry into buffer; overwriting with imported entry: %s"),
+						    (error != NULL) ? error->message : NULL);
+		}
+
+		g_error_free (error);
+		g_object_unref (imported_buffer);
+		g_object_unref (existing_buffer);
+		g_object_unref (existing_entry);
+		return ALMANAH_IMPORT_STATUS_IMPORTED;
+	}
+
+	/* Append some header text for the imported entry */
+	header_string = g_strdup_printf (_("\n\nEntry imported from \"%s\":\n\n"), import_source);
+	gtk_text_buffer_get_end_iter (existing_buffer, &existing_end);
+	gtk_text_buffer_insert (existing_buffer, &existing_end, header_string, -1);
+	g_free (header_string);
+
+	/* Append the imported entry to the end of the existing one */
+	gtk_text_buffer_get_bounds (imported_buffer, &imported_start, &imported_end);
+	gtk_text_buffer_insert_range (existing_buffer, &existing_end, &imported_start, &imported_end);
+	g_object_unref (imported_buffer);
+
+	/* Store the buffer back in the existing entry and save the entry */
+	almanah_entry_set_content (existing_entry, existing_buffer);
+	g_object_unref (existing_buffer);
+
+	almanah_storage_manager_set_entry (almanah->storage_manager, existing_entry);
+	g_object_unref (existing_entry);
+
+	return ALMANAH_IMPORT_STATUS_MERGED;
+}
+
+static gboolean
+import_text_files (AlmanahImportDialog *self, AlmanahImportResultsDialog *results_dialog, GError **error)
+{
+	gboolean retval = FALSE;
+	GFile *folder;
+	GFileInfo *file_info;
+	GFileEnumerator *enumerator;
+	GtkTextBuffer *buffer;
+	GError *child_error = NULL;
+	AlmanahImportDialogPrivate *priv = self->priv;
+
+	/* Get the folder containing all the files to import */
+	folder = gtk_file_chooser_get_file (priv->file_chooser);
+	g_assert (folder != NULL);
+
+	enumerator = g_file_enumerate_children (folder, "standard::name,standard::display-name,standard::is-hidden", G_FILE_QUERY_INFO_NONE,
+						NULL, error);
+	g_object_unref (folder);
+	if (enumerator == NULL)
+		return FALSE;
+
+	/* Build a text buffer to use when setting all the entries */
+	buffer = gtk_text_buffer_new (NULL);
+
+	/* Enumerate all the children of the folder */
+	while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &child_error)) != NULL) {
+		AlmanahEntry *entry;
+		GDate parsed_date;
+		GFile *file;
+		gchar *contents, *message = NULL;
+		gsize length;
+		AlmanahImportStatus status;
+		const gchar *file_name = g_file_info_get_name (file_info);
+		const gchar *display_name = g_file_info_get_display_name (file_info);
+
+		/* Skip the file if it's hidden */
+		if (g_file_info_get_is_hidden (file_info) == TRUE || file_name[strlen (file_name) - 1] == '~') {
+			g_object_unref (file_info);
+			continue;
+		}
+
+		/* Heuristically parse the date, though we recommend using the format: yyyy-mm-dd */
+		g_date_set_parse (&parsed_date, file_name);
+		if (g_date_valid (&parsed_date) == FALSE) {
+			g_object_unref (file_info);
+			continue;
+		}
+
+		/* Get the file */
+		file = g_file_get_child (folder, file_name);
+		g_assert (file != NULL);
+
+		/* Load the content */
+		if (g_file_load_contents (file, NULL, &contents, &length, NULL, error) == FALSE) {
+			g_object_unref (file);
+			g_object_unref (file_info);
+			break; /* let the error get handled by the code just after the loop */
+		}
+		g_object_unref (file);
+
+		/* Create the relevant entry */
+		entry = almanah_entry_new (&parsed_date);
+
+		/* Set the content on the entry */
+		gtk_text_buffer_set_text (buffer, contents, length);
+		almanah_entry_set_content (entry, buffer);
+		g_free (contents);
+
+		/* Store the entry */
+		status = set_entry (self, entry, display_name, &message);
+		almanah_import_results_dialog_add_result (results_dialog, &parsed_date, status, message);
+		g_free (message);
+
+		g_object_unref (entry);
+		g_object_unref (file_info);
+	}
+
+	/* Check if the loop was broken due to an error */
+	if (child_error != NULL) {
+		g_propagate_error (error, child_error);
+		goto finish;
+	}
+
+	/* Success! */
+	retval = TRUE;
+
+finish:
+	g_object_unref (folder);
+	g_object_unref (enumerator);
+	g_object_unref (buffer);
+
+	return retval;
+}
+
+static gboolean
+import_database (AlmanahImportDialog *self, AlmanahImportResultsDialog *results_dialog, GError **error)
+{
+	GFile *file;
+	GFileInfo *file_info;
+	gchar *path;
+	const gchar *display_name;
+	GSList *entries, *i, *definitions;
+	AlmanahStorageManager *database;
+	AlmanahImportDialogPrivate *priv = self->priv;
+
+	/* Get the database file to import */
+	file = gtk_file_chooser_get_file (priv->file_chooser);
+	g_assert (file != NULL);
+
+	/* Get the display name for use with set_entry(), below */
+	file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+	display_name = g_file_info_get_display_name (file_info);
+
+	/* Open the database */
+	path = g_file_get_path (file);
+	database = almanah_storage_manager_new (path);
+	g_free (path);
+	g_object_unref (file);
+
+	/* Connect to the database */
+	if (almanah_storage_manager_connect (database, error) == FALSE) {
+		g_object_unref (database);
+		return FALSE;
+	}
+
+	/* Query for every entry */
+	entries = almanah_storage_manager_get_entries (database);
+	for (i = entries; i != NULL; i = i->next) {
+		GDate date;
+		gchar *message = NULL;
+		AlmanahImportStatus status;
+		AlmanahEntry *entry = ALMANAH_ENTRY (i->data);
+
+		almanah_entry_get_date (entry, &date);
+
+		status = set_entry (self, entry, display_name, &message);
+		almanah_import_results_dialog_add_result (results_dialog, &date, status, message);
+		g_free (message);
+
+		g_object_unref (entry);
+	}
+	g_slist_free (entries);
+
+	/* Query for every definition */
+	definitions = almanah_storage_manager_get_definitions (database);
+	for (i = definitions; i != NULL; i = i->next) {
+		/* Add the definition to the proper database, ignoring failure */
+		almanah_storage_manager_add_definition (almanah->storage_manager, ALMANAH_DEFINITION (i->data));
+		g_object_unref (i->data);
+	}
+	g_slist_free (definitions);
+
+	almanah_storage_manager_disconnect (database, NULL);
+	g_object_unref (database);
+	g_object_unref (file_info);
+
+	return TRUE;
+}
+
+static void
+response_cb (GtkDialog *dialog, gint response_id, AlmanahImportDialog *self)
+{
+	GError *error = NULL;
+	AlmanahImportDialogPrivate *priv = self->priv;
+	AlmanahImportResultsDialog *results_dialog;
+
+	/* Just return if the user pressed Cancel */
+	if (response_id != GTK_RESPONSE_OK) {
+		gtk_widget_hide (GTK_WIDGET (self));
+		return;
+	}
+
+	/* Import the entries according to the selected method. It's OK if we block here, since the dialogue should
+	 * be running in its own main loop. */
+	results_dialog = almanah_import_results_dialog_new ();
+	switch (priv->current_mode) {
+		case MODE_TEXT_FILES:
+			import_text_files (self, results_dialog, &error);
+			break;
+		case MODE_DATABASE:
+			import_database (self, results_dialog, &error);
+			break;
+		default:
+			g_assert_not_reached ();
+	}
+
+	/* Check for errors (e.g. errors opening databases or files; not errors importing individual entries once we have the content to import) */
+	if (error != NULL) {
+		/* Show an error */
+		GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
+								  GTK_BUTTONS_OK, _("Import failed"));
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
+		gtk_dialog_run (GTK_DIALOG (error_dialog));
+		gtk_widget_destroy (error_dialog);
+
+		g_error_free (error);
+
+		gtk_widget_hide (GTK_WIDGET (self));
+	} else {
+		/* Show the results dialogue */
+		gtk_widget_hide (GTK_WIDGET (self));
+		gtk_widget_show_all (GTK_WIDGET (results_dialog));
+		gtk_dialog_run (GTK_DIALOG (results_dialog));
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (results_dialog));
+}
+
+void
+id_mode_combo_box_changed_cb (GtkComboBox *combo_box, AlmanahImportDialog *self)
+{
+	gint new_mode;
+	AlmanahImportDialogPrivate *priv = self->priv;
+
+	new_mode = gtk_combo_box_get_active (combo_box);
+	if (new_mode == -1 || new_mode == (gint) priv->current_mode)
+		return;
+
+	priv->current_mode = new_mode;
+
+	/* Create the new widgets */
+	switch (priv->current_mode) {
+		case MODE_TEXT_FILES:
+			gtk_file_chooser_set_action (priv->file_chooser, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+			gtk_label_set_text (priv->description_label, _("Select a folder containing text files, one per entry, with names in the "
+								       "format 'yyyy-mm-dd', and no extension. Any and all such files will be "
+								       "imported."));
+			break;
+		case MODE_DATABASE:
+			gtk_file_chooser_set_action (priv->file_chooser, GTK_FILE_CHOOSER_ACTION_OPEN);
+			gtk_label_set_text (priv->description_label, _("Select a database file created by Almanah Diary to import."));
+			break;
+		default:
+			g_assert_not_reached ();
+	}
+}
+
+void
+id_file_chooser_selection_changed_cb (GtkFileChooser *file_chooser, AlmanahImportDialog *self)
+{
+	GFile *current_file;
+
+	/* Change the sensitivity of the dialogue's buttons based on whether a file is selected */
+	current_file = gtk_file_chooser_get_file (file_chooser);
+	if (current_file == NULL) {
+		gtk_widget_set_sensitive (self->priv->import_button, FALSE);
+	} else {
+		gtk_widget_set_sensitive (self->priv->import_button, TRUE);
+		g_object_unref (current_file);
+	}
+}
+
+void
+id_file_chooser_file_activated_cb (GtkFileChooser *file_chooser, AlmanahImportDialog *self)
+{
+	/* Activate the dialogue's default button */
+	gtk_window_activate_default (GTK_WINDOW (self));
+}
+
+static gboolean filter_results_cb (GtkTreeModel *model, GtkTreeIter *iter, AlmanahImportResultsDialog *self);
+static void results_selection_changed_cb (GtkTreeSelection *tree_selection, GtkWidget *button);
+
+/* GtkBuilder callbacks */
+void ird_results_tree_view_row_activated_cb (GtkTreeView *self, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data);
+void ird_view_button_clicked_cb (GtkButton *button, AlmanahImportResultsDialog *self);
+void ird_view_combo_box_changed_cb (GtkComboBox *combo_box, AlmanahImportResultsDialog *self);
+
+struct _AlmanahImportResultsDialogPrivate {
+	GtkListStore *results_store;
+	GtkTreeSelection *results_selection;
+	GtkTreeModelFilter *filtered_results_store;
+	GtkComboBox *view_combo_box;
+	AlmanahImportStatus current_mode;
+};
+
+G_DEFINE_TYPE (AlmanahImportResultsDialog, almanah_import_results_dialog, GTK_TYPE_DIALOG)
+#define ALMANAH_IMPORT_RESULTS_DIALOG_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ALMANAH_TYPE_IMPORT_RESULTS_DIALOG,\
+							AlmanahImportResultsDialogPrivate))
+
+static void
+almanah_import_results_dialog_class_init (AlmanahImportResultsDialogClass *klass)
+{
+	g_type_class_add_private (klass, sizeof (AlmanahImportResultsDialogPrivate));
+}
+
+static void
+almanah_import_results_dialog_init (AlmanahImportResultsDialog *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_IMPORT_RESULTS_DIALOG, AlmanahImportResultsDialogPrivate);
+
+	g_signal_connect (self, "response", G_CALLBACK (response_cb), self);
+	g_signal_connect (self, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), self);
+	gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+	gtk_window_set_resizable (GTK_WINDOW (self), TRUE);
+	gtk_window_set_title (GTK_WINDOW (self), _("Import Results"));
+	gtk_window_set_default_size (GTK_WINDOW (self), 600, 400);
+	gtk_window_set_modal (GTK_WINDOW (self), FALSE);
+}
+
+AlmanahImportResultsDialog *
+almanah_import_results_dialog_new (void)
+{
+	GtkBuilder *builder;
+	AlmanahImportResultsDialog *results_dialog;
+	AlmanahImportResultsDialogPrivate *priv;
+	GError *error = NULL;
+	const gchar *interface_filename = almanah_get_interface_filename ();
+	const gchar *object_names[] = {
+		"almanah_ird_view_store",
+		"almanah_ird_results_store",
+		"almanah_ird_filtered_results_store",
+		"almanah_import_results_dialog",
+		NULL
+	};
+
+	builder = gtk_builder_new ();
+
+	if (gtk_builder_add_objects_from_file (builder, interface_filename, (gchar**) object_names, &error) == FALSE) {
+		/* Show an error */
+		GtkWidget *dialog = gtk_message_dialog_new (NULL,
+				GTK_DIALOG_MODAL,
+				GTK_MESSAGE_ERROR,
+				GTK_BUTTONS_OK,
+				_("UI file \"%s\" could not be loaded"), interface_filename);
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+		gtk_dialog_run (GTK_DIALOG (dialog));
+		gtk_widget_destroy (dialog);
+
+		g_error_free (error);
+		g_object_unref (builder);
+
+		return NULL;
+	}
+
+	gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
+	results_dialog = ALMANAH_IMPORT_RESULTS_DIALOG (gtk_builder_get_object (builder, "almanah_import_results_dialog"));
+	gtk_builder_connect_signals (builder, results_dialog);
+
+	if (results_dialog == NULL) {
+		g_object_unref (builder);
+		return NULL;
+	}
+
+	priv = results_dialog->priv;
+
+	/* Grab our child widgets */
+	priv->results_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "almanah_ird_results_store"));
+	priv->results_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (gtk_builder_get_object (builder, "almanah_ird_results_tree_view")));
+	priv->filtered_results_store = GTK_TREE_MODEL_FILTER (gtk_builder_get_object (builder, "almanah_ird_filtered_results_store"));
+	priv->view_combo_box = GTK_COMBO_BOX (gtk_builder_get_object (builder, "almanah_ird_view_combo_box"));
+
+	g_signal_connect (priv->results_selection, "changed", G_CALLBACK (results_selection_changed_cb),
+			  gtk_builder_get_object (builder, "almanah_ird_view_button"));
+
+	/* Set up the tree filter */
+	gtk_tree_model_filter_set_visible_func (priv->filtered_results_store, (GtkTreeModelFilterVisibleFunc) filter_results_cb, results_dialog, NULL);
+
+	/* Set up the combo box */
+	gtk_combo_box_set_active (priv->view_combo_box, ALMANAH_IMPORT_STATUS_MERGED);
+
+	g_object_unref (builder);
+
+	return results_dialog;
+}
+
+static gboolean
+filter_results_cb (GtkTreeModel *model, GtkTreeIter *iter, AlmanahImportResultsDialog *self)
+{
+	guint status;
+
+	/* Compare the current mode to the row's status column */
+	gtk_tree_model_get (model, iter, 4, &status, -1);
+
+	return (self->priv->current_mode == status) ? TRUE : FALSE;
+}
+
+static void
+results_selection_changed_cb (GtkTreeSelection *tree_selection, GtkWidget *button)
+{
+	gtk_widget_set_sensitive (button, gtk_tree_selection_count_selected_rows (tree_selection) == 0 ? FALSE : TRUE);
+}
+
+void
+almanah_import_results_dialog_add_result (AlmanahImportResultsDialog *self, GDate *date, AlmanahImportStatus status, const gchar *message)
+{
+	GtkTreeIter iter;
+	gchar formatted_date[100];
+
+	/* Translators: This is a strftime()-format string for the dates displayed in import results. */
+	g_date_strftime (formatted_date, sizeof (formatted_date), _("%A, %e %B %Y"), date);
+
+	gtk_list_store_append (self->priv->results_store, &iter);
+	gtk_list_store_set (self->priv->results_store, &iter,
+			    0, g_date_get_day (date),
+			    1, g_date_get_month (date),
+			    2, g_date_get_year (date),
+			    3, &formatted_date,
+			    4, status,
+			    5, message,
+			    -1);
+}
+
+static void
+select_date (GtkTreeModel *model, GtkTreeIter *iter)
+{
+	guint day, month, year;
+	GDate date;
+
+	gtk_tree_model_get (model, iter,
+			    0, &day,
+			    1, &month,
+			    2, &year,
+			    -1);
+
+	g_date_set_dmy (&date, day, month, year);
+	almanah_main_window_select_date (ALMANAH_MAIN_WINDOW (almanah->main_window), &date);
+}
+
+void
+ird_results_tree_view_row_activated_cb (GtkTreeView *self, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	model = gtk_tree_view_get_model (self);
+	gtk_tree_model_get_iter (model, &iter, path);
+	select_date (model, &iter);
+}
+
+void
+ird_view_button_clicked_cb (GtkButton *button, AlmanahImportResultsDialog *self)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	if (gtk_tree_selection_get_selected (self->priv->results_selection, &model, &iter) == TRUE)
+		select_date (model, &iter);
+}
+
+void
+ird_view_combo_box_changed_cb (GtkComboBox *combo_box, AlmanahImportResultsDialog *self)
+{
+	gint new_mode;
+	AlmanahImportResultsDialogPrivate *priv = self->priv;
+
+	new_mode = gtk_combo_box_get_active (combo_box);
+	if (new_mode == -1 || new_mode == (gint) priv->current_mode)
+		return;
+
+	priv->current_mode = new_mode;
+	gtk_tree_model_filter_refilter (self->priv->filtered_results_store);
+}
diff --git a/src/import-dialog.h b/src/import-dialog.h
new file mode 100644
index 0000000..5acd0ba
--- /dev/null
+++ b/src/import-dialog.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * Almanah 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.
+ *
+ * Almanah 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 Almanah.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ALMANAH_IMPORT_DIALOG_H
+#define ALMANAH_IMPORT_DIALOG_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* These must be kept in synchrony with the rows in almanah_ird_view_store in almanah.ui */
+typedef enum {
+	ALMANAH_IMPORT_STATUS_IMPORTED = 0,
+	ALMANAH_IMPORT_STATUS_MERGED = 1,
+	ALMANAH_IMPORT_STATUS_FAILED = 2
+} AlmanahImportStatus;
+
+#define ALMANAH_TYPE_IMPORT_DIALOG		(almanah_import_dialog_get_type ())
+#define ALMANAH_IMPORT_DIALOG(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_IMPORT_DIALOG, AlmanahImportDialog))
+#define ALMANAH_IMPORT_DIALOG_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_IMPORT_DIALOG, AlmanahImportDialogClass))
+#define ALMANAH_IS_IMPORT_DIALOG(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), ALMANAH_TYPE_IMPORT_DIALOG))
+#define ALMANAH_IS_IMPORT_DIALOG_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), ALMANAH_TYPE_IMPORT_DIALOG))
+#define ALMANAH_IMPORT_DIALOG_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), ALMANAH_TYPE_IMPORT_DIALOG, AlmanahImportDialogClass))
+
+typedef struct _AlmanahImportDialogPrivate	AlmanahImportDialogPrivate;
+
+typedef struct {
+	GtkDialog parent;
+	AlmanahImportDialogPrivate *priv;
+} AlmanahImportDialog;
+
+typedef struct {
+	GtkDialogClass parent;
+} AlmanahImportDialogClass;
+
+GType almanah_import_dialog_get_type (void) G_GNUC_CONST;
+
+AlmanahImportDialog *almanah_import_dialog_new (void) G_GNUC_WARN_UNUSED_RESULT;
+
+#define ALMANAH_TYPE_IMPORT_RESULTS_DIALOG		(almanah_import_results_dialog_get_type ())
+#define ALMANAH_IMPORT_RESULTS_DIALOG(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_IMPORT_RESULTS_DIALOG, AlmanahImportResultsDialog))
+#define ALMANAH_IMPORT_RESULTS_DIALOG_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_IMPORT_RESULTS_DIALOG, AlmanahImportResultsDialogClass))
+#define ALMANAH_IS_IMPORT_RESULTS_DIALOG(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), ALMANAH_TYPE_IMPORT_RESULTS_DIALOG))
+#define ALMANAH_IS_IMPORT_RESULTS_DIALOG_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), ALMANAH_TYPE_IMPORT_RESULTS_DIALOG))
+#define ALMANAH_IMPORT_RESULTS_DIALOG_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), ALMANAH_TYPE_IMPORT_RESULTS_DIALOG, AlmanahImportResultsDialogClass))
+
+typedef struct _AlmanahImportResultsDialogPrivate	AlmanahImportResultsDialogPrivate;
+
+typedef struct {
+	GtkDialog parent;
+	AlmanahImportResultsDialogPrivate *priv;
+} AlmanahImportResultsDialog;
+
+typedef struct {
+	GtkDialogClass parent;
+} AlmanahImportResultsDialogClass;
+
+GType almanah_import_results_dialog_get_type (void) G_GNUC_CONST;
+
+AlmanahImportResultsDialog *almanah_import_results_dialog_new (void) G_GNUC_WARN_UNUSED_RESULT;
+void almanah_import_results_dialog_add_result (AlmanahImportResultsDialog *self, GDate *date, AlmanahImportStatus status, const gchar *message);
+
+G_END_DECLS
+
+#endif /* !ALMANAH_IMPORT_DIALOG_H */
diff --git a/src/main-window.c b/src/main-window.c
index bd5ef31..eaa88af 100644
--- a/src/main-window.c
+++ b/src/main-window.c
@@ -2,7 +2,7 @@
 /*
  * Almanah
  * Copyright (C) Philip Withnall 2008-2009 <philip tecnocode co uk>
- * 
+ *
  * Almanah 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
@@ -40,6 +40,7 @@
 #include "event.h"
 #include "definition.h"
 #include "definition-manager-window.h"
+#include "import-dialog.h"
 
 static void almanah_main_window_dispose (GObject *object);
 static void save_window_state (AlmanahMainWindow *self);
@@ -60,6 +61,7 @@ static void mw_definition_removed_cb (AlmanahStorageManager *storage_manager, co
 
 /* GtkBuilder callbacks */
 void mw_calendar_day_selected_cb (GtkCalendar *calendar, AlmanahMainWindow *main_window);
+void mw_import_activate_cb (GtkAction *action, AlmanahMainWindow *main_window);
 void mw_page_setup_activate_cb (GtkAction *action, AlmanahMainWindow *main_window);
 void mw_print_preview_activate_cb (GtkAction *action, AlmanahMainWindow *main_window);
 void mw_print_activate_cb (GtkAction *action, AlmanahMainWindow *main_window);
@@ -522,7 +524,7 @@ remove_definition_from_current_entry (AlmanahMainWindow *self)
 		end_iter = start_iter;
 		gtk_text_iter_forward_to_tag_toggle (&end_iter, tag);
 	}
-		
+
 	/* Remove the tag */
 	gtk_text_buffer_remove_tag (priv->entry_buffer, tag, &start_iter, &end_iter);
 	gtk_text_buffer_set_modified (priv->entry_buffer, TRUE);
@@ -696,6 +698,16 @@ mw_delete_event_cb (GtkWindow *window, gpointer user_data)
 }
 
 void
+mw_import_activate_cb (GtkAction *action, AlmanahMainWindow *main_window)
+{
+	if (almanah->import_dialog == NULL)
+		almanah->import_dialog = GTK_WIDGET (almanah_import_dialog_new ());
+
+	gtk_widget_show_all (almanah->import_dialog);
+	gtk_dialog_run (GTK_DIALOG (almanah->import_dialog));
+}
+
+void
 mw_page_setup_activate_cb (GtkAction *action, AlmanahMainWindow *main_window)
 {
 	almanah_print_page_setup ();
diff --git a/src/main.c b/src/main.c
index ac1db72..5c5e0de 100644
--- a/src/main.c
+++ b/src/main.c
@@ -73,6 +73,8 @@ almanah_quit (void)
 		gtk_widget_destroy (almanah->search_dialog);
 	if (almanah->date_entry_dialog != NULL)
 		gtk_widget_destroy (almanah->date_entry_dialog);
+	if (almanah->import_dialog != NULL)
+		gtk_widget_destroy (almanah->import_dialog);
 #ifdef ENABLE_ENCRYPTION
 	if (almanah->preferences_dialog != NULL)
 		gtk_widget_destroy (almanah->preferences_dialog);
diff --git a/src/main.h b/src/main.h
index 08f9d50..8010a97 100644
--- a/src/main.h
+++ b/src/main.h
@@ -46,6 +46,7 @@ typedef struct {
 	GtkWidget *search_dialog;
 	GtkWidget *date_entry_dialog;
 	GtkWidget *definition_manager_window;
+	GtkWidget *import_dialog;
 #ifdef ENABLE_ENCRYPTION
 	GtkWidget *preferences_dialog;
 #endif /* ENABLE_ENCRYPTION */
diff --git a/src/storage-manager.c b/src/storage-manager.c
index 4b095dd..f69189f 100644
--- a/src/storage-manager.c
+++ b/src/storage-manager.c
@@ -35,6 +35,8 @@
 #include "storage-manager.h"
 #include "almanah-marshal.h"
 
+#define ENCRYPTED_SUFFIX ".encrypted"
+
 static void almanah_storage_manager_finalize (GObject *object);
 static void almanah_storage_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void almanah_storage_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
@@ -126,12 +128,25 @@ almanah_storage_manager_init (AlmanahStorageManager *self)
  *
  * Creates a new #AlmanahStorageManager, connected to the given database @filename.
  *
+ * If @filename is for an encrypted database, it will automatically be changed to the canonical filename for the
+ * unencrypted database, even if that file doesn't exist, and even if Almanah was compiled without encryption support.
+ * Database filenames are always passed as the unencrypted filename.
+ *
  * Return value: the new #AlmanahStorageManager
  **/
 AlmanahStorageManager *
 almanah_storage_manager_new (const gchar *filename)
 {
-	return g_object_new (ALMANAH_TYPE_STORAGE_MANAGER, "filename", filename, NULL);
+	gchar *new_filename = NULL;
+	AlmanahStorageManager *sm;
+
+	if (g_str_has_suffix (filename, ENCRYPTED_SUFFIX) == TRUE)
+		filename = new_filename = g_strndup (filename, strlen (filename) - strlen (ENCRYPTED_SUFFIX));
+
+	sm = g_object_new (ALMANAH_TYPE_STORAGE_MANAGER, "filename", filename, NULL);
+	g_free (new_filename);
+
+	return sm;
 }
 
 static void
@@ -170,7 +185,7 @@ almanah_storage_manager_set_property (GObject *object, guint property_id, const
 	switch (property_id) {
 		case PROP_FILENAME:
 			priv->plain_filename = g_strdup (g_value_get_string (value));
-			priv->filename = g_strjoin (NULL, priv->plain_filename, ".encrypted", NULL);
+			priv->filename = g_strjoin (NULL, priv->plain_filename, ENCRYPTED_SUFFIX, NULL);
 			break;
 		default:
 			/* We don't have any other property... */
@@ -898,6 +913,61 @@ almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar
 	return result_count - 1;
 }
 
+/**
+ * almanah_storage_manager_get_entries:
+ * @self: an #AlmanahStorageManager
+ *
+ * Returns a list of all #AlmanahEntry<!-- -->s in the database.
+ *
+ * Return value: a #GSList of #AlmanahEntry<!-- -->s, or %NULL; unref elements with g_object_unref(); free list with g_slist_free()
+ **/
+GSList *
+almanah_storage_manager_get_entries (AlmanahStorageManager *self)
+{
+	GSList *entries = NULL;
+	int result;
+	sqlite3_stmt *statement;
+
+	/* Just as with almanah_storage_manager_get_entry(), it's necessary to avoid our nice SQLite interface
+	 * here. It's probably more efficient to avoid it anyway. */
+
+	/* Prepare the statement */
+	if (sqlite3_prepare_v2 (self->priv->connection,
+				"SELECT content, is_important, day, month, year FROM entries", -1,
+				&statement, NULL) != SQLITE_OK) {
+		return NULL;
+	}
+
+	/* Execute the statement */
+	while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
+		GDate date;
+		AlmanahEntry *entry;
+
+		g_date_set_dmy (&date,
+				sqlite3_column_int (statement, 2),
+				sqlite3_column_int (statement, 3),
+				sqlite3_column_int (statement, 4));
+
+		/* Get the data */
+		entry = almanah_entry_new (&date);
+		almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0));
+		almanah_entry_set_is_important (entry, (sqlite3_column_int (statement, 1) == 1) ? TRUE : FALSE);
+
+		entries = g_slist_prepend (entries, entry);
+	}
+
+	sqlite3_finalize (statement);
+
+	/* Check for errors */
+	if (result != SQLITE_DONE) {
+		g_slist_foreach (entries, (GFunc) g_object_unref, NULL);
+		g_slist_free (entries);
+		return NULL;
+	}
+
+	return g_slist_reverse (entries);
+}
+
 /* NOTE: Free results with g_free. Return value is 0-based. */
 gboolean *
 almanah_storage_manager_get_month_marked_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days)
@@ -930,46 +1000,46 @@ almanah_storage_manager_get_month_marked_days (AlmanahStorageManager *self, GDat
  * almanah_storage_manager_get_definitions:
  * @self: an #AlmanahStorageManager
  *
- * Returns a %NULL-terminated array of all the #AlmanahDefinitions in the
- * database. Each #AlmanahDefinition should be unreffed, and the array should
- * be freed with g_free().
- *
- * On error, an array with a single %NULL element will be returned. The array
- * should still be freed with g_free().
+ * Returns a list of all #AlmanahDefinition<!-- -->s in the database.
  *
- * Return value: a %NULL-terminated array of #AlmanahDefinitions
+ * Return value: a #GSList of #AlmanahDefinition<!-- -->s, or %NULL; unref elements with g_object_unref(); free list with g_slist_free()
  **/
-AlmanahDefinition **
+GSList *
 almanah_storage_manager_get_definitions (AlmanahStorageManager *self)
 {
-	AlmanahQueryResults *results;
-	AlmanahDefinition **definitions;
-	gint i;
+	GSList *definitions = NULL;
+	int result;
+	sqlite3_stmt *statement;
 
-	results = almanah_storage_manager_query (self, "SELECT definition_type, definition_value, definition_value2, definition_text FROM definitions", NULL);
+	/* It's more efficient to avoid our nice SQLite interface and do things manually. */
 
-	if (results == NULL || results->rows == 0) {
-		if (results != NULL)
-			almanah_storage_manager_free_results (results);
-
-		/* Return empty array */
-		definitions = (AlmanahDefinition**) g_new (AlmanahDefinition*, 1);
-		definitions[0] = NULL;
-		return definitions;
+	/* Prepare the statement */
+	if (sqlite3_prepare_v2 (self->priv->connection,
+				"SELECT definition_type, definition_value, definition_value2, definition_text FROM definitions", -1,
+				&statement, NULL) != SQLITE_OK) {
+		return NULL;
 	}
 
-	definitions = (AlmanahDefinition**) g_new (AlmanahDefinition*, results->rows + 1);
-	for (i = 0; i < results->rows; i++) {
-		definitions[i] = almanah_definition_new (atoi (results->data[(i + 1) * results->columns]));
-		almanah_definition_set_value (definitions[i], results->data[(i + 1) * results->columns + 1]);
-		almanah_definition_set_value2 (definitions[i], results->data[(i + 1) * results->columns + 2]);
-		almanah_definition_set_text (definitions[i], results->data[(i + 1) * results->columns + 3]);
+	/* Execute the statement */
+	while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
+		AlmanahDefinition *definition = almanah_definition_new (sqlite3_column_int (statement, 0));
+		almanah_definition_set_value (definition, (gchar*) sqlite3_column_text (statement, 1));
+		almanah_definition_set_value2 (definition, (gchar*) sqlite3_column_text (statement, 2));
+		almanah_definition_set_text (definition, (gchar*) sqlite3_column_text (statement, 3));
+
+		definitions = g_slist_prepend (definitions, definition);
 	}
-	definitions[i] = NULL;
 
-	almanah_storage_manager_free_results (results);
+	sqlite3_finalize (statement);
+
+	/* Check for errors */
+	if (result != SQLITE_DONE) {
+		g_slist_foreach (definitions, (GFunc) g_object_unref, NULL);
+		g_slist_free (definitions);
+		return NULL;
+	}
 
-	return definitions;
+	return g_slist_reverse (definitions);
 }
 
 /* Note: this function is case-insensitive, unless the definition text contains Unicode characters
diff --git a/src/storage-manager.h b/src/storage-manager.h
index 3bb84e8..14a3cf9 100644
--- a/src/storage-manager.h
+++ b/src/storage-manager.h
@@ -82,10 +82,11 @@ gboolean almanah_storage_manager_entry_exists (AlmanahStorageManager *self, GDat
 AlmanahEntry *almanah_storage_manager_get_entry (AlmanahStorageManager *self, GDate *date);
 gboolean almanah_storage_manager_set_entry (AlmanahStorageManager *self, AlmanahEntry *entry);
 gint almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string, GDate *matches[]);
+GSList *almanah_storage_manager_get_entries (AlmanahStorageManager *self) G_GNUC_WARN_UNUSED_RESULT;
 
 gboolean *almanah_storage_manager_get_month_marked_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days);
 
-AlmanahDefinition **almanah_storage_manager_get_definitions (AlmanahStorageManager *self);
+GSList *almanah_storage_manager_get_definitions (AlmanahStorageManager *self);
 AlmanahDefinition *almanah_storage_manager_get_definition (AlmanahStorageManager *self, const gchar *definition_text);
 gboolean almanah_storage_manager_add_definition (AlmanahStorageManager *self, AlmanahDefinition *definition);
 gboolean almanah_storage_manager_remove_definition (AlmanahStorageManager *self, const gchar *definition_text);



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