summaryrefslogtreecommitdiff
path: root/pluma
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-11-07 19:52:18 -0300
committerPerberos <[email protected]>2011-11-07 19:52:18 -0300
commit5ded9cba8563f336939400303d6a841d5089b107 (patch)
treec5676588cff26ba37e12369fe4de24b54e9f6682 /pluma
parentf00b3a11a199f9f85a4d46a600f9d14179b37dbf (diff)
downloadpluma-5ded9cba8563f336939400303d6a841d5089b107.tar.bz2
pluma-5ded9cba8563f336939400303d6a841d5089b107.tar.xz
renaming from gedit to pluma
Diffstat (limited to 'pluma')
-rwxr-xr-xpluma/Makefile.am254
-rwxr-xr-xpluma/bacon-message-connection.c396
-rwxr-xr-xpluma/bacon-message-connection.h43
-rwxr-xr-xpluma/dialogs/Makefile.am31
-rwxr-xr-xpluma/dialogs/pluma-close-confirmation-dialog.c790
-rwxr-xr-xpluma/dialogs/pluma-close-confirmation-dialog.h75
-rwxr-xr-xpluma/dialogs/pluma-encodings-dialog.c499
-rwxr-xr-xpluma/dialogs/pluma-encodings-dialog.h86
-rwxr-xr-xpluma/dialogs/pluma-encodings-dialog.ui256
-rwxr-xr-xpluma/dialogs/pluma-preferences-dialog.c1189
-rwxr-xr-xpluma/dialogs/pluma-preferences-dialog.h87
-rwxr-xr-xpluma/dialogs/pluma-preferences-dialog.ui1107
-rwxr-xr-xpluma/dialogs/pluma-search-dialog.c634
-rwxr-xr-xpluma/dialogs/pluma-search-dialog.h128
-rwxr-xr-xpluma/dialogs/pluma-search-dialog.ui255
-rwxr-xr-xpluma/osx/Makefile.am23
-rwxr-xr-xpluma/osx/pluma-osx-delegate.h16
-rwxr-xr-xpluma/osx/pluma-osx-delegate.m84
-rwxr-xr-xpluma/osx/pluma-osx.c94
-rwxr-xr-xpluma/osx/pluma-osx.h17
-rwxr-xr-xpluma/pluma-app.c911
-rwxr-xr-xpluma/pluma-app.h142
-rwxr-xr-xpluma/pluma-close-button.c80
-rwxr-xr-xpluma/pluma-close-button.h56
-rwxr-xr-xpluma/pluma-commands-documents.c87
-rwxr-xr-xpluma/pluma-commands-edit.c174
-rwxr-xr-xpluma/pluma-commands-file-print.c91
-rwxr-xr-xpluma/pluma-commands-file.c1885
-rwxr-xr-xpluma/pluma-commands-help.c116
-rwxr-xr-xpluma/pluma-commands-search.c716
-rwxr-xr-xpluma/pluma-commands-view.c154
-rwxr-xr-xpluma/pluma-commands.h166
-rwxr-xr-xpluma/pluma-debug.c159
-rwxr-xr-xpluma/pluma-debug.h93
-rwxr-xr-xpluma/pluma-dirs.c320
-rwxr-xr-xpluma/pluma-dirs.h54
-rwxr-xr-xpluma/pluma-document-input-stream.c479
-rwxr-xr-xpluma/pluma-document-input-stream.h68
-rwxr-xr-xpluma/pluma-document-loader.c357
-rwxr-xr-xpluma/pluma-document-loader.h130
-rwxr-xr-xpluma/pluma-document-output-stream.c391
-rwxr-xr-xpluma/pluma-document-output-stream.h64
-rwxr-xr-xpluma/pluma-document-saver.c359
-rwxr-xr-xpluma/pluma-document-saver.h133
-rwxr-xr-xpluma/pluma-document.c2732
-rwxr-xr-xpluma/pluma-document.h338
-rwxr-xr-xpluma/pluma-documents-panel.c828
-rwxr-xr-xpluma/pluma-documents-panel.h85
-rwxr-xr-xpluma/pluma-encodings-combo-box.c468
-rwxr-xr-xpluma/pluma-encodings-combo-box.h78
-rwxr-xr-xpluma/pluma-encodings.c473
-rwxr-xr-xpluma/pluma-encodings.h62
-rwxr-xr-xpluma/pluma-enum-types.c.template39
-rwxr-xr-xpluma/pluma-enum-types.h.template27
-rwxr-xr-xpluma/pluma-file-chooser-dialog.c560
-rwxr-xr-xpluma/pluma-file-chooser-dialog.h89
-rwxr-xr-xpluma/pluma-gio-document-loader.c708
-rwxr-xr-xpluma/pluma-gio-document-loader.h79
-rwxr-xr-xpluma/pluma-gio-document-saver.c775
-rwxr-xr-xpluma/pluma-gio-document-saver.h76
-rwxr-xr-xpluma/pluma-help.c122
-rwxr-xr-xpluma/pluma-help.h44
-rwxr-xr-xpluma/pluma-history-entry.c632
-rwxr-xr-xpluma/pluma-history-entry.h96
-rwxr-xr-xpluma/pluma-io-error-message-area.c1288
-rwxr-xr-xpluma/pluma-io-error-message-area.h68
-rwxr-xr-xpluma/pluma-language-manager.c90
-rwxr-xr-xpluma/pluma-language-manager.h47
-rwxr-xr-xpluma/pluma-marshal.list13
-rwxr-xr-xpluma/pluma-message-area.c626
-rwxr-xr-xpluma/pluma-message-area.h129
-rwxr-xr-xpluma/pluma-message-bus.c1158
-rwxr-xr-xpluma/pluma-message-bus.h129
-rwxr-xr-xpluma/pluma-message-type.c526
-rwxr-xr-xpluma/pluma-message-type.h67
-rwxr-xr-xpluma/pluma-message.c593
-rwxr-xr-xpluma/pluma-message.h71
-rwxr-xr-xpluma/pluma-metadata-manager.c563
-rwxr-xr-xpluma/pluma-metadata-manager.h50
-rwxr-xr-xpluma/pluma-notebook.c1099
-rwxr-xr-xpluma/pluma-notebook.h143
-rwxr-xr-xpluma/pluma-object-module.c343
-rwxr-xr-xpluma/pluma-object-module.h94
-rwxr-xr-xpluma/pluma-panel.c950
-rwxr-xr-xpluma/pluma-panel.h130
-rwxr-xr-xpluma/pluma-plugin-info-priv.h68
-rwxr-xr-xpluma/pluma-plugin-info.c394
-rwxr-xr-xpluma/pluma-plugin-info.h63
-rwxr-xr-xpluma/pluma-plugin-loader.c131
-rwxr-xr-xpluma/pluma-plugin-loader.h106
-rwxr-xr-xpluma/pluma-plugin-manager.c889
-rwxr-xr-xpluma/pluma-plugin-manager.h83
-rwxr-xr-xpluma/pluma-plugin.c334
-rwxr-xr-xpluma/pluma-plugin.h240
-rwxr-xr-xpluma/pluma-plugins-engine.c861
-rwxr-xr-xpluma/pluma-plugins-engine.h107
-rwxr-xr-xpluma/pluma-prefs-manager-app.c1620
-rwxr-xr-xpluma/pluma-prefs-manager-app.h84
-rwxr-xr-xpluma/pluma-prefs-manager-private.h45
-rwxr-xr-xpluma/pluma-prefs-manager.c1241
-rwxr-xr-xpluma/pluma-prefs-manager.h423
-rwxr-xr-xpluma/pluma-print-job.c865
-rwxr-xr-xpluma/pluma-print-job.h133
-rwxr-xr-xpluma/pluma-print-preferences.ui497
-rwxr-xr-xpluma/pluma-print-preview.c1327
-rwxr-xr-xpluma/pluma-print-preview.h71
-rwxr-xr-xpluma/pluma-progress-message-area.c261
-rwxr-xr-xpluma/pluma-progress-message-area.h112
-rwxr-xr-xpluma/pluma-session.c601
-rwxr-xr-xpluma/pluma-session.h47
-rwxr-xr-xpluma/pluma-smart-charset-converter.c422
-rwxr-xr-xpluma/pluma-smart-charset-converter.h66
-rwxr-xr-xpluma/pluma-spinner.c989
-rwxr-xr-xpluma/pluma-spinner.h95
-rwxr-xr-xpluma/pluma-status-combo-box.c418
-rwxr-xr-xpluma/pluma-status-combo-box.h82
-rwxr-xr-xpluma/pluma-statusbar.c448
-rwxr-xr-xpluma/pluma-statusbar.h99
-rwxr-xr-xpluma/pluma-style-scheme-manager.c364
-rwxr-xr-xpluma/pluma-style-scheme-manager.h56
-rwxr-xr-xpluma/pluma-tab-label.c371
-rwxr-xr-xpluma/pluma-tab-label.h66
-rwxr-xr-xpluma/pluma-tab.c2832
-rwxr-xr-xpluma/pluma-tab.h165
-rwxr-xr-xpluma/pluma-ui.h188
-rwxr-xr-xpluma/pluma-ui.xml203
-rwxr-xr-xpluma/pluma-utils.c1546
-rwxr-xr-xpluma/pluma-utils.h162
-rwxr-xr-xpluma/pluma-view.c2181
-rwxr-xr-xpluma/pluma-view.h108
-rwxr-xr-xpluma/pluma-window-private.h124
-rwxr-xr-xpluma/pluma-window.c4798
-rwxr-xr-xpluma/pluma-window.h195
-rwxr-xr-xpluma/pluma.c766
-rwxr-xr-xpluma/pluma.rc1
-rwxr-xr-xpluma/plumatextregion.c647
-rwxr-xr-xpluma/plumatextregion.h88
-rwxr-xr-xpluma/smclient/Makefile.am52
-rwxr-xr-xpluma/smclient/eggdesktopfile.c1510
-rwxr-xr-xpluma/smclient/eggdesktopfile.h160
-rwxr-xr-xpluma/smclient/eggsmclient-osx.c235
-rwxr-xr-xpluma/smclient/eggsmclient-private.h53
-rwxr-xr-xpluma/smclient/eggsmclient-win32.c353
-rwxr-xr-xpluma/smclient/eggsmclient-xsmp.c1370
-rwxr-xr-xpluma/smclient/eggsmclient.c589
-rwxr-xr-xpluma/smclient/eggsmclient.h117
146 files changed, 62259 insertions, 0 deletions
diff --git a/pluma/Makefile.am b/pluma/Makefile.am
new file mode 100755
index 00000000..746a0100
--- /dev/null
+++ b/pluma/Makefile.am
@@ -0,0 +1,254 @@
+## Process this file with automake to produce Makefile.in
+SUBDIRS = dialogs smclient
+
+if OS_OSX
+SUBDIRS += osx
+endif
+
+bin_PROGRAMS = pluma
+
+noinst_LTLIBRARIES = libpluma.la
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ -I$(srcdir) \
+ -I$(srcdir)/smclient \
+ $(PLUMA_CFLAGS) \
+ $(IGE_MAC_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DLIBDIR=\""$(libdir)"\"
+
+pluma_SOURCES = \
+ pluma.c
+
+pluma_LDADD = libpluma.la $(PLUMA_LIBS) $(IGE_MAC_LIBS) $(EGG_SMCLIENT_LIBS)
+
+if PLATFORM_WIN32
+pluma_LDFLAGS = -Wl,--export-all-symbols -Wl,--out-implib,libpluma-$(PLUMA_API_VERSION).a
+if OS_WIN32
+pluma_LDFLAGS += -mwindows
+endif
+else
+pluma_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*"
+endif
+
+libpluma_la_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*"
+
+libpluma_la_LIBADD = \
+ dialogs/libdialogs.la \
+ smclient/libeggsmclient.la
+
+# PLUMA_LIBS must be the last to ensure correct order on some platforms
+libpluma_la_LIBADD += $(PLUMA_LIBS)
+
+if OS_OSX
+pluma_LDFLAGS += -framework Carbon
+
+libpluma_la_LIBADD += osx/libosx.la
+endif
+
+BUILT_SOURCES = \
+ pluma-enum-types.c \
+ pluma-enum-types.h \
+ pluma-marshal.c \
+ pluma-marshal.h
+
+if OS_WIN32
+pluma-res.o: pluma.rc
+ $(WINDRES) -i pluma.rc --input-format=rc -o pluma-res.o -O coff
+
+pluma_LDADD += pluma-res.o
+endif
+
+NOINST_H_FILES = \
+ pluma-close-button.h \
+ pluma-dirs.h \
+ pluma-document-input-stream.h \
+ pluma-document-loader.h \
+ pluma-document-output-stream.h \
+ pluma-document-saver.h \
+ pluma-documents-panel.h \
+ pluma-gio-document-loader.h \
+ pluma-gio-document-saver.h \
+ pluma-history-entry.h \
+ pluma-io-error-message-area.h \
+ pluma-language-manager.h \
+ pluma-object-module.h \
+ pluma-plugin-info.h \
+ pluma-plugin-info-priv.h \
+ pluma-plugin-loader.h \
+ pluma-plugin-manager.h \
+ pluma-plugins-engine.h \
+ pluma-prefs-manager-private.h \
+ pluma-print-job.h \
+ pluma-print-preview.h \
+ pluma-session.h \
+ pluma-smart-charset-converter.h \
+ pluma-style-scheme-manager.h \
+ pluma-tab-label.h \
+ plumatextregion.h \
+ pluma-ui.h \
+ pluma-window-private.h
+
+INST_H_FILES = \
+ pluma-app.h \
+ pluma-commands.h \
+ pluma-debug.h \
+ pluma-document.h \
+ pluma-encodings.h \
+ pluma-encodings-combo-box.h \
+ pluma-file-chooser-dialog.h \
+ pluma-help.h \
+ pluma-message-bus.h \
+ pluma-message-type.h \
+ pluma-message.h \
+ pluma-notebook.h \
+ pluma-panel.h \
+ pluma-plugin.h \
+ pluma-prefs-manager-app.h \
+ pluma-prefs-manager.h \
+ pluma-progress-message-area.h \
+ pluma-statusbar.h \
+ pluma-status-combo-box.h \
+ pluma-tab.h \
+ pluma-utils.h \
+ pluma-view.h \
+ pluma-window.h
+
+if !ENABLE_GVFS_METADATA
+INST_H_FILES += pluma-metadata-manager.h
+endif
+
+headerdir = $(prefix)/include/pluma-@PLUMA_API_VERSION@/pluma
+
+header_DATA = \
+ $(INST_H_FILES)
+
+
+libpluma_la_SOURCES = \
+ $(BUILT_SOURCES) \
+ $(BACON_FILES) \
+ $(POSIXIO_FILES) \
+ pluma-app.c \
+ pluma-close-button.c \
+ pluma-commands-documents.c \
+ pluma-commands-edit.c \
+ pluma-commands-file.c \
+ pluma-commands-file-print.c \
+ pluma-commands-help.c \
+ pluma-commands-search.c \
+ pluma-commands-view.c \
+ pluma-debug.c \
+ pluma-dirs.c \
+ pluma-document.c \
+ pluma-document-input-stream.c \
+ pluma-document-loader.c \
+ pluma-document-output-stream.c \
+ pluma-gio-document-loader.c \
+ pluma-document-saver.c \
+ pluma-gio-document-saver.c \
+ pluma-documents-panel.c \
+ pluma-encodings.c \
+ pluma-encodings-combo-box.c \
+ pluma-file-chooser-dialog.c \
+ pluma-help.c \
+ pluma-history-entry.c \
+ pluma-io-error-message-area.c \
+ pluma-language-manager.c \
+ pluma-message-bus.c \
+ pluma-message-type.c \
+ pluma-message.c \
+ pluma-object-module.c \
+ pluma-notebook.c \
+ pluma-panel.c \
+ pluma-plugin-info.c \
+ pluma-plugin.c \
+ pluma-plugin-loader.c \
+ pluma-plugin-manager.c \
+ pluma-plugins-engine.c \
+ pluma-prefs-manager-app.c \
+ pluma-prefs-manager.c \
+ pluma-prefs-manager-private.h \
+ pluma-print-job.c \
+ pluma-print-preview.c \
+ pluma-progress-message-area.c \
+ pluma-session.c \
+ pluma-smart-charset-converter.c \
+ pluma-statusbar.c \
+ pluma-status-combo-box.c \
+ pluma-style-scheme-manager.c \
+ pluma-tab.c \
+ pluma-tab-label.c \
+ pluma-utils.c \
+ pluma-view.c \
+ pluma-window.c \
+ plumatextregion.c \
+ $(NOINST_H_FILES) \
+ $(INST_H_FILES)
+
+if !ENABLE_GVFS_METADATA
+libpluma_la_SOURCES += pluma-metadata-manager.c
+endif
+
+pluma-enum-types.h: pluma-enum-types.h.template $(INST_H_FILES) $(GLIB_MKENUMS)
+ $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template pluma-enum-types.h.template $(INST_H_FILES)) > $@
+
+pluma-enum-types.c: pluma-enum-types.c.template $(INST_H_FILES) $(GLIB_MKENUMS)
+ $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template pluma-enum-types.c.template $(INST_H_FILES)) > $@
+
+pluma-marshal.h: pluma-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=pluma_marshal > $@
+
+pluma-marshal.c: pluma-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN) echo "#include \"pluma-marshal.h\"" > $@ && \
+ $(GLIB_GENMARSHAL) $< --body --prefix=pluma_marshal >> $@
+
+uidir = $(datadir)/pluma-2/ui/
+ui_DATA = \
+ pluma-ui.xml \
+ pluma-print-preferences.ui
+
+EXTRA_DIST = \
+ $(ui_DATA) \
+ pluma-enum-types.h.template \
+ pluma-enum-types.c.template \
+ pluma-marshal.list \
+ pluma.rc
+
+CLEANFILES = $(BUILT_SOURCES)
+
+dist-hook:
+ cd $(distdir); rm -f $(BUILT_SOURCES)
+
+install-exec-hook:
+if PLATFORM_WIN32
+ $(mkinstalldirs) "$(DESTDIR)$(libdir)"
+ $(INSTALL_DATA) libpluma-$(PLUMA_API_VERSION).a "$(DESTDIR)$(libdir)"
+else
+ rm -f $(DESTDIR)$(bindir)/mate-text-editor
+ ln -s pluma $(DESTDIR)$(bindir)/mate-text-editor
+endif
+
+if !OS_WIN32
+BACON_DIR=$(srcdir)/../../libbacon/src/
+BACON_FILES=bacon-message-connection.h bacon-message-connection.c
+
+regenerate-built-sources:
+ BACONFILES="$(BACON_FILES)" BACONDIR="$(BACON_DIR)" $(top_srcdir)/pluma/update-from-bacon.sh
+else
+BACON_DIR=
+endif
+
+if BUILD_MESSAGE_AREA
+libpluma_la_SOURCES += pluma-message-area.c
+INST_H_FILES += pluma-message-area.h
+endif
+
+if BUILD_SPINNER
+libpluma_la_SOURCES += pluma-spinner.c
+NOINST_H_FILES += pluma-spinner.h
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/pluma/bacon-message-connection.c b/pluma/bacon-message-connection.c
new file mode 100755
index 00000000..c8000de2
--- /dev/null
+++ b/pluma/bacon-message-connection.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2003 Bastien Nocera <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+
+#include "bacon-message-connection.h"
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+struct BaconMessageConnection {
+ /* A server accepts connections */
+ gboolean is_server;
+
+ /* The socket path itself */
+ char *path;
+
+ /* File descriptor of the socket */
+ int fd;
+ /* Channel to watch */
+ GIOChannel *chan;
+ /* Event id returned by g_io_add_watch() */
+ int conn_id;
+
+ /* Connections accepted by this connection */
+ GSList *accepted_connections;
+
+ /* callback */
+ void (*func) (const char *message, gpointer user_data);
+ gpointer data;
+};
+
+static gboolean
+test_is_socket (const char *path)
+{
+ struct stat s;
+
+ if (stat (path, &s) == -1)
+ return FALSE;
+
+ if (S_ISSOCK (s.st_mode))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+is_owned_by_user_and_socket (const char *path)
+{
+ struct stat s;
+
+ if (stat (path, &s) == -1)
+ return FALSE;
+
+ if (s.st_uid != geteuid ())
+ return FALSE;
+
+ if ((s.st_mode & S_IFSOCK) != S_IFSOCK)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean server_cb (GIOChannel *source,
+ GIOCondition condition, gpointer data);
+
+static gboolean
+setup_connection (BaconMessageConnection *conn)
+{
+ g_return_val_if_fail (conn->chan == NULL, FALSE);
+
+ conn->chan = g_io_channel_unix_new (conn->fd);
+ if (!conn->chan) {
+ return FALSE;
+ }
+ g_io_channel_set_line_term (conn->chan, "\n", 1);
+ conn->conn_id = g_io_add_watch (conn->chan, G_IO_IN, server_cb, conn);
+
+ return TRUE;
+}
+
+static void
+accept_new_connection (BaconMessageConnection *server_conn)
+{
+ BaconMessageConnection *conn;
+ int alen;
+
+ g_return_if_fail (server_conn->is_server);
+
+ conn = g_new0 (BaconMessageConnection, 1);
+ conn->is_server = FALSE;
+ conn->func = server_conn->func;
+ conn->data = server_conn->data;
+
+ conn->fd = accept (server_conn->fd, NULL, (guint *)&alen);
+
+ server_conn->accepted_connections =
+ g_slist_prepend (server_conn->accepted_connections, conn);
+
+ setup_connection (conn);
+}
+
+static gboolean
+server_cb (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ BaconMessageConnection *conn = (BaconMessageConnection *)data;
+ char *message, *subs, buf;
+ int cd, rc, offset;
+ gboolean finished;
+
+ offset = 0;
+ if (conn->is_server && conn->fd == g_io_channel_unix_get_fd (source)) {
+ accept_new_connection (conn);
+ return TRUE;
+ }
+ message = g_malloc (1);
+ cd = conn->fd;
+ rc = read (cd, &buf, 1);
+ while (rc > 0 && buf != '\n')
+ {
+ message = g_realloc (message, rc + offset + 1);
+ message[offset] = buf;
+ offset = offset + rc;
+ rc = read (cd, &buf, 1);
+ }
+ if (rc <= 0) {
+ g_io_channel_shutdown (conn->chan, FALSE, NULL);
+ g_io_channel_unref (conn->chan);
+ conn->chan = NULL;
+ close (conn->fd);
+ conn->fd = -1;
+ g_free (message);
+ conn->conn_id = 0;
+
+ return FALSE;
+ }
+ message[offset] = '\0';
+
+ subs = message;
+ finished = FALSE;
+
+ while (finished == FALSE && *subs != '\0')
+ {
+ if (conn->func != NULL)
+ (*conn->func) (subs, conn->data);
+
+ subs += strlen (subs) + 1;
+ if (subs - message >= offset)
+ finished = TRUE;
+ }
+
+ g_free (message);
+
+ return TRUE;
+}
+
+static char *
+find_file_with_pattern (const char *dir, const char *pattern)
+{
+ GDir *filedir;
+ char *found_filename;
+ const char *filename;
+ GPatternSpec *pat;
+
+ filedir = g_dir_open (dir, 0, NULL);
+ if (filedir == NULL)
+ return NULL;
+
+ pat = g_pattern_spec_new (pattern);
+ if (pat == NULL)
+ {
+ g_dir_close (filedir);
+ return NULL;
+ }
+
+ found_filename = NULL;
+
+ while ((filename = g_dir_read_name (filedir)))
+ {
+ if (g_pattern_match_string (pat, filename))
+ {
+ char *tmp = g_build_filename (dir, filename, NULL);
+ if (is_owned_by_user_and_socket (tmp))
+ found_filename = g_strdup (filename);
+ g_free (tmp);
+ }
+
+ if (found_filename != NULL)
+ break;
+ }
+
+ g_pattern_spec_free (pat);
+ g_dir_close (filedir);
+
+ return found_filename;
+}
+
+static char *
+socket_filename (const char *prefix)
+{
+ char *pattern, *newfile, *path, *filename;
+ const char *tmpdir;
+
+ pattern = g_strdup_printf ("%s.%s.*", prefix, g_get_user_name ());
+ tmpdir = g_get_tmp_dir ();
+ filename = find_file_with_pattern (tmpdir, pattern);
+ if (filename == NULL)
+ {
+ newfile = g_strdup_printf ("%s.%s.%u", prefix,
+ g_get_user_name (), g_random_int ());
+ path = g_build_filename (tmpdir, newfile, NULL);
+ g_free (newfile);
+ } else {
+ path = g_build_filename (tmpdir, filename, NULL);
+ g_free (filename);
+ }
+
+ g_free (pattern);
+ return path;
+}
+
+static gboolean
+try_server (BaconMessageConnection *conn)
+{
+ struct sockaddr_un uaddr;
+
+ uaddr.sun_family = AF_UNIX;
+ strncpy (uaddr.sun_path, conn->path,
+ MIN (strlen(conn->path)+1, UNIX_PATH_MAX));
+ conn->fd = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (bind (conn->fd, (struct sockaddr *) &uaddr, sizeof (uaddr)) == -1)
+ {
+ conn->fd = -1;
+ return FALSE;
+ }
+ listen (conn->fd, 5);
+
+ if (!setup_connection (conn))
+ return FALSE;
+ return TRUE;
+}
+
+static gboolean
+try_client (BaconMessageConnection *conn)
+{
+ struct sockaddr_un uaddr;
+
+ uaddr.sun_family = AF_UNIX;
+ strncpy (uaddr.sun_path, conn->path,
+ MIN(strlen(conn->path)+1, UNIX_PATH_MAX));
+ conn->fd = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (connect (conn->fd, (struct sockaddr *) &uaddr,
+ sizeof (uaddr)) == -1)
+ {
+ conn->fd = -1;
+ return FALSE;
+ }
+
+ return setup_connection (conn);
+}
+
+BaconMessageConnection *
+bacon_message_connection_new (const char *prefix)
+{
+ BaconMessageConnection *conn;
+
+ g_return_val_if_fail (prefix != NULL, NULL);
+
+ conn = g_new0 (BaconMessageConnection, 1);
+ conn->path = socket_filename (prefix);
+
+ if (test_is_socket (conn->path) == FALSE)
+ {
+ if (!try_server (conn))
+ {
+ bacon_message_connection_free (conn);
+ return NULL;
+ }
+
+ conn->is_server = TRUE;
+ return conn;
+ }
+
+ if (try_client (conn) == FALSE)
+ {
+ unlink (conn->path);
+ try_server (conn);
+ if (conn->fd == -1)
+ {
+ bacon_message_connection_free (conn);
+ return NULL;
+ }
+
+ conn->is_server = TRUE;
+ return conn;
+ }
+
+ conn->is_server = FALSE;
+ return conn;
+}
+
+void
+bacon_message_connection_free (BaconMessageConnection *conn)
+{
+ GSList *child_conn;
+
+ g_return_if_fail (conn != NULL);
+ /* Only servers can accept other connections */
+ g_return_if_fail (conn->is_server != FALSE ||
+ conn->accepted_connections == NULL);
+
+ child_conn = conn->accepted_connections;
+ while (child_conn != NULL) {
+ bacon_message_connection_free (child_conn->data);
+ child_conn = g_slist_next (child_conn);
+ }
+ g_slist_free (conn->accepted_connections);
+
+ if (conn->conn_id) {
+ g_source_remove (conn->conn_id);
+ conn->conn_id = 0;
+ }
+ if (conn->chan) {
+ g_io_channel_shutdown (conn->chan, FALSE, NULL);
+ g_io_channel_unref (conn->chan);
+ }
+
+ if (conn->is_server != FALSE) {
+ unlink (conn->path);
+ }
+ if (conn->fd != -1) {
+ close (conn->fd);
+ }
+
+ g_free (conn->path);
+ g_free (conn);
+}
+
+void
+bacon_message_connection_set_callback (BaconMessageConnection *conn,
+ BaconMessageReceivedFunc func,
+ gpointer user_data)
+{
+ g_return_if_fail (conn != NULL);
+
+ conn->func = func;
+ conn->data = user_data;
+}
+
+void
+bacon_message_connection_send (BaconMessageConnection *conn,
+ const char *message)
+{
+ g_return_if_fail (conn != NULL);
+ g_return_if_fail (message != NULL);
+
+ g_io_channel_write_chars (conn->chan, message, strlen (message),
+ NULL, NULL);
+ g_io_channel_write_chars (conn->chan, "\n", 1, NULL, NULL);
+ g_io_channel_flush (conn->chan, NULL);
+}
+
+gboolean
+bacon_message_connection_get_is_server (BaconMessageConnection *conn)
+{
+ g_return_val_if_fail (conn != NULL, FALSE);
+
+ return conn->is_server;
+}
+
diff --git a/pluma/bacon-message-connection.h b/pluma/bacon-message-connection.h
new file mode 100755
index 00000000..aac7a2d1
--- /dev/null
+++ b/pluma/bacon-message-connection.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003 Bastien Nocera <[email protected]>
+ *
+ * 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 BACON_MESSAGE_CONNECTION_H
+#define BACON_MESSAGE_CONNECTION_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef void (*BaconMessageReceivedFunc) (const char *message,
+ gpointer user_data);
+
+typedef struct BaconMessageConnection BaconMessageConnection;
+
+BaconMessageConnection *bacon_message_connection_new (const char *prefix);
+void bacon_message_connection_free (BaconMessageConnection *conn);
+void bacon_message_connection_set_callback (BaconMessageConnection *conn,
+ BaconMessageReceivedFunc func,
+ gpointer user_data);
+void bacon_message_connection_send (BaconMessageConnection *conn,
+ const char *message);
+gboolean bacon_message_connection_get_is_server (BaconMessageConnection *conn);
+
+G_END_DECLS
+
+#endif /* BACON_MESSAGE_CONNECTION_H */
diff --git a/pluma/dialogs/Makefile.am b/pluma/dialogs/Makefile.am
new file mode 100755
index 00000000..c8a822cc
--- /dev/null
+++ b/pluma/dialogs/Makefile.am
@@ -0,0 +1,31 @@
+uidir = $(datadir)/pluma-2/ui/
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/pluma \
+ -I$(top_builddir)/pluma \
+ $(PLUMA_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+noinst_LTLIBRARIES = libdialogs.la
+
+libdialogs_la_SOURCES = \
+ pluma-preferences-dialog.h \
+ pluma-preferences-dialog.c \
+ pluma-close-confirmation-dialog.c \
+ pluma-close-confirmation-dialog.h \
+ pluma-encodings-dialog.c \
+ pluma-encodings-dialog.h \
+ pluma-search-dialog.h \
+ pluma-search-dialog.c
+
+ui_DATA = \
+ pluma-encodings-dialog.ui \
+ pluma-preferences-dialog.ui \
+ pluma-search-dialog.ui
+
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/pluma/dialogs/pluma-close-confirmation-dialog.c b/pluma/dialogs/pluma-close-confirmation-dialog.c
new file mode 100755
index 00000000..6bb2bd0c
--- /dev/null
+++ b/pluma/dialogs/pluma-close-confirmation-dialog.c
@@ -0,0 +1,790 @@
+/*
+ * pluma-close-confirmation-dialog.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2004-2005 MATE Foundation
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2004-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "pluma-close-confirmation-dialog.h"
+#include <pluma/pluma-app.h>
+#include <pluma/pluma-utils.h>
+#include <pluma/pluma-window.h>
+
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_UNSAVED_DOCUMENTS,
+ PROP_LOGOUT_MODE
+};
+
+/* Mode */
+enum
+{
+ SINGLE_DOC_MODE,
+ MULTIPLE_DOCS_MODE
+};
+
+/* Columns */
+enum
+{
+ SAVE_COLUMN,
+ NAME_COLUMN,
+ DOC_COLUMN, /* a handy pointer to the document */
+ N_COLUMNS
+};
+
+struct _PlumaCloseConfirmationDialogPrivate
+{
+ gboolean logout_mode;
+
+ GList *unsaved_documents;
+
+ GList *selected_documents;
+
+ GtkTreeModel *list_store;
+
+ gboolean disable_save_to_disk;
+};
+
+#define PLUMA_CLOSE_CONFIRMATION_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+ PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG, \
+ PlumaCloseConfirmationDialogPrivate))
+
+#define GET_MODE(priv) (((priv->unsaved_documents != NULL) && \
+ (priv->unsaved_documents->next == NULL)) ? \
+ SINGLE_DOC_MODE : MULTIPLE_DOCS_MODE)
+
+G_DEFINE_TYPE(PlumaCloseConfirmationDialog, pluma_close_confirmation_dialog, GTK_TYPE_DIALOG)
+
+static void set_unsaved_document (PlumaCloseConfirmationDialog *dlg,
+ const GList *list);
+
+static GList *get_selected_docs (GtkTreeModel *store);
+
+/* Since we connect in the costructor we are sure this handler will be called
+ * before the user ones
+ */
+static void
+response_cb (PlumaCloseConfirmationDialog *dlg,
+ gint response_id,
+ gpointer data)
+{
+ PlumaCloseConfirmationDialogPrivate *priv;
+
+ g_return_if_fail (PLUMA_IS_CLOSE_CONFIRMATION_DIALOG (dlg));
+
+ priv = dlg->priv;
+
+ if (priv->selected_documents != NULL)
+ g_list_free (priv->selected_documents);
+
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ if (GET_MODE (priv) == SINGLE_DOC_MODE)
+ {
+ priv->selected_documents =
+ g_list_copy (priv->unsaved_documents);
+ }
+ else
+ {
+ g_return_if_fail (priv->list_store);
+
+ priv->selected_documents =
+ get_selected_docs (priv->list_store);
+ }
+ }
+ else
+ priv->selected_documents = NULL;
+}
+
+static void
+set_logout_mode (PlumaCloseConfirmationDialog *dlg,
+ gboolean logout_mode)
+{
+ dlg->priv->logout_mode = logout_mode;
+
+ if (logout_mode)
+ {
+ gtk_dialog_add_button (GTK_DIALOG (dlg),
+ _("Log Out _without Saving"),
+ GTK_RESPONSE_NO);
+
+ pluma_dialog_add_button (GTK_DIALOG (dlg),
+ _("_Cancel Logout"),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ }
+ else
+ {
+ gtk_dialog_add_button (GTK_DIALOG (dlg),
+ _("Close _without Saving"),
+ GTK_RESPONSE_NO);
+
+ gtk_dialog_add_button (GTK_DIALOG (dlg),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ }
+
+ if (dlg->priv->disable_save_to_disk)
+ {
+ gtk_dialog_set_default_response (GTK_DIALOG (dlg),
+ GTK_RESPONSE_NO);
+ }
+ else
+ {
+ const gchar *stock_id = GTK_STOCK_SAVE;
+
+ if (GET_MODE (dlg->priv) == SINGLE_DOC_MODE)
+ {
+ PlumaDocument *doc;
+
+ doc = PLUMA_DOCUMENT (dlg->priv->unsaved_documents->data);
+
+ if (pluma_document_get_readonly (doc) ||
+ pluma_document_is_untitled (doc))
+ stock_id = GTK_STOCK_SAVE_AS;
+ }
+
+ gtk_dialog_add_button (GTK_DIALOG (dlg),
+ stock_id,
+ GTK_RESPONSE_YES);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dlg),
+ GTK_RESPONSE_YES);
+ }
+}
+
+static void
+pluma_close_confirmation_dialog_init (PlumaCloseConfirmationDialog *dlg)
+{
+ AtkObject *atk_obj;
+
+ dlg->priv = PLUMA_CLOSE_CONFIRMATION_DIALOG_GET_PRIVATE (dlg);
+
+ dlg->priv->disable_save_to_disk =
+ pluma_app_get_lockdown (pluma_app_get_default ())
+ & PLUMA_LOCKDOWN_SAVE_TO_DISK;
+
+ gtk_container_set_border_width (GTK_CONTAINER (dlg), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ 14);
+ gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE);
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dlg), TRUE);
+
+ gtk_window_set_title (GTK_WINDOW (dlg), "");
+
+ gtk_window_set_modal (GTK_WINDOW (dlg), TRUE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dlg), TRUE);
+
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (dlg));
+ atk_object_set_role (atk_obj, ATK_ROLE_ALERT);
+ atk_object_set_name (atk_obj, _("Question"));
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (response_cb),
+ NULL);
+}
+
+static void
+pluma_close_confirmation_dialog_finalize (GObject *object)
+{
+ PlumaCloseConfirmationDialogPrivate *priv;
+
+ priv = PLUMA_CLOSE_CONFIRMATION_DIALOG (object)->priv;
+
+ if (priv->unsaved_documents != NULL)
+ g_list_free (priv->unsaved_documents);
+
+ if (priv->selected_documents != NULL)
+ g_list_free (priv->selected_documents);
+
+ /* Call the parent's destructor */
+ G_OBJECT_CLASS (pluma_close_confirmation_dialog_parent_class)->finalize (object);
+}
+
+static void
+pluma_close_confirmation_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaCloseConfirmationDialog *dlg;
+
+ dlg = PLUMA_CLOSE_CONFIRMATION_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_UNSAVED_DOCUMENTS:
+ set_unsaved_document (dlg, g_value_get_pointer (value));
+ break;
+
+ case PROP_LOGOUT_MODE:
+ set_logout_mode (dlg, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_close_confirmation_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaCloseConfirmationDialogPrivate *priv;
+
+ priv = PLUMA_CLOSE_CONFIRMATION_DIALOG (object)->priv;
+
+ switch( prop_id )
+ {
+ case PROP_UNSAVED_DOCUMENTS:
+ g_value_set_pointer (value, priv->unsaved_documents);
+ break;
+
+ case PROP_LOGOUT_MODE:
+ g_value_set_boolean (value, priv->logout_mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_close_confirmation_dialog_class_init (PlumaCloseConfirmationDialogClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = pluma_close_confirmation_dialog_set_property;
+ gobject_class->get_property = pluma_close_confirmation_dialog_get_property;
+ gobject_class->finalize = pluma_close_confirmation_dialog_finalize;
+
+ g_type_class_add_private (klass, sizeof (PlumaCloseConfirmationDialogPrivate));
+
+ g_object_class_install_property (gobject_class,
+ PROP_UNSAVED_DOCUMENTS,
+ g_param_spec_pointer ("unsaved_documents",
+ "Unsaved Documents",
+ "List of Unsaved Documents",
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY)));
+
+ g_object_class_install_property (gobject_class,
+ PROP_LOGOUT_MODE,
+ g_param_spec_boolean ("logout_mode",
+ "Logout Mode",
+ "Whether the dialog is in logout mode",
+ FALSE,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY)));
+}
+
+static GList *
+get_selected_docs (GtkTreeModel *store)
+{
+ GList *list;
+ gboolean valid;
+ GtkTreeIter iter;
+
+ list = NULL;
+ valid = gtk_tree_model_get_iter_first (store, &iter);
+
+ while (valid)
+ {
+ gboolean to_save;
+ PlumaDocument *doc;
+
+ gtk_tree_model_get (store, &iter,
+ SAVE_COLUMN, &to_save,
+ DOC_COLUMN, &doc,
+ -1);
+ if (to_save)
+ list = g_list_prepend (list, doc);
+
+ valid = gtk_tree_model_iter_next (store, &iter);
+ }
+
+ list = g_list_reverse (list);
+
+ return list;
+}
+
+GList *
+pluma_close_confirmation_dialog_get_selected_documents (PlumaCloseConfirmationDialog *dlg)
+{
+ g_return_val_if_fail (PLUMA_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL);
+
+ return g_list_copy (dlg->priv->selected_documents);
+}
+
+GtkWidget *
+pluma_close_confirmation_dialog_new (GtkWindow *parent,
+ GList *unsaved_documents,
+ gboolean logout_mode)
+{
+ GtkWidget *dlg;
+ g_return_val_if_fail (unsaved_documents != NULL, NULL);
+
+ dlg = GTK_WIDGET (g_object_new (PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG,
+ "unsaved_documents", unsaved_documents,
+ "logout_mode", logout_mode,
+ NULL));
+ g_return_val_if_fail (dlg != NULL, NULL);
+
+ if (parent != NULL)
+ {
+ gtk_window_group_add_window (pluma_window_get_group (PLUMA_WINDOW (parent)),
+ GTK_WINDOW (dlg));
+
+ gtk_window_set_transient_for (GTK_WINDOW (dlg), parent);
+ }
+
+ return dlg;
+}
+
+GtkWidget *
+pluma_close_confirmation_dialog_new_single (GtkWindow *parent,
+ PlumaDocument *doc,
+ gboolean logout_mode)
+{
+ GtkWidget *dlg;
+ GList *unsaved_documents;
+ g_return_val_if_fail (doc != NULL, NULL);
+
+ unsaved_documents = g_list_prepend (NULL, doc);
+
+ dlg = pluma_close_confirmation_dialog_new (parent,
+ unsaved_documents,
+ logout_mode);
+
+ g_list_free (unsaved_documents);
+
+ return dlg;
+}
+
+static gchar *
+get_text_secondary_label (PlumaDocument *doc)
+{
+ glong seconds;
+ gchar *secondary_msg;
+
+ seconds = MAX (1, _pluma_document_get_seconds_since_last_save_or_load (doc));
+
+ if (seconds < 55)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don't save, changes from the last %ld second "
+ "will be permanently lost.",
+ "If you don't save, changes from the last %ld seconds "
+ "will be permanently lost.",
+ seconds),
+ seconds);
+ }
+ else if (seconds < 75) /* 55 <= seconds < 75 */
+ {
+ secondary_msg = g_strdup (_("If you don't save, changes from the last minute "
+ "will be permanently lost."));
+ }
+ else if (seconds < 110) /* 75 <= seconds < 110 */
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don't save, changes from the last minute and %ld "
+ "second will be permanently lost.",
+ "If you don't save, changes from the last minute and %ld "
+ "seconds will be permanently lost.",
+ seconds - 60 ),
+ seconds - 60);
+ }
+ else if (seconds < 3600)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don't save, changes from the last %ld minute "
+ "will be permanently lost.",
+ "If you don't save, changes from the last %ld minutes "
+ "will be permanently lost.",
+ seconds / 60),
+ seconds / 60);
+ }
+ else if (seconds < 7200)
+ {
+ gint minutes;
+ seconds -= 3600;
+
+ minutes = seconds / 60;
+ if (minutes < 5)
+ {
+ secondary_msg = g_strdup (_("If you don't save, changes from the last hour "
+ "will be permanently lost."));
+ }
+ else
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don't save, changes from the last hour and %d "
+ "minute will be permanently lost.",
+ "If you don't save, changes from the last hour and %d "
+ "minutes will be permanently lost.",
+ minutes),
+ minutes);
+ }
+ }
+ else
+ {
+ gint hours;
+
+ hours = seconds / 3600;
+
+ secondary_msg = g_strdup_printf (
+ ngettext ("If you don't save, changes from the last %d hour "
+ "will be permanently lost.",
+ "If you don't save, changes from the last %d hours "
+ "will be permanently lost.",
+ hours),
+ hours);
+ }
+
+ return secondary_msg;
+}
+
+static void
+build_single_doc_dialog (PlumaCloseConfirmationDialog *dlg)
+{
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+ GtkWidget *image;
+ PlumaDocument *doc;
+ gchar *doc_name;
+ gchar *str;
+ gchar *markup_str;
+
+ g_return_if_fail (dlg->priv->unsaved_documents->data != NULL);
+ doc = PLUMA_DOCUMENT (dlg->priv->unsaved_documents->data);
+
+ /* Image */
+ image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING,
+ GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ /* Primary label */
+ primary_label = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0.0, 0.5);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ doc_name = pluma_document_get_short_name_for_display (doc);
+
+ if (dlg->priv->disable_save_to_disk)
+ {
+ str = g_markup_printf_escaped (_("Changes to document \"%s\" will be permanently lost."),
+ doc_name);
+ }
+ else
+ {
+ str = g_markup_printf_escaped (_("Save changes to document \"%s\" before closing?"),
+ doc_name);
+ }
+
+ g_free (doc_name);
+
+ markup_str = g_strconcat ("<span weight=\"bold\" size=\"larger\">", str, "</span>", NULL);
+ g_free (str);
+
+ gtk_label_set_markup (GTK_LABEL (primary_label), markup_str);
+ g_free (markup_str);
+
+ /* Secondary label */
+ if (dlg->priv->disable_save_to_disk)
+ str = g_strdup (_("Saving has been disabled by the system administrator."));
+ else
+ str = get_text_secondary_label (doc);
+ secondary_label = gtk_label_new (str);
+ g_free (str);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0.0, 0.5);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+
+ hbox = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
+
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+ vbox = gtk_vbox_new (FALSE, 12);
+
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ hbox,
+ FALSE,
+ FALSE,
+ 0);
+
+ gtk_widget_show_all (hbox);
+}
+
+static void
+populate_model (GtkTreeModel *store, GList *docs)
+{
+ GtkTreeIter iter;
+
+ while (docs != NULL)
+ {
+ PlumaDocument *doc;
+ gchar *name;
+
+ doc = PLUMA_DOCUMENT (docs->data);
+
+ name = pluma_document_get_short_name_for_display (doc);
+
+ gtk_list_store_append (GTK_LIST_STORE (store), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (store), &iter,
+ SAVE_COLUMN, TRUE,
+ NAME_COLUMN, name,
+ DOC_COLUMN, doc,
+ -1);
+
+ g_free (name);
+
+ docs = g_list_next (docs);
+ }
+}
+
+static void
+save_toggled (GtkCellRendererToggle *renderer, gchar *path_str, GtkTreeModel *store)
+{
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
+ GtkTreeIter iter;
+ gboolean active;
+
+ gtk_tree_model_get_iter (store, &iter, path);
+ gtk_tree_model_get (store, &iter, SAVE_COLUMN, &active, -1);
+
+ active ^= 1;
+
+ gtk_list_store_set (GTK_LIST_STORE (store), &iter,
+ SAVE_COLUMN, active, -1);
+
+ gtk_tree_path_free (path);
+}
+
+static GtkWidget *
+create_treeview (PlumaCloseConfirmationDialogPrivate *priv)
+{
+ GtkListStore *store;
+ GtkWidget *treeview;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ treeview = gtk_tree_view_new ();
+ gtk_widget_set_size_request (treeview, 260, 120);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+ gtk_tree_view_set_enable_search (GTK_TREE_VIEW (treeview), FALSE);
+
+ /* Create and populate the model */
+ store = gtk_list_store_new (N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER);
+ populate_model (GTK_TREE_MODEL (store), priv->unsaved_documents);
+
+ /* Set model to the treeview */
+ gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ priv->list_store = GTK_TREE_MODEL (store);
+
+ /* Add columns */
+ if (!priv->disable_save_to_disk)
+ {
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (renderer, "toggled",
+ G_CALLBACK (save_toggled), store);
+
+ column = gtk_tree_view_column_new_with_attributes ("Save?",
+ renderer,
+ "active",
+ SAVE_COLUMN,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+ }
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Name",
+ renderer,
+ "text",
+ NAME_COLUMN,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ return treeview;
+}
+
+static void
+build_multiple_docs_dialog (PlumaCloseConfirmationDialog *dlg)
+{
+ PlumaCloseConfirmationDialogPrivate *priv;
+ GtkWidget *hbox;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ GtkWidget *primary_label;
+ GtkWidget *vbox2;
+ GtkWidget *select_label;
+ GtkWidget *scrolledwindow;
+ GtkWidget *treeview;
+ GtkWidget *secondary_label;
+ gchar *str;
+ gchar *markup_str;
+
+ priv = dlg->priv;
+
+ hbox = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ hbox, TRUE, TRUE, 0);
+
+ /* Image */
+ image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING,
+ GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+ vbox = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+
+ /* Primary label */
+ primary_label = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0.0, 0.5);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ if (priv->disable_save_to_disk)
+ str = g_strdup_printf (
+ ngettext ("Changes to %d document will be permanently lost.",
+ "Changes to %d documents will be permanently lost.",
+ g_list_length (priv->unsaved_documents)),
+ g_list_length (priv->unsaved_documents));
+ else
+ str = g_strdup_printf (
+ ngettext ("There is %d document with unsaved changes. "
+ "Save changes before closing?",
+ "There are %d documents with unsaved changes. "
+ "Save changes before closing?",
+ g_list_length (priv->unsaved_documents)),
+ g_list_length (priv->unsaved_documents));
+
+ markup_str = g_strconcat ("<span weight=\"bold\" size=\"larger\">", str, "</span>", NULL);
+ g_free (str);
+
+ gtk_label_set_markup (GTK_LABEL (primary_label), markup_str);
+ g_free (markup_str);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, FALSE, FALSE, 0);
+
+ vbox2 = gtk_vbox_new (FALSE, 8);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);
+
+ if (priv->disable_save_to_disk)
+ select_label = gtk_label_new_with_mnemonic (_("Docum_ents with unsaved changes:"));
+ else
+ select_label = gtk_label_new_with_mnemonic (_("S_elect the documents you want to save:"));
+
+ gtk_box_pack_start (GTK_BOX (vbox2), select_label, FALSE, FALSE, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (select_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (select_label), 0.0, 0.5);
+
+ scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox2), scrolledwindow, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow),
+ GTK_SHADOW_IN);
+
+ treeview = create_treeview (priv);
+ gtk_container_add (GTK_CONTAINER (scrolledwindow), treeview);
+
+ /* Secondary label */
+ if (priv->disable_save_to_disk)
+ secondary_label = gtk_label_new (_("Saving has been disabled by the system administrator."));
+ else
+ secondary_label = gtk_label_new (_("If you don't save, "
+ "all your changes will be permanently lost."));
+
+ gtk_box_pack_start (GTK_BOX (vbox2), secondary_label, FALSE, FALSE, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (select_label), treeview);
+
+ gtk_widget_show_all (hbox);
+}
+
+static void
+set_unsaved_document (PlumaCloseConfirmationDialog *dlg,
+ const GList *list)
+{
+ PlumaCloseConfirmationDialogPrivate *priv;
+
+ g_return_if_fail (list != NULL);
+
+ priv = dlg->priv;
+ g_return_if_fail (priv->unsaved_documents == NULL);
+
+ priv->unsaved_documents = g_list_copy ((GList *)list);
+
+ if (GET_MODE (priv) == SINGLE_DOC_MODE)
+ {
+ build_single_doc_dialog (dlg);
+ }
+ else
+ {
+ build_multiple_docs_dialog (dlg);
+ }
+}
+
+const GList *
+pluma_close_confirmation_dialog_get_unsaved_documents (PlumaCloseConfirmationDialog *dlg)
+{
+ g_return_val_if_fail (PLUMA_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL);
+
+ return dlg->priv->unsaved_documents;
+}
diff --git a/pluma/dialogs/pluma-close-confirmation-dialog.h b/pluma/dialogs/pluma-close-confirmation-dialog.h
new file mode 100755
index 00000000..dfc027f4
--- /dev/null
+++ b/pluma/dialogs/pluma-close-confirmation-dialog.h
@@ -0,0 +1,75 @@
+/*
+ * pluma-close-confirmation-dialog.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2004-2005 MATE Foundation
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2004-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __PLUMA_CLOSE_CONFIRMATION_DIALOG_H__
+#define __PLUMA_CLOSE_CONFIRMATION_DIALOG_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-document.h>
+
+#define PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG (pluma_close_confirmation_dialog_get_type ())
+#define PLUMA_CLOSE_CONFIRMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG, PlumaCloseConfirmationDialog))
+#define PLUMA_CLOSE_CONFIRMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG, PlumaCloseConfirmationDialogClass))
+#define PLUMA_IS_CLOSE_CONFIRMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG))
+#define PLUMA_IS_CLOSE_CONFIRMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG))
+#define PLUMA_CLOSE_CONFIRMATION_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),PLUMA_TYPE_CLOSE_CONFIRMATION_DIALOG, PlumaCloseConfirmationDialogClass))
+
+typedef struct _PlumaCloseConfirmationDialog PlumaCloseConfirmationDialog;
+typedef struct _PlumaCloseConfirmationDialogClass PlumaCloseConfirmationDialogClass;
+typedef struct _PlumaCloseConfirmationDialogPrivate PlumaCloseConfirmationDialogPrivate;
+
+struct _PlumaCloseConfirmationDialog
+{
+ GtkDialog parent;
+
+ /*< private > */
+ PlumaCloseConfirmationDialogPrivate *priv;
+};
+
+struct _PlumaCloseConfirmationDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+GType pluma_close_confirmation_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_close_confirmation_dialog_new (GtkWindow *parent,
+ GList *unsaved_documents,
+ gboolean logout_mode);
+GtkWidget *pluma_close_confirmation_dialog_new_single (GtkWindow *parent,
+ PlumaDocument *doc,
+ gboolean logout_mode);
+
+const GList *pluma_close_confirmation_dialog_get_unsaved_documents (PlumaCloseConfirmationDialog *dlg);
+
+GList *pluma_close_confirmation_dialog_get_selected_documents (PlumaCloseConfirmationDialog *dlg);
+
+#endif /* __PLUMA_CLOSE_CONFIRMATION_DIALOG_H__ */
+
diff --git a/pluma/dialogs/pluma-encodings-dialog.c b/pluma/dialogs/pluma-encodings-dialog.c
new file mode 100755
index 00000000..b64fe1d8
--- /dev/null
+++ b/pluma/dialogs/pluma-encodings-dialog.c
@@ -0,0 +1,499 @@
+/*
+ * pluma-encodings-dialog.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "pluma-encodings-dialog.h"
+#include "pluma-encodings.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-utils.h"
+#include "pluma-debug.h"
+#include "pluma-help.h"
+#include "pluma-dirs.h"
+
+#define PLUMA_ENCODINGS_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_ENCODINGS_DIALOG, \
+ PlumaEncodingsDialogPrivate))
+
+struct _PlumaEncodingsDialogPrivate
+{
+ GtkListStore *available_liststore;
+ GtkListStore *displayed_liststore;
+ GtkWidget *available_treeview;
+ GtkWidget *displayed_treeview;
+ GtkWidget *add_button;
+ GtkWidget *remove_button;
+
+ GSList *show_in_menu_list;
+};
+
+G_DEFINE_TYPE(PlumaEncodingsDialog, pluma_encodings_dialog, GTK_TYPE_DIALOG)
+
+static void
+pluma_encodings_dialog_finalize (GObject *object)
+{
+ PlumaEncodingsDialogPrivate *priv = PLUMA_ENCODINGS_DIALOG (object)->priv;
+
+ g_slist_free (priv->show_in_menu_list);
+
+ G_OBJECT_CLASS (pluma_encodings_dialog_parent_class)->finalize (object);
+}
+
+static void
+pluma_encodings_dialog_class_init (PlumaEncodingsDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_encodings_dialog_finalize;
+
+ g_type_class_add_private (object_class, sizeof (PlumaEncodingsDialogPrivate));
+}
+
+enum {
+ COLUMN_NAME,
+ COLUMN_CHARSET,
+ N_COLUMNS
+};
+
+static void
+count_selected_items_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int *count = data;
+
+ *count += 1;
+}
+
+static void
+available_selection_changed_callback (GtkTreeSelection *selection,
+ PlumaEncodingsDialog *dialogs)
+{
+ int count;
+
+ count = 0;
+ gtk_tree_selection_selected_foreach (selection,
+ count_selected_items_func,
+ &count);
+
+ gtk_widget_set_sensitive (dialogs->priv->add_button, count > 0);
+}
+
+static void
+displayed_selection_changed_callback (GtkTreeSelection *selection,
+ PlumaEncodingsDialog *dialogs)
+{
+ int count;
+
+ count = 0;
+ gtk_tree_selection_selected_foreach (selection,
+ count_selected_items_func,
+ &count);
+
+ gtk_widget_set_sensitive (dialogs->priv->remove_button, count > 0);
+}
+
+static void
+get_selected_encodings_func (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GSList **list = data;
+ gchar *charset;
+ const PlumaEncoding *enc;
+
+ charset = NULL;
+ gtk_tree_model_get (model, iter, COLUMN_CHARSET, &charset, -1);
+
+ enc = pluma_encoding_get_from_charset (charset);
+ g_free (charset);
+
+ *list = g_slist_prepend (*list, (gpointer)enc);
+}
+
+static void
+update_shown_in_menu_tree_model (GtkListStore *store,
+ GSList *list)
+{
+ GtkTreeIter iter;
+
+ gtk_list_store_clear (store);
+
+ while (list != NULL)
+ {
+ const PlumaEncoding *enc;
+
+ enc = (const PlumaEncoding*) list->data;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_CHARSET,
+ pluma_encoding_get_charset (enc),
+ COLUMN_NAME,
+ pluma_encoding_get_name (enc), -1);
+
+ list = g_slist_next (list);
+ }
+}
+
+static void
+add_button_clicked_callback (GtkWidget *button,
+ PlumaEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+ GSList *encodings;
+ GSList *tmp;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->available_treeview));
+
+ encodings = NULL;
+ gtk_tree_selection_selected_foreach (selection,
+ get_selected_encodings_func,
+ &encodings);
+
+ tmp = encodings;
+ while (tmp != NULL)
+ {
+ if (g_slist_find (dialog->priv->show_in_menu_list, tmp->data) == NULL)
+ dialog->priv->show_in_menu_list = g_slist_prepend (dialog->priv->show_in_menu_list,
+ tmp->data);
+
+ tmp = g_slist_next (tmp);
+ }
+
+ g_slist_free (encodings);
+
+ update_shown_in_menu_tree_model (GTK_LIST_STORE (dialog->priv->displayed_liststore),
+ dialog->priv->show_in_menu_list);
+}
+
+static void
+remove_button_clicked_callback (GtkWidget *button,
+ PlumaEncodingsDialog *dialog)
+{
+ GtkTreeSelection *selection;
+ GSList *encodings;
+ GSList *tmp;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->displayed_treeview));
+
+ encodings = NULL;
+ gtk_tree_selection_selected_foreach (selection,
+ get_selected_encodings_func,
+ &encodings);
+
+ tmp = encodings;
+ while (tmp != NULL)
+ {
+ dialog->priv->show_in_menu_list = g_slist_remove (dialog->priv->show_in_menu_list,
+ tmp->data);
+
+ tmp = g_slist_next (tmp);
+ }
+
+ g_slist_free (encodings);
+
+ update_shown_in_menu_tree_model (GTK_LIST_STORE (dialog->priv->displayed_liststore),
+ dialog->priv->show_in_menu_list);
+}
+
+static void
+init_shown_in_menu_tree_model (PlumaEncodingsDialog *dialog)
+{
+ GtkTreeIter iter;
+ GSList *list, *tmp;
+
+ /* add data to the list store */
+ list = pluma_prefs_manager_get_shown_in_menu_encodings ();
+
+ tmp = list;
+
+ while (tmp != NULL)
+ {
+ const PlumaEncoding *enc;
+
+ enc = (const PlumaEncoding *) tmp->data;
+
+ dialog->priv->show_in_menu_list = g_slist_prepend (dialog->priv->show_in_menu_list,
+ tmp->data);
+
+ gtk_list_store_append (dialog->priv->displayed_liststore,
+ &iter);
+ gtk_list_store_set (dialog->priv->displayed_liststore,
+ &iter,
+ COLUMN_CHARSET,
+ pluma_encoding_get_charset (enc),
+ COLUMN_NAME,
+ pluma_encoding_get_name (enc), -1);
+
+ tmp = g_slist_next (tmp);
+ }
+
+ g_slist_free (list);
+}
+
+static void
+response_handler (GtkDialog *dialog,
+ gint response_id,
+ PlumaEncodingsDialog *dlg)
+{
+ if (response_id == GTK_RESPONSE_HELP)
+ {
+ pluma_help_display (GTK_WINDOW (dialog), "pluma", NULL);
+ g_signal_stop_emission_by_name (dialog, "response");
+ return;
+ }
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ g_return_if_fail (pluma_prefs_manager_shown_in_menu_encodings_can_set ());
+ pluma_prefs_manager_set_shown_in_menu_encodings (dlg->priv->show_in_menu_list);
+ }
+}
+
+static void
+pluma_encodings_dialog_init (PlumaEncodingsDialog *dlg)
+{
+ GtkWidget *content;
+ GtkCellRenderer *cell_renderer;
+ GtkTreeModel *sort_model;
+ GtkTreeViewColumn *column;
+ GtkTreeIter parent_iter;
+ GtkTreeSelection *selection;
+ const PlumaEncoding *enc;
+ GtkWidget *error_widget;
+ int i;
+ gboolean ret;
+ gchar *file;
+ gchar *root_objects[] = {
+ "encodings-dialog-contents",
+ NULL
+ };
+
+ dlg->priv = PLUMA_ENCODINGS_DIALOG_GET_PRIVATE (dlg);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dlg),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK,
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP,
+ NULL);
+
+ gtk_window_set_title (GTK_WINDOW (dlg), _("Character Encodings"));
+ gtk_window_set_default_size (GTK_WINDOW (dlg), 650, 400);
+ gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE);
+
+ /* HIG defaults */
+ gtk_container_set_border_width (GTK_CONTAINER (dlg), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ 2); /* 2 * 5 + 2 = 12 */
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dlg))),
+ 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dlg))), 6);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dlg),
+ GTK_RESPONSE_OK);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (response_handler),
+ dlg);
+
+ file = pluma_dirs_get_ui_file ("pluma-encodings-dialog.ui");
+ ret = pluma_utils_get_ui_objects (file,
+ root_objects,
+ &error_widget,
+ "encodings-dialog-contents", &content,
+ "add-button", &dlg->priv->add_button,
+ "remove-button", &dlg->priv->remove_button,
+ "available-treeview", &dlg->priv->available_treeview,
+ "displayed-treeview", &dlg->priv->displayed_treeview,
+ NULL);
+ g_free (file);
+
+ if (!ret)
+ {
+ gtk_widget_show (error_widget);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ error_widget,
+ TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (error_widget), 5);
+
+ return;
+ }
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ content, TRUE, TRUE, 0);
+ g_object_unref (content);
+ gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+
+ g_signal_connect (dlg->priv->add_button,
+ "clicked",
+ G_CALLBACK (add_button_clicked_callback),
+ dlg);
+ g_signal_connect (dlg->priv->remove_button,
+ "clicked",
+ G_CALLBACK (remove_button_clicked_callback),
+ dlg);
+
+ /* Tree view of available encodings */
+ dlg->priv->available_liststore = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("_Description"),
+ cell_renderer,
+ "text", COLUMN_NAME,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->priv->available_treeview),
+ column);
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("_Encoding"),
+ cell_renderer,
+ "text",
+ COLUMN_CHARSET,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->priv->available_treeview),
+ column);
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_CHARSET);
+
+ /* Add the data */
+ i = 0;
+ while ((enc = pluma_encoding_get_from_index (i)) != NULL)
+ {
+ gtk_list_store_append (dlg->priv->available_liststore,
+ &parent_iter);
+ gtk_list_store_set (dlg->priv->available_liststore,
+ &parent_iter,
+ COLUMN_CHARSET,
+ pluma_encoding_get_charset (enc),
+ COLUMN_NAME,
+ pluma_encoding_get_name (enc), -1);
+
+ ++i;
+ }
+
+ /* Sort model */
+ sort_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (dlg->priv->available_liststore));
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model),
+ COLUMN_NAME,
+ GTK_SORT_ASCENDING);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dlg->priv->available_treeview),
+ sort_model);
+ g_object_unref (G_OBJECT (dlg->priv->available_liststore));
+ g_object_unref (G_OBJECT (sort_model));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->priv->available_treeview));
+ gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection),
+ GTK_SELECTION_MULTIPLE);
+
+ available_selection_changed_callback (selection, dlg);
+ g_signal_connect (selection,
+ "changed",
+ G_CALLBACK (available_selection_changed_callback),
+ dlg);
+
+ /* Tree view of selected encodings */
+ dlg->priv->displayed_liststore = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("_Description"),
+ cell_renderer,
+ "text", COLUMN_NAME,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->priv->displayed_treeview),
+ column);
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("_Encoding"),
+ cell_renderer,
+ "text",
+ COLUMN_CHARSET,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->priv->displayed_treeview),
+ column);
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_CHARSET);
+
+ /* Add the data */
+ init_shown_in_menu_tree_model (dlg);
+
+ /* Sort model */
+ sort_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (dlg->priv->displayed_liststore));
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE
+ (sort_model), COLUMN_NAME,
+ GTK_SORT_ASCENDING);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dlg->priv->displayed_treeview),
+ sort_model);
+ g_object_unref (G_OBJECT (sort_model));
+ g_object_unref (G_OBJECT (dlg->priv->displayed_liststore));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->priv->displayed_treeview));
+ gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection),
+ GTK_SELECTION_MULTIPLE);
+
+ displayed_selection_changed_callback (selection, dlg);
+ g_signal_connect (selection,
+ "changed",
+ G_CALLBACK (displayed_selection_changed_callback),
+ dlg);
+}
+
+GtkWidget *
+pluma_encodings_dialog_new (void)
+{
+ GtkWidget *dlg;
+
+ dlg = GTK_WIDGET (g_object_new (PLUMA_TYPE_ENCODINGS_DIALOG, NULL));
+
+ return dlg;
+}
+
diff --git a/pluma/dialogs/pluma-encodings-dialog.h b/pluma/dialogs/pluma-encodings-dialog.h
new file mode 100755
index 00000000..a9aeed65
--- /dev/null
+++ b/pluma/dialogs/pluma-encodings-dialog.h
@@ -0,0 +1,86 @@
+/*
+ * pluma-encodings-dialog.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2003-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_ENCODINGS_DIALOG_H__
+#define __PLUMA_ENCODINGS_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_ENCODINGS_DIALOG (pluma_encodings_dialog_get_type())
+#define PLUMA_ENCODINGS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_ENCODINGS_DIALOG, PlumaEncodingsDialog))
+#define PLUMA_ENCODINGS_DIALOG_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_ENCODINGS_DIALOG, PlumaEncodingsDialog const))
+#define PLUMA_ENCODINGS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_ENCODINGS_DIALOG, PlumaEncodingsDialogClass))
+#define PLUMA_IS_ENCODINGS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_ENCODINGS_DIALOG))
+#define PLUMA_IS_ENCODINGS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_ENCODINGS_DIALOG))
+#define PLUMA_ENCODINGS_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_ENCODINGS_DIALOG, PlumaEncodingsDialogClass))
+
+
+/* Private structure type */
+typedef struct _PlumaEncodingsDialogPrivate PlumaEncodingsDialogPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaEncodingsDialog PlumaEncodingsDialog;
+
+struct _PlumaEncodingsDialog
+{
+ GtkDialog dialog;
+
+ /*< private > */
+ PlumaEncodingsDialogPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaEncodingsDialogClass PlumaEncodingsDialogClass;
+
+struct _PlumaEncodingsDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType pluma_encodings_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_encodings_dialog_new (void);
+
+G_END_DECLS
+
+#endif /* __PLUMA_ENCODINGS_DIALOG_H__ */
+
diff --git a/pluma/dialogs/pluma-encodings-dialog.ui b/pluma/dialogs/pluma-encodings-dialog.ui
new file mode 100755
index 00000000..0f37b432
--- /dev/null
+++ b/pluma/dialogs/pluma-encodings-dialog.ui
@@ -0,0 +1,256 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="encodings-dialog">
+ <property name="width_request">650</property>
+ <property name="height_request">400</property>
+ <property name="title" translatable="yes">Character encodings</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">True</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area3">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="helpbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="closebutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="encodings-dialog-contents">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="available-label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">A_vailable encodings:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">available-treeview</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="available-treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">True</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkButton" id="add-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="displayed-label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">E_ncodings shown in menu:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">displayed-treeview</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="displayed-treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">True</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox8">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkButton" id="remove-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">helpbutton1</action-widget>
+ <action-widget response="-6">closebutton1</action-widget>
+ <action-widget response="-5">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/pluma/dialogs/pluma-preferences-dialog.c b/pluma/dialogs/pluma-preferences-dialog.c
new file mode 100755
index 00000000..e14cb83d
--- /dev/null
+++ b/pluma/dialogs/pluma-preferences-dialog.c
@@ -0,0 +1,1189 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-preferences-dialog.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2001-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2001-2003. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <mateconf/mateconf-client.h>
+
+#include <pluma/pluma-prefs-manager.h>
+
+#include "pluma-preferences-dialog.h"
+#include "pluma-utils.h"
+#include "pluma-debug.h"
+#include "pluma-document.h"
+#include "pluma-style-scheme-manager.h"
+#include "pluma-plugin-manager.h"
+#include "pluma-help.h"
+#include "pluma-dirs.h"
+
+/*
+ * pluma-preferences dialog is a singleton since we don't
+ * want two dialogs showing an inconsistent state of the
+ * preferences.
+ * When pluma_show_preferences_dialog is called and there
+ * is already a prefs dialog dialog open, it is reparented
+ * and shown.
+ */
+
+static GtkWidget *preferences_dialog = NULL;
+
+
+enum
+{
+ ID_COLUMN = 0,
+ NAME_COLUMN,
+ DESC_COLUMN,
+ NUM_COLUMNS
+};
+
+
+#define PLUMA_PREFERENCES_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_PREFERENCES_DIALOG, \
+ PlumaPreferencesDialogPrivate))
+
+struct _PlumaPreferencesDialogPrivate
+{
+ GtkWidget *notebook;
+
+ /* Font */
+ GtkWidget *default_font_checkbutton;
+ GtkWidget *font_button;
+ GtkWidget *font_hbox;
+
+ /* Style Scheme */
+ GtkListStore *schemes_treeview_model;
+ GtkWidget *schemes_treeview;
+ GtkWidget *install_scheme_button;
+ GtkWidget *uninstall_scheme_button;
+
+ GtkWidget *install_scheme_file_schooser;
+
+ /* Tabs */
+ GtkWidget *tabs_width_spinbutton;
+ GtkWidget *insert_spaces_checkbutton;
+ GtkWidget *tabs_width_hbox;
+
+ /* Auto indentation */
+ GtkWidget *auto_indent_checkbutton;
+
+ /* Text Wrapping */
+ GtkWidget *wrap_text_checkbutton;
+ GtkWidget *split_checkbutton;
+
+ /* File Saving */
+ GtkWidget *backup_copy_checkbutton;
+ GtkWidget *auto_save_checkbutton;
+ GtkWidget *auto_save_spinbutton;
+ GtkWidget *autosave_hbox;
+
+ /* Line numbers */
+ GtkWidget *display_line_numbers_checkbutton;
+
+ /* Highlight current line */
+ GtkWidget *highlight_current_line_checkbutton;
+
+ /* Highlight matching bracket */
+ GtkWidget *bracket_matching_checkbutton;
+
+ /* Right margin */
+ GtkWidget *right_margin_checkbutton;
+ GtkWidget *right_margin_position_spinbutton;
+ GtkWidget *right_margin_position_hbox;
+
+ /* Plugins manager */
+ GtkWidget *plugin_manager_place_holder;
+
+ /* Style Scheme editor dialog */
+ GtkWidget *style_scheme_dialog;
+};
+
+
+G_DEFINE_TYPE(PlumaPreferencesDialog, pluma_preferences_dialog, GTK_TYPE_DIALOG)
+
+
+static void
+pluma_preferences_dialog_class_init (PlumaPreferencesDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (PlumaPreferencesDialogPrivate));
+}
+
+static void
+dialog_response_handler (GtkDialog *dlg,
+ gint res_id)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ switch (res_id)
+ {
+ case GTK_RESPONSE_HELP:
+ pluma_help_display (GTK_WINDOW (dlg),
+ NULL,
+ "pluma-prefs");
+
+ g_signal_stop_emission_by_name (dlg, "response");
+
+ break;
+
+ default:
+ gtk_widget_destroy (GTK_WIDGET(dlg));
+ }
+}
+
+static void
+tabs_width_spinbutton_value_changed (GtkSpinButton *spin_button,
+ PlumaPreferencesDialog *dlg)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (spin_button == GTK_SPIN_BUTTON (dlg->priv->tabs_width_spinbutton));
+
+ pluma_prefs_manager_set_tabs_size (gtk_spin_button_get_value_as_int (spin_button));
+}
+
+static void
+insert_spaces_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->insert_spaces_checkbutton));
+
+ pluma_prefs_manager_set_insert_spaces (gtk_toggle_button_get_active (button));
+}
+
+static void
+auto_indent_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->auto_indent_checkbutton));
+
+ pluma_prefs_manager_set_auto_indent (gtk_toggle_button_get_active (button));
+}
+
+static void
+auto_save_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->auto_save_checkbutton));
+
+ if (gtk_toggle_button_get_active (button))
+ {
+ gtk_widget_set_sensitive (dlg->priv->auto_save_spinbutton,
+ pluma_prefs_manager_auto_save_interval_can_set());
+
+ pluma_prefs_manager_set_auto_save (TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dlg->priv->auto_save_spinbutton, FALSE);
+ pluma_prefs_manager_set_auto_save (FALSE);
+ }
+}
+
+static void
+backup_copy_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->backup_copy_checkbutton));
+
+ pluma_prefs_manager_set_create_backup_copy (gtk_toggle_button_get_active (button));
+}
+
+static void
+auto_save_spinbutton_value_changed (GtkSpinButton *spin_button,
+ PlumaPreferencesDialog *dlg)
+{
+ g_return_if_fail (spin_button == GTK_SPIN_BUTTON (dlg->priv->auto_save_spinbutton));
+
+ pluma_prefs_manager_set_auto_save_interval (
+ MAX (1, gtk_spin_button_get_value_as_int (spin_button)));
+}
+
+static void
+setup_editor_page (PlumaPreferencesDialog *dlg)
+{
+ gboolean auto_save;
+ gint auto_save_interval;
+
+ pluma_debug (DEBUG_PREFS);
+
+ /* Set initial state */
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (dlg->priv->tabs_width_spinbutton),
+ (guint) pluma_prefs_manager_get_tabs_size ());
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->insert_spaces_checkbutton),
+ pluma_prefs_manager_get_insert_spaces ());
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->auto_indent_checkbutton),
+ pluma_prefs_manager_get_auto_indent ());
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->backup_copy_checkbutton),
+ pluma_prefs_manager_get_create_backup_copy ());
+
+ auto_save = pluma_prefs_manager_get_auto_save ();
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->auto_save_checkbutton),
+ auto_save);
+
+ auto_save_interval = pluma_prefs_manager_get_auto_save_interval ();
+ if (auto_save_interval <= 0)
+ auto_save_interval = GPM_DEFAULT_AUTO_SAVE_INTERVAL;
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (dlg->priv->auto_save_spinbutton),
+ auto_save_interval);
+
+ /* Set widget sensitivity */
+ gtk_widget_set_sensitive (dlg->priv->tabs_width_hbox,
+ pluma_prefs_manager_tabs_size_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->insert_spaces_checkbutton,
+ pluma_prefs_manager_insert_spaces_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->auto_indent_checkbutton,
+ pluma_prefs_manager_auto_indent_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->backup_copy_checkbutton,
+ pluma_prefs_manager_create_backup_copy_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->autosave_hbox,
+ pluma_prefs_manager_auto_save_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->auto_save_spinbutton,
+ auto_save &&
+ pluma_prefs_manager_auto_save_interval_can_set ());
+
+ /* Connect signal */
+ g_signal_connect (dlg->priv->tabs_width_spinbutton,
+ "value_changed",
+ G_CALLBACK (tabs_width_spinbutton_value_changed),
+ dlg);
+ g_signal_connect (dlg->priv->insert_spaces_checkbutton,
+ "toggled",
+ G_CALLBACK (insert_spaces_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->auto_indent_checkbutton,
+ "toggled",
+ G_CALLBACK (auto_indent_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->auto_save_checkbutton,
+ "toggled",
+ G_CALLBACK (auto_save_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->backup_copy_checkbutton,
+ "toggled",
+ G_CALLBACK (backup_copy_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->auto_save_spinbutton,
+ "value_changed",
+ G_CALLBACK (auto_save_spinbutton_value_changed),
+ dlg);
+}
+
+static void
+display_line_numbers_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ g_return_if_fail (button ==
+ GTK_TOGGLE_BUTTON (dlg->priv->display_line_numbers_checkbutton));
+
+ pluma_prefs_manager_set_display_line_numbers (gtk_toggle_button_get_active (button));
+}
+
+static void
+highlight_current_line_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ g_return_if_fail (button ==
+ GTK_TOGGLE_BUTTON (dlg->priv->highlight_current_line_checkbutton));
+
+ pluma_prefs_manager_set_highlight_current_line (gtk_toggle_button_get_active (button));
+}
+
+static void
+bracket_matching_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ g_return_if_fail (button ==
+ GTK_TOGGLE_BUTTON (dlg->priv->bracket_matching_checkbutton));
+
+ pluma_prefs_manager_set_bracket_matching (
+ gtk_toggle_button_get_active (button));
+}
+
+static gboolean split_button_state = TRUE;
+
+static void
+wrap_mode_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dlg->priv->wrap_text_checkbutton)))
+ {
+ pluma_prefs_manager_set_wrap_mode (GTK_WRAP_NONE);
+
+ gtk_widget_set_sensitive (dlg->priv->split_checkbutton,
+ FALSE);
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (dlg->priv->split_checkbutton), TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dlg->priv->split_checkbutton,
+ TRUE);
+
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (dlg->priv->split_checkbutton), FALSE);
+
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dlg->priv->split_checkbutton)))
+ {
+ split_button_state = TRUE;
+
+ pluma_prefs_manager_set_wrap_mode (GTK_WRAP_WORD);
+ }
+ else
+ {
+ split_button_state = FALSE;
+
+ pluma_prefs_manager_set_wrap_mode (GTK_WRAP_CHAR);
+ }
+ }
+}
+
+static void
+right_margin_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ gboolean active;
+
+ g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->right_margin_checkbutton));
+
+ active = gtk_toggle_button_get_active (button);
+
+ pluma_prefs_manager_set_display_right_margin (active);
+
+ gtk_widget_set_sensitive (dlg->priv->right_margin_position_hbox,
+ active &&
+ pluma_prefs_manager_right_margin_position_can_set ());
+}
+
+static void
+right_margin_position_spinbutton_value_changed (GtkSpinButton *spin_button,
+ PlumaPreferencesDialog *dlg)
+{
+ gint value;
+
+ g_return_if_fail (spin_button == GTK_SPIN_BUTTON (dlg->priv->right_margin_position_spinbutton));
+
+ value = CLAMP (gtk_spin_button_get_value_as_int (spin_button), 1, 160);
+
+ pluma_prefs_manager_set_right_margin_position (value);
+}
+
+static void
+setup_view_page (PlumaPreferencesDialog *dlg)
+{
+ GtkWrapMode wrap_mode;
+ gboolean display_right_margin;
+ gboolean wrap_mode_can_set;
+
+ pluma_debug (DEBUG_PREFS);
+
+ /* Set initial state */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->display_line_numbers_checkbutton),
+ pluma_prefs_manager_get_display_line_numbers ());
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->highlight_current_line_checkbutton),
+ pluma_prefs_manager_get_highlight_current_line ());
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->bracket_matching_checkbutton),
+ pluma_prefs_manager_get_bracket_matching ());
+
+ wrap_mode = pluma_prefs_manager_get_wrap_mode ();
+ switch (wrap_mode )
+ {
+ case GTK_WRAP_WORD:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->priv->wrap_text_checkbutton), TRUE);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->priv->split_checkbutton), TRUE);
+ break;
+ case GTK_WRAP_CHAR:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->priv->wrap_text_checkbutton), TRUE);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->priv->split_checkbutton), FALSE);
+ break;
+ default:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->priv->wrap_text_checkbutton), FALSE);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->priv->split_checkbutton), split_button_state);
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (dlg->priv->split_checkbutton), TRUE);
+
+ }
+
+ display_right_margin = pluma_prefs_manager_get_display_right_margin ();
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dlg->priv->right_margin_checkbutton),
+ display_right_margin);
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dlg->priv->right_margin_position_spinbutton),
+ (guint)CLAMP (pluma_prefs_manager_get_right_margin_position (), 1, 160));
+
+ /* Set widgets sensitivity */
+ gtk_widget_set_sensitive (dlg->priv->display_line_numbers_checkbutton,
+ pluma_prefs_manager_display_line_numbers_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->highlight_current_line_checkbutton,
+ pluma_prefs_manager_highlight_current_line_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->bracket_matching_checkbutton,
+ pluma_prefs_manager_bracket_matching_can_set ());
+ wrap_mode_can_set = pluma_prefs_manager_wrap_mode_can_set ();
+ gtk_widget_set_sensitive (dlg->priv->wrap_text_checkbutton,
+ wrap_mode_can_set);
+ gtk_widget_set_sensitive (dlg->priv->split_checkbutton,
+ wrap_mode_can_set &&
+ (wrap_mode != GTK_WRAP_NONE));
+ gtk_widget_set_sensitive (dlg->priv->right_margin_checkbutton,
+ pluma_prefs_manager_display_right_margin_can_set ());
+ gtk_widget_set_sensitive (dlg->priv->right_margin_position_hbox,
+ display_right_margin &&
+ pluma_prefs_manager_right_margin_position_can_set ());
+
+ /* Connect signals */
+ g_signal_connect (dlg->priv->display_line_numbers_checkbutton,
+ "toggled",
+ G_CALLBACK (display_line_numbers_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->highlight_current_line_checkbutton,
+ "toggled",
+ G_CALLBACK (highlight_current_line_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->bracket_matching_checkbutton,
+ "toggled",
+ G_CALLBACK (bracket_matching_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->wrap_text_checkbutton,
+ "toggled",
+ G_CALLBACK (wrap_mode_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->split_checkbutton,
+ "toggled",
+ G_CALLBACK (wrap_mode_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->right_margin_checkbutton,
+ "toggled",
+ G_CALLBACK (right_margin_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->right_margin_position_spinbutton,
+ "value_changed",
+ G_CALLBACK (right_margin_position_spinbutton_value_changed),
+ dlg);
+}
+
+static void
+default_font_font_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->default_font_checkbutton));
+
+ if (gtk_toggle_button_get_active (button))
+ {
+ gtk_widget_set_sensitive (dlg->priv->font_hbox, FALSE);
+ pluma_prefs_manager_set_use_default_font (TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dlg->priv->font_hbox,
+ pluma_prefs_manager_editor_font_can_set ());
+ pluma_prefs_manager_set_use_default_font (FALSE);
+ }
+}
+
+static void
+editor_font_button_font_set (GtkFontButton *font_button,
+ PlumaPreferencesDialog *dlg)
+{
+ const gchar *font_name;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (font_button == GTK_FONT_BUTTON (dlg->priv->font_button));
+
+ /* FIXME: Can this fail? Gtk docs are a bit terse... 21-02-2004 pbor */
+ font_name = gtk_font_button_get_font_name (font_button);
+ if (!font_name)
+ {
+ g_warning ("Could not get font name");
+ return;
+ }
+
+ pluma_prefs_manager_set_editor_font (font_name);
+}
+
+static void
+setup_font_colors_page_font_section (PlumaPreferencesDialog *dlg)
+{
+ gboolean use_default_font;
+ gchar *editor_font = NULL;
+ gchar *label;
+
+ pluma_debug (DEBUG_PREFS);
+
+ gtk_widget_set_tooltip_text (dlg->priv->font_button,
+ _("Click on this button to select the font to be used by the editor"));
+
+ pluma_utils_set_atk_relation (dlg->priv->font_button,
+ dlg->priv->default_font_checkbutton,
+ ATK_RELATION_CONTROLLED_BY);
+ pluma_utils_set_atk_relation (dlg->priv->default_font_checkbutton,
+ dlg->priv->font_button,
+ ATK_RELATION_CONTROLLER_FOR);
+
+ editor_font = pluma_prefs_manager_get_system_font ();
+ label = g_strdup_printf(_("_Use the system fixed width font (%s)"),
+ editor_font);
+ gtk_button_set_label (GTK_BUTTON (dlg->priv->default_font_checkbutton),
+ label);
+ g_free (editor_font);
+ g_free (label);
+
+ /* read current config and setup initial state */
+ use_default_font = pluma_prefs_manager_get_use_default_font ();
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->default_font_checkbutton),
+ use_default_font);
+
+ editor_font = pluma_prefs_manager_get_editor_font ();
+ if (editor_font != NULL)
+ {
+ gtk_font_button_set_font_name (GTK_FONT_BUTTON (dlg->priv->font_button),
+ editor_font);
+ g_free (editor_font);
+ }
+
+ /* Connect signals */
+ g_signal_connect (dlg->priv->default_font_checkbutton,
+ "toggled",
+ G_CALLBACK (default_font_font_checkbutton_toggled),
+ dlg);
+ g_signal_connect (dlg->priv->font_button,
+ "font_set",
+ G_CALLBACK (editor_font_button_font_set),
+ dlg);
+
+ /* Set initial widget sensitivity */
+ gtk_widget_set_sensitive (dlg->priv->default_font_checkbutton,
+ pluma_prefs_manager_use_default_font_can_set ());
+
+ if (use_default_font)
+ gtk_widget_set_sensitive (dlg->priv->font_hbox, FALSE);
+ else
+ gtk_widget_set_sensitive (dlg->priv->font_hbox,
+ pluma_prefs_manager_editor_font_can_set ());
+}
+
+static void
+set_buttons_sensisitivity_according_to_scheme (PlumaPreferencesDialog *dlg,
+ const gchar *scheme_id)
+{
+ gboolean editable;
+
+ editable = (scheme_id != NULL) &&
+ _pluma_style_scheme_manager_scheme_is_pluma_user_scheme (
+ pluma_get_style_scheme_manager (),
+ scheme_id);
+
+ gtk_widget_set_sensitive (dlg->priv->uninstall_scheme_button,
+ editable);
+}
+
+static void
+style_scheme_changed (GtkWidget *treeview,
+ PlumaPreferencesDialog *dlg)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *id;
+
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (dlg->priv->schemes_treeview), &path, NULL);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (dlg->priv->schemes_treeview_model),
+ &iter, path);
+ gtk_tree_path_free (path);
+ gtk_tree_model_get (GTK_TREE_MODEL (dlg->priv->schemes_treeview_model),
+ &iter, ID_COLUMN, &id, -1);
+
+ pluma_prefs_manager_set_source_style_scheme (id);
+
+ set_buttons_sensisitivity_according_to_scheme (dlg, id);
+
+ g_free (id);
+}
+
+static const gchar *
+ensure_color_scheme_id (const gchar *id)
+{
+ GtkSourceStyleScheme *scheme = NULL;
+ GtkSourceStyleSchemeManager *manager = pluma_get_style_scheme_manager ();
+
+ if (id == NULL)
+ {
+ gchar *pref_id;
+
+ pref_id = pluma_prefs_manager_get_source_style_scheme ();
+ scheme = gtk_source_style_scheme_manager_get_scheme (manager,
+ pref_id);
+ g_free (pref_id);
+ }
+ else
+ {
+ scheme = gtk_source_style_scheme_manager_get_scheme (manager,
+ id);
+ }
+
+ if (scheme == NULL)
+ {
+ /* Fall-back to classic style scheme */
+ scheme = gtk_source_style_scheme_manager_get_scheme (manager,
+ "classic");
+ }
+
+ if (scheme == NULL)
+ {
+ /* Cannot determine default style scheme -> broken GtkSourceView installation */
+ return NULL;
+ }
+
+ return gtk_source_style_scheme_get_id (scheme);
+}
+
+/* If def_id is NULL, use the default scheme as returned by
+ * pluma_style_scheme_manager_get_default_scheme. If this one returns NULL
+ * use the first available scheme as default */
+static const gchar *
+populate_color_scheme_list (PlumaPreferencesDialog *dlg, const gchar *def_id)
+{
+ GSList *schemes;
+ GSList *l;
+
+ gtk_list_store_clear (dlg->priv->schemes_treeview_model);
+
+ def_id = ensure_color_scheme_id (def_id);
+ if (def_id == NULL)
+ {
+ g_warning ("Cannot build the list of available color schemes.\n"
+ "Please check your GtkSourceView installation.");
+ return NULL;
+ }
+
+ schemes = pluma_style_scheme_manager_list_schemes_sorted (pluma_get_style_scheme_manager ());
+ l = schemes;
+ while (l != NULL)
+ {
+ GtkSourceStyleScheme *scheme;
+ const gchar *id;
+ const gchar *name;
+ const gchar *description;
+ GtkTreeIter iter;
+
+ scheme = GTK_SOURCE_STYLE_SCHEME (l->data);
+
+ id = gtk_source_style_scheme_get_id (scheme);
+ name = gtk_source_style_scheme_get_name (scheme);
+ description = gtk_source_style_scheme_get_description (scheme);
+
+ gtk_list_store_append (dlg->priv->schemes_treeview_model, &iter);
+ gtk_list_store_set (dlg->priv->schemes_treeview_model,
+ &iter,
+ ID_COLUMN, id,
+ NAME_COLUMN, name,
+ DESC_COLUMN, description,
+ -1);
+
+ g_return_val_if_fail (def_id != NULL, NULL);
+ if (strcmp (id, def_id) == 0)
+ {
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->priv->schemes_treeview));
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+
+ l = g_slist_next (l);
+ }
+
+ g_slist_free (schemes);
+
+ return def_id;
+}
+
+static void
+add_scheme_chooser_response_cb (GtkDialog *chooser,
+ gint res_id,
+ PlumaPreferencesDialog *dlg)
+{
+ gchar* filename;
+ const gchar *scheme_id;
+
+ if (res_id != GTK_RESPONSE_ACCEPT)
+ {
+ gtk_widget_hide (GTK_WIDGET (chooser));
+ return;
+ }
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
+ if (filename == NULL)
+ return;
+
+ gtk_widget_hide (GTK_WIDGET (chooser));
+
+ scheme_id = _pluma_style_scheme_manager_install_scheme (
+ pluma_get_style_scheme_manager (),
+ filename);
+ g_free (filename);
+
+ if (scheme_id == NULL)
+ {
+ pluma_warning (GTK_WINDOW (dlg),
+ _("The selected color scheme cannot be installed."));
+
+ return;
+ }
+
+ pluma_prefs_manager_set_source_style_scheme (scheme_id);
+
+ scheme_id = populate_color_scheme_list (dlg, scheme_id);
+
+ set_buttons_sensisitivity_according_to_scheme (dlg, scheme_id);
+}
+
+static void
+install_scheme_clicked (GtkButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ GtkWidget *chooser;
+ GtkFileFilter *filter;
+
+ if (dlg->priv->install_scheme_file_schooser != NULL) {
+ gtk_window_present (GTK_WINDOW (dlg->priv->install_scheme_file_schooser));
+ gtk_widget_grab_focus (dlg->priv->install_scheme_file_schooser);
+ return;
+ }
+
+ chooser = gtk_file_chooser_dialog_new (_("Add Scheme"),
+ GTK_WINDOW (dlg),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ NULL);
+
+ pluma_dialog_add_button (GTK_DIALOG (chooser),
+ _("A_dd Scheme"),
+ GTK_STOCK_ADD,
+ GTK_RESPONSE_ACCEPT);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (chooser), TRUE);
+
+ /* Filters */
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("Color Scheme Files"));
+ gtk_file_filter_add_pattern (filter, "*.xml");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("All Files"));
+ gtk_file_filter_add_pattern (filter, "*");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT);
+
+ g_signal_connect (chooser,
+ "response",
+ G_CALLBACK (add_scheme_chooser_response_cb),
+ dlg);
+
+ dlg->priv->install_scheme_file_schooser = chooser;
+
+ g_object_add_weak_pointer (G_OBJECT (chooser),
+ (gpointer) &dlg->priv->install_scheme_file_schooser);
+
+ gtk_widget_show (chooser);
+}
+
+static void
+uninstall_scheme_clicked (GtkButton *button,
+ PlumaPreferencesDialog *dlg)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->priv->schemes_treeview));
+ model = GTK_TREE_MODEL (dlg->priv->schemes_treeview_model);
+
+ if (gtk_tree_selection_get_selected (selection,
+ &model,
+ &iter))
+ {
+ gchar *id;
+ gchar *name;
+
+ gtk_tree_model_get (model, &iter,
+ ID_COLUMN, &id,
+ NAME_COLUMN, &name,
+ -1);
+
+ if (!_pluma_style_scheme_manager_uninstall_scheme (pluma_get_style_scheme_manager (), id))
+ {
+ pluma_warning (GTK_WINDOW (dlg),
+ _("Could not remove color scheme \"%s\"."),
+ name);
+ }
+ else
+ {
+ const gchar *real_new_id;
+ gchar *new_id = NULL;
+ GtkTreePath *path;
+ GtkTreeIter new_iter;
+ gboolean new_iter_set = FALSE;
+
+ /* If the removed style scheme is the last of the list,
+ * set as new default style scheme the previous one,
+ * otherwise set the next one.
+ * To make this possible, we need to get the id of the
+ * new default style scheme before re-populating the list.
+ * Fall back to "classic" if it is not possible to get
+ * the id
+ */
+ path = gtk_tree_model_get_path (model, &iter);
+
+ /* Try to move to the next path */
+ gtk_tree_path_next (path);
+ if (!gtk_tree_model_get_iter (model, &new_iter, path))
+ {
+ /* It seems the removed style scheme was the
+ * last of the list. Try to move to the
+ * previous one */
+ gtk_tree_path_free (path);
+
+ path = gtk_tree_model_get_path (model, &iter);
+
+ gtk_tree_path_prev (path);
+ if (gtk_tree_model_get_iter (model, &new_iter, path))
+ new_iter_set = TRUE;
+ }
+ else
+ new_iter_set = TRUE;
+
+ gtk_tree_path_free (path);
+
+ if (new_iter_set)
+ gtk_tree_model_get (model, &new_iter,
+ ID_COLUMN, &new_id,
+ -1);
+
+ real_new_id = populate_color_scheme_list (dlg, new_id);
+ g_free (new_id);
+
+ set_buttons_sensisitivity_according_to_scheme (dlg, real_new_id);
+
+ if (real_new_id != NULL)
+ pluma_prefs_manager_set_source_style_scheme (real_new_id);
+ }
+
+ g_free (id);
+ g_free (name);
+ }
+}
+
+static void
+scheme_description_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *name;
+ gchar *desc;
+ gchar *text;
+
+ gtk_tree_model_get (model, iter,
+ NAME_COLUMN, &name,
+ DESC_COLUMN, &desc,
+ -1);
+
+ if (desc != NULL)
+ {
+ text = g_markup_printf_escaped ("<b>%s</b> - %s",
+ name,
+ desc);
+ }
+ else
+ {
+ text = g_markup_printf_escaped ("<b>%s</b>",
+ name);
+ }
+
+ g_free (name);
+ g_free (desc);
+
+ g_object_set (G_OBJECT (renderer),
+ "markup",
+ text,
+ NULL);
+
+ g_free (text);
+}
+
+static void
+setup_font_colors_page_style_scheme_section (PlumaPreferencesDialog *dlg)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+ const gchar *def_id;
+
+ pluma_debug (DEBUG_PREFS);
+
+ /* Create GtkListStore for styles & setup treeview. */
+ dlg->priv->schemes_treeview_model = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (dlg->priv->schemes_treeview_model),
+ 0,
+ GTK_SORT_ASCENDING);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dlg->priv->schemes_treeview),
+ GTK_TREE_MODEL (dlg->priv->schemes_treeview_model));
+
+ column = gtk_tree_view_column_new ();
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_cell_data_func (column,
+ renderer,
+ scheme_description_cell_data_func,
+ dlg,
+ NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->priv->schemes_treeview),
+ column);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->priv->schemes_treeview));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+ def_id = populate_color_scheme_list (dlg, NULL);
+
+ /* Connect signals */
+ g_signal_connect (dlg->priv->schemes_treeview,
+ "cursor-changed",
+ G_CALLBACK (style_scheme_changed),
+ dlg);
+ g_signal_connect (dlg->priv->install_scheme_button,
+ "clicked",
+ G_CALLBACK (install_scheme_clicked),
+ dlg);
+ g_signal_connect (dlg->priv->uninstall_scheme_button,
+ "clicked",
+ G_CALLBACK (uninstall_scheme_clicked),
+ dlg);
+
+ /* Set initial widget sensitivity */
+ set_buttons_sensisitivity_according_to_scheme (dlg, def_id);
+}
+
+static void
+setup_font_colors_page (PlumaPreferencesDialog *dlg)
+{
+ setup_font_colors_page_font_section (dlg);
+ setup_font_colors_page_style_scheme_section (dlg);
+}
+
+static void
+setup_plugins_page (PlumaPreferencesDialog *dlg)
+{
+ GtkWidget *page_content;
+
+ pluma_debug (DEBUG_PREFS);
+
+ page_content = pluma_plugin_manager_new ();
+ g_return_if_fail (page_content != NULL);
+
+ gtk_box_pack_start (GTK_BOX (dlg->priv->plugin_manager_place_holder),
+ page_content,
+ TRUE,
+ TRUE,
+ 0);
+
+ gtk_widget_show_all (page_content);
+}
+
+static void
+pluma_preferences_dialog_init (PlumaPreferencesDialog *dlg)
+{
+ GtkWidget *error_widget;
+ gboolean ret;
+ gchar *file;
+ gchar *root_objects[] = {
+ "notebook",
+ "adjustment1",
+ "adjustment2",
+ "adjustment3",
+ "install_scheme_image",
+ NULL
+ };
+
+ pluma_debug (DEBUG_PREFS);
+
+ dlg->priv = PLUMA_PREFERENCES_DIALOG_GET_PRIVATE (dlg);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dlg),
+ GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE,
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP,
+ NULL);
+
+ gtk_window_set_title (GTK_WINDOW (dlg), _("pluma Preferences"));
+ gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dlg), TRUE);
+
+ /* HIG defaults */
+ gtk_container_set_border_width (GTK_CONTAINER (dlg), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), 2); /* 2 * 5 + 2 = 12 */
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dlg))), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dlg))), 6);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (dialog_response_handler),
+ NULL);
+
+ file = pluma_dirs_get_ui_file ("pluma-preferences-dialog.ui");
+ ret = pluma_utils_get_ui_objects (file,
+ root_objects,
+ &error_widget,
+
+ "notebook", &dlg->priv->notebook,
+
+ "display_line_numbers_checkbutton", &dlg->priv->display_line_numbers_checkbutton,
+ "highlight_current_line_checkbutton", &dlg->priv->highlight_current_line_checkbutton,
+ "bracket_matching_checkbutton", &dlg->priv->bracket_matching_checkbutton,
+ "wrap_text_checkbutton", &dlg->priv->wrap_text_checkbutton,
+ "split_checkbutton", &dlg->priv->split_checkbutton,
+
+ "right_margin_checkbutton", &dlg->priv->right_margin_checkbutton,
+ "right_margin_position_spinbutton", &dlg->priv->right_margin_position_spinbutton,
+ "right_margin_position_hbox", &dlg->priv->right_margin_position_hbox,
+
+ "tabs_width_spinbutton", &dlg->priv->tabs_width_spinbutton,
+ "tabs_width_hbox", &dlg->priv->tabs_width_hbox,
+ "insert_spaces_checkbutton", &dlg->priv->insert_spaces_checkbutton,
+
+ "auto_indent_checkbutton", &dlg->priv->auto_indent_checkbutton,
+
+ "autosave_hbox", &dlg->priv->autosave_hbox,
+ "backup_copy_checkbutton", &dlg->priv->backup_copy_checkbutton,
+ "auto_save_checkbutton", &dlg->priv->auto_save_checkbutton,
+ "auto_save_spinbutton", &dlg->priv->auto_save_spinbutton,
+
+ "default_font_checkbutton", &dlg->priv->default_font_checkbutton,
+ "font_button", &dlg->priv->font_button,
+ "font_hbox", &dlg->priv->font_hbox,
+
+ "schemes_treeview", &dlg->priv->schemes_treeview,
+ "install_scheme_button", &dlg->priv->install_scheme_button,
+ "uninstall_scheme_button", &dlg->priv->uninstall_scheme_button,
+
+ "plugin_manager_place_holder", &dlg->priv->plugin_manager_place_holder,
+
+ NULL);
+ g_free (file);
+
+ if (!ret)
+ {
+ gtk_widget_show (error_widget);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ error_widget,
+ TRUE, TRUE, 0);
+
+ return;
+ }
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ dlg->priv->notebook, FALSE, FALSE, 0);
+ g_object_unref (dlg->priv->notebook);
+ gtk_container_set_border_width (GTK_CONTAINER (dlg->priv->notebook), 5);
+
+ setup_editor_page (dlg);
+ setup_view_page (dlg);
+ setup_font_colors_page (dlg);
+ setup_plugins_page (dlg);
+}
+
+void
+pluma_show_preferences_dialog (PlumaWindow *parent)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (PLUMA_IS_WINDOW (parent));
+
+ if (preferences_dialog == NULL)
+ {
+ preferences_dialog = GTK_WIDGET (g_object_new (PLUMA_TYPE_PREFERENCES_DIALOG, NULL));
+ g_signal_connect (preferences_dialog,
+ "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &preferences_dialog);
+ }
+
+ if (GTK_WINDOW (parent) != gtk_window_get_transient_for (GTK_WINDOW (preferences_dialog)))
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (preferences_dialog),
+ GTK_WINDOW (parent));
+ }
+
+ gtk_window_present (GTK_WINDOW (preferences_dialog));
+}
diff --git a/pluma/dialogs/pluma-preferences-dialog.h b/pluma/dialogs/pluma-preferences-dialog.h
new file mode 100755
index 00000000..d66a79d7
--- /dev/null
+++ b/pluma/dialogs/pluma-preferences-dialog.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-preferences-dialog.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2001-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PREFERENCES_DIALOG_H__
+#define __PLUMA_PREFERENCES_DIALOG_H__
+
+#include "pluma-window.h"
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_PREFERENCES_DIALOG (pluma_preferences_dialog_get_type())
+#define PLUMA_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PREFERENCES_DIALOG, PlumaPreferencesDialog))
+#define PLUMA_PREFERENCES_DIALOG_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PREFERENCES_DIALOG, PlumaPreferencesDialog const))
+#define PLUMA_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_PREFERENCES_DIALOG, PlumaPreferencesDialogClass))
+#define PLUMA_IS_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_PREFERENCES_DIALOG))
+#define PLUMA_IS_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PREFERENCES_DIALOG))
+#define PLUMA_PREFERENCES_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_PREFERENCES_DIALOG, PlumaPreferencesDialogClass))
+
+
+/* Private structure type */
+typedef struct _PlumaPreferencesDialogPrivate PlumaPreferencesDialogPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaPreferencesDialog PlumaPreferencesDialog;
+
+struct _PlumaPreferencesDialog
+{
+ GtkDialog dialog;
+
+ /*< private > */
+ PlumaPreferencesDialogPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaPreferencesDialogClass PlumaPreferencesDialogClass;
+
+struct _PlumaPreferencesDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType pluma_preferences_dialog_get_type (void) G_GNUC_CONST;
+
+void pluma_show_preferences_dialog (PlumaWindow *parent);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PREFERENCES_DIALOG_H__ */
+
diff --git a/pluma/dialogs/pluma-preferences-dialog.ui b/pluma/dialogs/pluma-preferences-dialog.ui
new file mode 100755
index 00000000..42d41e31
--- /dev/null
+++ b/pluma/dialogs/pluma-preferences-dialog.ui
@@ -0,0 +1,1107 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="value">80</property>
+ <property name="lower">1</property>
+ <property name="upper">160</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="value">8</property>
+ <property name="lower">1</property>
+ <property name="upper">24</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">4</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="value">8</property>
+ <property name="lower">1</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkImage" id="install_scheme_image">
+ <property name="stock">gtk-add</property>
+ </object>
+ <object class="GtkDialog" id="preferences_dialog">
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</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>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox228">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox226">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label848">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Text Wrapping</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox142">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label849">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="wrap_mode_frame">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="wrap_text_checkbutton">
+ <property name="label" translatable="yes">Enable text _wrapping</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="split_checkbutton">
+ <property name="label" translatable="yes">Do not _split words over two lines</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">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">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox217">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label854">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Line Numbers</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox137">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label843">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox222">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="display_line_numbers_checkbutton">
+ <property name="label" translatable="yes">_Display line numbers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </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="GtkVBox" id="vbox244">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label876">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Current Line</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox161">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label877">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox245">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="highlight_current_line_checkbutton">
+ <property name="label" translatable="yes">Highlight current _line</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox230">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label855">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Right Margin</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox145">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label856">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox231">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="right_margin_checkbutton">
+ <property name="label" translatable="yes">Display right _margin</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="right_margin_position_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label857">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Right margin at column:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">right_margin_position_spinbutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="right_margin_position_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">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="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox249">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label881">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Bracket Matching</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox163">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label882">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox250">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="bracket_matching_checkbutton">
+ <property name="label" translatable="yes">Highlight matching _bracket</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label853">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">View</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox224">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox225">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label846">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Tab Stops</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox141">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label847">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox205">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="tabs_width_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label98">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Tab width:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">tabs_width_spinbutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="tabs_width_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">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>
+ <child>
+ <object class="GtkCheckButton" id="insert_spaces_checkbutton">
+ <property name="label" translatable="yes">Insert _spaces instead of tabs</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">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="fill">False</property>
+ <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>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox227">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label851">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Automatic Indentation</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox143">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label852">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="auto_indent_checkbutton">
+ <property name="label" translatable="yes">_Enable automatic indentation</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">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="fill">False</property>
+ <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="GtkVBox" id="vbox232">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label859">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">File Saving</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox147">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label860">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox187">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="backup_copy_checkbutton">
+ <property name="label" translatable="yes">Create a _backup copy of files before saving</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="autosave_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="auto_save_checkbutton">
+ <property name="label" translatable="yes">_Autosave files every</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="auto_save_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment3</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label97">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_minutes</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">auto_save_spinbutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </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>
+ </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>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label829">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Editor</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox202">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox185">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label819">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Font</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox116">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label800">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox183">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="default_font_checkbutton">
+ <property name="label">_Use the system fixed width font (%s)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="font_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="font_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Editor _font: </property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">font_button</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="font_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="title" translatable="yes">Pick the editor font</property>
+ <property name="use_font">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">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox14">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label798">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Color Scheme</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox115">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label797">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">etched-in</property>
+ <child>
+ <object class="GtkTreeView" id="schemes_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="install_scheme_button">
+ <property name="label" translatable="yes">_Add...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="image">install_scheme_image</property>
+ <property name="use_underline">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="uninstall_scheme_button">
+ <property name="label">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">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="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label830">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Font &amp; Colors</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="plugin_manager_place_holder">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label868">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Plugins</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="helpbutton1">
+ <property name="label">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">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="closebutton1">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">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="-11">helpbutton1</action-widget>
+ <action-widget response="-7">closebutton1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/pluma/dialogs/pluma-search-dialog.c b/pluma/dialogs/pluma-search-dialog.c
new file mode 100755
index 00000000..af1b3795
--- /dev/null
+++ b/pluma/dialogs/pluma-search-dialog.c
@@ -0,0 +1,634 @@
+/*
+ * pluma-search-dialog.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "pluma-search-dialog.h"
+#include "pluma-history-entry.h"
+#include "pluma-utils.h"
+#include "pluma-marshal.h"
+#include "pluma-dirs.h"
+
+#define PLUMA_SEARCH_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_SEARCH_DIALOG, \
+ PlumaSearchDialogPrivate))
+
+/* Signals */
+enum
+{
+ SHOW_REPLACE,
+ LAST_SIGNAL
+};
+
+static guint dialog_signals [LAST_SIGNAL] = { 0 };
+
+struct _PlumaSearchDialogPrivate
+{
+ gboolean show_replace;
+
+ GtkWidget *table;
+ GtkWidget *search_label;
+ GtkWidget *search_entry;
+ GtkWidget *search_text_entry;
+ GtkWidget *replace_label;
+ GtkWidget *replace_entry;
+ GtkWidget *replace_text_entry;
+ GtkWidget *match_case_checkbutton;
+ GtkWidget *entire_word_checkbutton;
+ GtkWidget *backwards_checkbutton;
+ GtkWidget *wrap_around_checkbutton;
+ GtkWidget *find_button;
+ GtkWidget *replace_button;
+ GtkWidget *replace_all_button;
+
+ gboolean ui_error;
+};
+
+G_DEFINE_TYPE(PlumaSearchDialog, pluma_search_dialog, GTK_TYPE_DIALOG)
+
+enum
+{
+ PROP_0,
+ PROP_SHOW_REPLACE
+};
+
+static void
+pluma_search_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaSearchDialog *dlg = PLUMA_SEARCH_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_SHOW_REPLACE:
+ pluma_search_dialog_set_show_replace (dlg,
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_search_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaSearchDialog *dlg = PLUMA_SEARCH_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_SHOW_REPLACE:
+ g_value_set_boolean (value, dlg->priv->show_replace);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+void
+pluma_search_dialog_present_with_time (PlumaSearchDialog *dialog,
+ guint32 timestamp)
+{
+ g_return_if_fail (PLUMA_SEARCH_DIALOG (dialog));
+
+ gtk_window_present_with_time (GTK_WINDOW (dialog), timestamp);
+
+ gtk_widget_grab_focus (dialog->priv->search_text_entry);
+}
+
+static gboolean
+show_replace (PlumaSearchDialog *dlg)
+{
+ pluma_search_dialog_set_show_replace (dlg, TRUE);
+
+ return TRUE;
+}
+
+static void
+pluma_search_dialog_class_init (PlumaSearchDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkBindingSet *binding_set;
+
+ object_class->set_property = pluma_search_dialog_set_property;
+ object_class->get_property = pluma_search_dialog_get_property;
+
+ klass->show_replace = show_replace;
+
+ dialog_signals[SHOW_REPLACE] =
+ g_signal_new ("show_replace",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaSearchDialogClass, show_replace),
+ NULL, NULL,
+ pluma_marshal_BOOLEAN__NONE,
+ G_TYPE_BOOLEAN, 0);
+
+ g_object_class_install_property (object_class, PROP_SHOW_REPLACE,
+ g_param_spec_boolean ("show-replace",
+ "Show Replace",
+ "Whether the dialog is used for Search&Replace",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (PlumaSearchDialogPrivate));
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ /* Note: we cannot use the keyval/modifier associated with the
+ * GTK_STOCK_FIND_AND_REPLACE stock item since MATE HIG suggests Ctrl+h
+ * for Replace while gtk+ uses Ctrl+r */
+ gtk_binding_entry_add_signal (binding_set, GDK_h, GDK_CONTROL_MASK, "show_replace", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_H, GDK_CONTROL_MASK, "show_replace", 0);
+}
+
+static void
+insert_text_handler (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ gpointer data)
+{
+ static gboolean insert_text = FALSE;
+ gchar *escaped_text;
+ gint new_len;
+
+ /* To avoid recursive behavior */
+ if (insert_text)
+ return;
+
+ escaped_text = pluma_utils_escape_search_text (text);
+
+ new_len = strlen (escaped_text);
+
+ if (new_len == length)
+ {
+ g_free (escaped_text);
+ return;
+ }
+
+ insert_text = TRUE;
+
+ g_signal_stop_emission_by_name (editable, "insert_text");
+
+ gtk_editable_insert_text (editable, escaped_text, new_len, position);
+
+ insert_text = FALSE;
+
+ g_free (escaped_text);
+}
+
+static void
+search_text_entry_changed (GtkEditable *editable,
+ PlumaSearchDialog *dialog)
+{
+ const gchar *search_string;
+
+ search_string = gtk_entry_get_text (GTK_ENTRY (editable));
+ g_return_if_fail (search_string != NULL);
+
+ if (*search_string != '\0')
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_FIND_RESPONSE, TRUE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE, TRUE);
+ }
+ else
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_FIND_RESPONSE, FALSE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE, FALSE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE, FALSE);
+ }
+}
+
+static void
+response_handler (PlumaSearchDialog *dialog,
+ gint response_id,
+ gpointer data)
+{
+ const gchar *str;
+
+ switch (response_id)
+ {
+ case PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE:
+ case PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE:
+ str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_text_entry));
+ if (*str != '\0')
+ {
+ gchar *text;
+
+ text = pluma_utils_unescape_search_text (str);
+ pluma_history_entry_prepend_text
+ (PLUMA_HISTORY_ENTRY (dialog->priv->replace_entry),
+ text);
+
+ g_free (text);
+ }
+ /* fall through, so that we also save the find entry */
+ case PLUMA_SEARCH_DIALOG_FIND_RESPONSE:
+ str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->search_text_entry));
+ if (*str != '\0')
+ {
+ gchar *text;
+
+ text = pluma_utils_unescape_search_text (str);
+ pluma_history_entry_prepend_text
+ (PLUMA_HISTORY_ENTRY (dialog->priv->search_entry),
+ text);
+
+ g_free (text);
+ }
+ }
+}
+
+static void
+show_replace_widgets (PlumaSearchDialog *dlg,
+ gboolean show_replace)
+{
+ if (show_replace)
+ {
+ gtk_widget_show (dlg->priv->replace_label);
+ gtk_widget_show (dlg->priv->replace_entry);
+ gtk_widget_show (dlg->priv->replace_all_button);
+ gtk_widget_show (dlg->priv->replace_button);
+
+ gtk_table_set_row_spacings (GTK_TABLE (dlg->priv->table), 12);
+
+ gtk_window_set_title (GTK_WINDOW (dlg), _("Replace"));
+ }
+ else
+ {
+ gtk_widget_hide (dlg->priv->replace_label);
+ gtk_widget_hide (dlg->priv->replace_entry);
+ gtk_widget_hide (dlg->priv->replace_all_button);
+ gtk_widget_hide (dlg->priv->replace_button);
+
+ gtk_table_set_row_spacings (GTK_TABLE (dlg->priv->table), 0);
+
+ gtk_window_set_title (GTK_WINDOW (dlg), _("Find"));
+ }
+
+ gtk_widget_show (dlg->priv->find_button);
+}
+
+static void
+pluma_search_dialog_init (PlumaSearchDialog *dlg)
+{
+ GtkWidget *content;
+ GtkWidget *error_widget;
+ gboolean ret;
+ gchar *file;
+ gchar *root_objects[] = {
+ "search_dialog_content",
+ NULL
+ };
+
+ dlg->priv = PLUMA_SEARCH_DIALOG_GET_PRIVATE (dlg);
+
+ gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dlg), TRUE);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dlg),
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL,
+ NULL);
+
+ /* HIG defaults */
+ gtk_container_set_border_width (GTK_CONTAINER (dlg), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ 2); /* 2 * 5 + 2 = 12 */
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dlg))),
+ 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dlg))),
+ 6);
+
+ file = pluma_dirs_get_ui_file ("pluma-search-dialog.ui");
+ ret = pluma_utils_get_ui_objects (file,
+ root_objects,
+ &error_widget,
+ "search_dialog_content", &content,
+ "table", &dlg->priv->table,
+ "search_label", &dlg->priv->search_label,
+ "replace_with_label", &dlg->priv->replace_label,
+ "match_case_checkbutton", &dlg->priv->match_case_checkbutton,
+ "entire_word_checkbutton", &dlg->priv->entire_word_checkbutton,
+ "search_backwards_checkbutton", &dlg->priv->backwards_checkbutton,
+ "wrap_around_checkbutton", &dlg->priv->wrap_around_checkbutton,
+ NULL);
+ g_free (file);
+
+ if (!ret)
+ {
+ gtk_widget_show (error_widget);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ error_widget,
+ TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (error_widget),
+ 5);
+
+ dlg->priv->ui_error = TRUE;
+
+ return;
+ }
+
+ dlg->priv->search_entry = pluma_history_entry_new ("pluma2_search_for_entry",
+ TRUE);
+ gtk_widget_set_size_request (dlg->priv->search_entry, 300, -1);
+ pluma_history_entry_set_escape_func
+ (PLUMA_HISTORY_ENTRY (dlg->priv->search_entry),
+ (PlumaHistoryEntryEscapeFunc) pluma_utils_escape_search_text);
+
+ dlg->priv->search_text_entry = pluma_history_entry_get_entry
+ (PLUMA_HISTORY_ENTRY (dlg->priv->search_entry));
+ gtk_entry_set_activates_default (GTK_ENTRY (dlg->priv->search_text_entry),
+ TRUE);
+ gtk_widget_show (dlg->priv->search_entry);
+ gtk_table_attach_defaults (GTK_TABLE (dlg->priv->table),
+ dlg->priv->search_entry,
+ 1, 2, 0, 1);
+
+ dlg->priv->replace_entry = pluma_history_entry_new ("pluma2_replace_with_entry",
+ TRUE);
+ pluma_history_entry_set_escape_func
+ (PLUMA_HISTORY_ENTRY (dlg->priv->replace_entry),
+ (PlumaHistoryEntryEscapeFunc) pluma_utils_escape_search_text);
+
+ dlg->priv->replace_text_entry = pluma_history_entry_get_entry
+ (PLUMA_HISTORY_ENTRY (dlg->priv->replace_entry));
+ gtk_entry_set_activates_default (GTK_ENTRY (dlg->priv->replace_text_entry),
+ TRUE);
+ gtk_widget_show (dlg->priv->replace_entry);
+ gtk_table_attach_defaults (GTK_TABLE (dlg->priv->table),
+ dlg->priv->replace_entry,
+ 1, 2, 1, 2);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (dlg->priv->search_label),
+ dlg->priv->search_entry);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (dlg->priv->replace_label),
+ dlg->priv->replace_entry);
+
+ dlg->priv->find_button = gtk_button_new_from_stock (GTK_STOCK_FIND);
+ dlg->priv->replace_all_button = gtk_button_new_with_mnemonic (_("Replace _All"));
+ dlg->priv->replace_button = pluma_gtk_button_new_with_stock_icon (_("_Replace"),
+ GTK_STOCK_FIND_AND_REPLACE);
+
+ gtk_dialog_add_action_widget (GTK_DIALOG (dlg),
+ dlg->priv->replace_all_button,
+ PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE);
+ gtk_dialog_add_action_widget (GTK_DIALOG (dlg),
+ dlg->priv->replace_button,
+ PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE);
+ gtk_dialog_add_action_widget (GTK_DIALOG (dlg),
+ dlg->priv->find_button,
+ PLUMA_SEARCH_DIALOG_FIND_RESPONSE);
+ g_object_set (G_OBJECT (dlg->priv->find_button),
+ "can-default", TRUE,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dlg),
+ PLUMA_SEARCH_DIALOG_FIND_RESPONSE);
+
+ /* insensitive by default */
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg),
+ PLUMA_SEARCH_DIALOG_FIND_RESPONSE,
+ FALSE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg),
+ PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE,
+ FALSE);
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg),
+ PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE,
+ FALSE);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ content, FALSE, FALSE, 0);
+ g_object_unref (content);
+ gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+
+ g_signal_connect (dlg->priv->search_text_entry,
+ "insert_text",
+ G_CALLBACK (insert_text_handler),
+ NULL);
+ g_signal_connect (dlg->priv->replace_text_entry,
+ "insert_text",
+ G_CALLBACK (insert_text_handler),
+ NULL);
+ g_signal_connect (dlg->priv->search_text_entry,
+ "changed",
+ G_CALLBACK (search_text_entry_changed),
+ dlg);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (response_handler),
+ NULL);
+}
+
+GtkWidget *
+pluma_search_dialog_new (GtkWindow *parent,
+ gboolean show_replace)
+{
+ PlumaSearchDialog *dlg;
+
+ dlg = g_object_new (PLUMA_TYPE_SEARCH_DIALOG,
+ "show-replace", show_replace,
+ NULL);
+
+ if (parent != NULL)
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (dlg),
+ parent);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dlg),
+ TRUE);
+ }
+
+ return GTK_WIDGET (dlg);
+}
+
+gboolean
+pluma_search_dialog_get_show_replace (PlumaSearchDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog), FALSE);
+
+ return dialog->priv->show_replace;
+}
+
+void
+pluma_search_dialog_set_show_replace (PlumaSearchDialog *dialog,
+ gboolean show_replace)
+{
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog));
+
+ if (dialog->priv->ui_error)
+ return;
+
+ dialog->priv->show_replace = show_replace != FALSE;
+ show_replace_widgets (dialog, dialog->priv->show_replace);
+
+ g_object_notify (G_OBJECT (dialog), "show-replace");
+}
+
+void
+pluma_search_dialog_set_search_text (PlumaSearchDialog *dialog,
+ const gchar *text)
+{
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog));
+ g_return_if_fail (text != NULL);
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->search_text_entry),
+ text);
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_FIND_RESPONSE,
+ (text != '\0'));
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE,
+ (text != '\0'));
+}
+
+/*
+ * The text must be unescaped before searching.
+ */
+const gchar *
+pluma_search_dialog_get_search_text (PlumaSearchDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog), NULL);
+
+ return gtk_entry_get_text (GTK_ENTRY (dialog->priv->search_text_entry));
+}
+
+void
+pluma_search_dialog_set_replace_text (PlumaSearchDialog *dialog,
+ const gchar *text)
+{
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog));
+ g_return_if_fail (text != NULL);
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->replace_text_entry),
+ text);
+}
+
+const gchar *
+pluma_search_dialog_get_replace_text (PlumaSearchDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog), NULL);
+
+ return gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_text_entry));
+}
+
+void
+pluma_search_dialog_set_match_case (PlumaSearchDialog *dialog,
+ gboolean match_case)
+{
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog));
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->match_case_checkbutton),
+ match_case);
+}
+
+gboolean
+pluma_search_dialog_get_match_case (PlumaSearchDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog), FALSE);
+
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->match_case_checkbutton));
+}
+
+void
+pluma_search_dialog_set_entire_word (PlumaSearchDialog *dialog,
+ gboolean entire_word)
+{
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog));
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->entire_word_checkbutton),
+ entire_word);
+}
+
+gboolean
+pluma_search_dialog_get_entire_word (PlumaSearchDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog), FALSE);
+
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->entire_word_checkbutton));
+}
+
+void
+pluma_search_dialog_set_backwards (PlumaSearchDialog *dialog,
+ gboolean backwards)
+{
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog));
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->backwards_checkbutton),
+ backwards);
+}
+
+gboolean
+pluma_search_dialog_get_backwards (PlumaSearchDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog), FALSE);
+
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->backwards_checkbutton));
+}
+
+void
+pluma_search_dialog_set_wrap_around (PlumaSearchDialog *dialog,
+ gboolean wrap_around)
+{
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog));
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->wrap_around_checkbutton),
+ wrap_around);
+}
+
+gboolean
+pluma_search_dialog_get_wrap_around (PlumaSearchDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_SEARCH_DIALOG (dialog), FALSE);
+
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->wrap_around_checkbutton));
+}
diff --git a/pluma/dialogs/pluma-search-dialog.h b/pluma/dialogs/pluma-search-dialog.h
new file mode 100755
index 00000000..f45672a3
--- /dev/null
+++ b/pluma/dialogs/pluma-search-dialog.h
@@ -0,0 +1,128 @@
+/*
+ * pluma-search-dialog.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_SEARCH_DIALOG_H__
+#define __PLUMA_SEARCH_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_SEARCH_DIALOG (pluma_search_dialog_get_type())
+#define PLUMA_SEARCH_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_SEARCH_DIALOG, PlumaSearchDialog))
+#define PLUMA_SEARCH_DIALOG_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_SEARCH_DIALOG, PlumaSearchDialog const))
+#define PLUMA_SEARCH_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_SEARCH_DIALOG, PlumaSearchDialogClass))
+#define PLUMA_IS_SEARCH_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_SEARCH_DIALOG))
+#define PLUMA_IS_SEARCH_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_SEARCH_DIALOG))
+#define PLUMA_SEARCH_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_SEARCH_DIALOG, PlumaSearchDialogClass))
+
+/* Private structure type */
+typedef struct _PlumaSearchDialogPrivate PlumaSearchDialogPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaSearchDialog PlumaSearchDialog;
+
+struct _PlumaSearchDialog
+{
+ GtkDialog dialog;
+
+ /*< private > */
+ PlumaSearchDialogPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaSearchDialogClass PlumaSearchDialogClass;
+
+struct _PlumaSearchDialogClass
+{
+ GtkDialogClass parent_class;
+
+ /* Key bindings */
+ gboolean (* show_replace) (PlumaSearchDialog *dlg);
+};
+
+enum
+{
+ PLUMA_SEARCH_DIALOG_FIND_RESPONSE = 100,
+ PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE,
+ PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE
+};
+
+/*
+ * Public methods
+ */
+GType pluma_search_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_search_dialog_new (GtkWindow *parent,
+ gboolean show_replace);
+
+void pluma_search_dialog_present_with_time (PlumaSearchDialog *dialog,
+ guint32 timestamp);
+
+gboolean pluma_search_dialog_get_show_replace (PlumaSearchDialog *dialog);
+
+void pluma_search_dialog_set_show_replace (PlumaSearchDialog *dialog,
+ gboolean show_replace);
+
+
+void pluma_search_dialog_set_search_text (PlumaSearchDialog *dialog,
+ const gchar *text);
+const gchar *pluma_search_dialog_get_search_text (PlumaSearchDialog *dialog);
+
+void pluma_search_dialog_set_replace_text (PlumaSearchDialog *dialog,
+ const gchar *text);
+const gchar *pluma_search_dialog_get_replace_text (PlumaSearchDialog *dialog);
+
+void pluma_search_dialog_set_match_case (PlumaSearchDialog *dialog,
+ gboolean match_case);
+gboolean pluma_search_dialog_get_match_case (PlumaSearchDialog *dialog);
+
+void pluma_search_dialog_set_entire_word (PlumaSearchDialog *dialog,
+ gboolean entire_word);
+gboolean pluma_search_dialog_get_entire_word (PlumaSearchDialog *dialog);
+
+void pluma_search_dialog_set_backwards (PlumaSearchDialog *dialog,
+ gboolean backwards);
+gboolean pluma_search_dialog_get_backwards (PlumaSearchDialog *dialog);
+
+void pluma_search_dialog_set_wrap_around (PlumaSearchDialog *dialog,
+ gboolean wrap_around);
+gboolean pluma_search_dialog_get_wrap_around (PlumaSearchDialog *dialog);
+
+G_END_DECLS
+
+#endif /* __PLUMA_SEARCH_DIALOG_H__ */
diff --git a/pluma/dialogs/pluma-search-dialog.ui b/pluma/dialogs/pluma-search-dialog.ui
new file mode 100755
index 00000000..35b6c390
--- /dev/null
+++ b/pluma/dialogs/pluma-search-dialog.ui
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="dialog">
+ <property name="title" translatable="yes">Replace</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="replace_all_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Replace All</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="replace_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Replace</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="find_next_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-find</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="search_dialog_content">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkTable" id="table">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="search_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Search for: </property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="replace_with_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Replace _with: </property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkCheckButton" id="match_case_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Match case</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="entire_word_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Match _entire word only</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="search_backwards_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Search _backwards</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="wrap_around_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Wrap around</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">close_button</action-widget>
+ <action-widget response="0">replace_all_button</action-widget>
+ <action-widget response="0">replace_button</action-widget>
+ <action-widget response="0">find_next_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/pluma/osx/Makefile.am b/pluma/osx/Makefile.am
new file mode 100755
index 00000000..994b9ff7
--- /dev/null
+++ b/pluma/osx/Makefile.am
@@ -0,0 +1,23 @@
+INCLUDES = \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/pluma \
+ -I$(top_builddir)/pluma \
+ $(PLUMA_CFLAGS) \
+ $(IGE_MAC_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+noinst_LTLIBRARIES = libosx.la
+
+libosx_la_LDFLAGS = -framework Carbon -framework ApplicationServices -framework Cocoa
+libosx_la_LIBADD = -lobjc
+libosx_la_CFLAGS = -xobjective-c
+
+libosx_la_SOURCES = \
+ pluma-osx.c \
+ pluma-osx.h \
+ pluma-osx-delegate.m \
+ pluma-osx-delegate.h
+
+-include $(top_srcdir)/git.mk
diff --git a/pluma/osx/pluma-osx-delegate.h b/pluma/osx/pluma-osx-delegate.h
new file mode 100755
index 00000000..f6eb2ae5
--- /dev/null
+++ b/pluma/osx/pluma-osx-delegate.h
@@ -0,0 +1,16 @@
+#ifndef PLUMA_OSX_DELEGATE_H_
+#define PLUMA_OSX_DELEGATE_H_
+
+#import <Foundation/NSAppleEventManager.h>
+
+@interface PlumaOSXDelegate : NSObject
+{
+}
+
+-(id) init;
+-(void) openFiles:(NSAppleEventDescriptor*)event
+ withReply:(NSAppleEventDescriptor*)reply;
+
+@end
+
+#endif /* PLUMA_OSX_DELEGATE_H_ */
diff --git a/pluma/osx/pluma-osx-delegate.m b/pluma/osx/pluma-osx-delegate.m
new file mode 100755
index 00000000..15ecf813
--- /dev/null
+++ b/pluma/osx/pluma-osx-delegate.m
@@ -0,0 +1,84 @@
+#import "pluma-osx-delegate.h"
+#import <Foundation/NSAppleEventManager.h>
+#import <Foundation/NSAppleEventDescriptor.h>
+#import <Foundation/NSData.h>
+#include <glib.h>
+#include <pluma/pluma-app.h>
+#include <pluma/pluma-commands.h>
+
+@implementation PlumaOSXDelegate
+-(id)init
+{
+ if ((self = [super init]))
+ {
+ NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
+
+ [em setEventHandler:self
+ andSelector:@selector(openFiles:withReply:)
+ forEventClass:kCoreEventClass
+ andEventID:kAEOpenDocuments];
+ }
+
+ return self;
+}
+
+static PlumaWindow *
+get_window(NSAppleEventDescriptor *event)
+{
+ PlumaApp *app = pluma_app_get_default ();
+ return pluma_app_get_active_window (app);
+}
+
+- (void)openFiles:(NSAppleEventDescriptor*)event
+ withReply:(NSAppleEventDescriptor*)reply
+{
+ NSAppleEventDescriptor *fileList = [event paramDescriptorForKeyword:keyDirectObject];
+ NSInteger i;
+ GSList *uris = NULL;
+
+ if (!fileList)
+ {
+ return;
+ }
+
+ for (i = 1; i <= [fileList numberOfItems]; ++i)
+ {
+ NSAppleEventDescriptor *fileAliasDesc = [fileList descriptorAtIndex:i];
+ NSAppleEventDescriptor *fileURLDesc;
+ NSData *fileURLData;
+ gchar *url;
+
+ if (!fileAliasDesc)
+ {
+ continue;
+ }
+
+ fileURLDesc = [fileAliasDesc coerceToDescriptorType:typeFileURL];
+
+ if (!fileURLDesc)
+ {
+ continue;
+ }
+
+ fileURLData = [fileURLDesc data];
+
+ if (!fileURLData)
+ {
+ continue;
+ }
+
+ url = g_strndup([fileURLData bytes], [fileURLData length]);
+ uris = g_slist_prepend (uris, url);
+ }
+
+ if (uris != NULL)
+ {
+ PlumaWindow *window = get_window (event);
+ pluma_commands_load_uris (window, uris, NULL, 0);
+
+ g_slist_foreach (uris, (GFunc)g_free, NULL);
+ g_slist_free (uris);
+ }
+}
+
+@end \ No newline at end of file
diff --git a/pluma/osx/pluma-osx.c b/pluma/osx/pluma-osx.c
new file mode 100755
index 00000000..b9e23c90
--- /dev/null
+++ b/pluma/osx/pluma-osx.c
@@ -0,0 +1,94 @@
+#include "pluma-osx.h"
+#include <gdk/gdkquartz.h>
+#include <Carbon/Carbon.h>
+
+#import "pluma-osx-delegate.h"
+
+void
+pluma_osx_set_window_title (PlumaWindow *window,
+ gchar const *title,
+ PlumaDocument *document)
+{
+ NSWindow *native;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ if (GTK_WIDGET (window)->window == NULL)
+ {
+ return;
+ }
+
+ native = gdk_quartz_window_get_nswindow (GTK_WIDGET (window)->window);
+
+ if (document)
+ {
+ bool ismodified;
+
+ if (pluma_document_is_untitled (document))
+ {
+ [native setRepresentedURL:nil];
+ }
+ else
+ {
+ const gchar *uri = pluma_document_get_uri (document);
+ NSURL *nsurl = [NSURL URLWithString:[NSString stringWithUTF8String:uri]];
+
+ [native setRepresentedURL:nsurl];
+ }
+
+ ismodified = !pluma_document_is_untouched (document);
+ [native setDocumentEdited:ismodified];
+ }
+ else
+ {
+ [native setRepresentedURL:nil];
+ [native setDocumentEdited:false];
+ }
+
+ gtk_window_set_title (GTK_WINDOW (window), title);
+}
+
+gboolean
+pluma_osx_show_url (const gchar *url)
+{
+ return [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]];
+}
+
+gboolean
+pluma_osx_show_help (const gchar *link_id)
+{
+ gchar *link;
+ gboolean ret;
+
+ if (link_id)
+ {
+ link = g_strdup_printf ("http://library.gnome.org/users/pluma/stable/%s",
+ link_id);
+ }
+ else
+ {
+ link = g_strdup ("http://library.gnome.org/users/pluma/stable/");
+ }
+
+ ret = pluma_osx_show_url (link);
+ g_free (link);
+
+ return ret;
+}
+
+static void
+destroy_delegate (PlumaOSXDelegate *delegate)
+{
+ [delegate dealloc];
+}
+
+void
+pluma_osx_init(PlumaApp *app)
+{
+ PlumaOSXDelegate *delegate = [[PlumaOSXDelegate alloc] init];
+
+ g_object_set_data_full (G_OBJECT (app),
+ "PlumaOSXDelegate",
+ delegate,
+ (GDestroyNotify)destroy_delegate);
+} \ No newline at end of file
diff --git a/pluma/osx/pluma-osx.h b/pluma/osx/pluma-osx.h
new file mode 100755
index 00000000..df06865f
--- /dev/null
+++ b/pluma/osx/pluma-osx.h
@@ -0,0 +1,17 @@
+#ifndef __PLUMA_OSX_H__
+#define __PLUMA_OSX_H__
+
+#include <gtk/gtk.h>
+#include <pluma/pluma-window.h>
+#include <pluma/pluma-app.h>
+
+void pluma_osx_init (PlumaApp *app);
+
+void pluma_osx_set_window_title (PlumaWindow *window,
+ gchar const *title,
+ PlumaDocument *document);
+
+gboolean pluma_osx_show_url (const gchar *url);
+gboolean pluma_osx_show_help (const gchar *link_id);
+
+#endif /* __PLUMA_OSX_H__ */
diff --git a/pluma/pluma-app.c b/pluma/pluma-app.c
new file mode 100755
index 00000000..090cb7a2
--- /dev/null
+++ b/pluma/pluma-app.c
@@ -0,0 +1,911 @@
+/*
+ * pluma-app.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005-2006 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+
+#include "pluma-app.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-commands.h"
+#include "pluma-notebook.h"
+#include "pluma-debug.h"
+#include "pluma-utils.h"
+#include "pluma-enum-types.h"
+#include "pluma-dirs.h"
+
+#ifdef OS_OSX
+#include <ige-mac-integration.h>
+#endif
+
+#define PLUMA_PAGE_SETUP_FILE "pluma-page-setup"
+#define PLUMA_PRINT_SETTINGS_FILE "pluma-print-settings"
+
+#define PLUMA_APP_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_APP, PlumaAppPrivate))
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_LOCKDOWN
+};
+
+struct _PlumaAppPrivate
+{
+ GList *windows;
+ PlumaWindow *active_window;
+
+ PlumaLockdownMask lockdown;
+
+ GtkPageSetup *page_setup;
+ GtkPrintSettings *print_settings;
+};
+
+G_DEFINE_TYPE(PlumaApp, pluma_app, G_TYPE_OBJECT)
+
+static void
+pluma_app_finalize (GObject *object)
+{
+ PlumaApp *app = PLUMA_APP (object);
+
+ g_list_free (app->priv->windows);
+
+ if (app->priv->page_setup)
+ g_object_unref (app->priv->page_setup);
+ if (app->priv->print_settings)
+ g_object_unref (app->priv->print_settings);
+
+ G_OBJECT_CLASS (pluma_app_parent_class)->finalize (object);
+}
+
+static void
+pluma_app_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaApp *app = PLUMA_APP (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCKDOWN:
+ g_value_set_flags (value, pluma_app_get_lockdown (app));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_app_class_init (PlumaAppClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_app_finalize;
+ object_class->get_property = pluma_app_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_LOCKDOWN,
+ g_param_spec_flags ("lockdown",
+ "Lockdown",
+ "The lockdown mask",
+ PLUMA_TYPE_LOCKDOWN_MASK,
+ 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof(PlumaAppPrivate));
+}
+
+static gboolean
+ensure_user_config_dir (void)
+{
+ gchar *config_dir;
+ gboolean ret = TRUE;
+ gint res;
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+ if (config_dir == NULL)
+ {
+ g_warning ("Could not get config directory\n");
+ return FALSE;
+ }
+
+ res = g_mkdir_with_parents (config_dir, 0755);
+ if (res < 0)
+ {
+ g_warning ("Could not create config directory\n");
+ ret = FALSE;
+ }
+
+ g_free (config_dir);
+
+ return ret;
+}
+
+static void
+load_accels (void)
+{
+ gchar *filename;
+
+ filename = pluma_dirs_get_user_accels_file ();
+ if (filename != NULL)
+ {
+ pluma_debug_message (DEBUG_APP, "Loading keybindings from %s\n", filename);
+ gtk_accel_map_load (filename);
+ g_free (filename);
+ }
+}
+
+static void
+save_accels (void)
+{
+ gchar *filename;
+
+ filename = pluma_dirs_get_user_accels_file ();
+ if (filename != NULL)
+ {
+ pluma_debug_message (DEBUG_APP, "Saving keybindings in %s\n", filename);
+ gtk_accel_map_save (filename);
+ g_free (filename);
+ }
+}
+
+static gchar *
+get_page_setup_file (void)
+{
+ gchar *config_dir;
+ gchar *setup = NULL;
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+
+ if (config_dir != NULL)
+ {
+ setup = g_build_filename (config_dir,
+ PLUMA_PAGE_SETUP_FILE,
+ NULL);
+ g_free (config_dir);
+ }
+
+ return setup;
+}
+
+static void
+load_page_setup (PlumaApp *app)
+{
+ gchar *filename;
+ GError *error = NULL;
+
+ g_return_if_fail (app->priv->page_setup == NULL);
+
+ filename = get_page_setup_file ();
+
+ app->priv->page_setup = gtk_page_setup_new_from_file (filename,
+ &error);
+ if (error)
+ {
+ /* Ignore file not found error */
+ if (error->domain != G_FILE_ERROR ||
+ error->code != G_FILE_ERROR_NOENT)
+ {
+ g_warning ("%s", error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ g_free (filename);
+
+ /* fall back to default settings */
+ if (app->priv->page_setup == NULL)
+ app->priv->page_setup = gtk_page_setup_new ();
+}
+
+static void
+save_page_setup (PlumaApp *app)
+{
+ gchar *filename;
+ GError *error = NULL;
+
+ if (app->priv->page_setup == NULL)
+ return;
+
+ filename = get_page_setup_file ();
+
+ gtk_page_setup_to_file (app->priv->page_setup,
+ filename,
+ &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (filename);
+}
+
+static gchar *
+get_print_settings_file (void)
+{
+ gchar *config_dir;
+ gchar *settings = NULL;
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+
+ if (config_dir != NULL)
+ {
+ settings = g_build_filename (config_dir,
+ PLUMA_PRINT_SETTINGS_FILE,
+ NULL);
+ g_free (config_dir);
+ }
+
+ return settings;
+}
+
+static void
+load_print_settings (PlumaApp *app)
+{
+ gchar *filename;
+ GError *error = NULL;
+
+ g_return_if_fail (app->priv->print_settings == NULL);
+
+ filename = get_print_settings_file ();
+
+ app->priv->print_settings = gtk_print_settings_new_from_file (filename,
+ &error);
+ if (error)
+ {
+ /* Ignore file not found error */
+ if (error->domain != G_FILE_ERROR ||
+ error->code != G_FILE_ERROR_NOENT)
+ {
+ g_warning ("%s", error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ g_free (filename);
+
+ /* fall back to default settings */
+ if (app->priv->print_settings == NULL)
+ app->priv->print_settings = gtk_print_settings_new ();
+}
+
+static void
+save_print_settings (PlumaApp *app)
+{
+ gchar *filename;
+ GError *error = NULL;
+
+ if (app->priv->print_settings == NULL)
+ return;
+
+ filename = get_print_settings_file ();
+
+ gtk_print_settings_to_file (app->priv->print_settings,
+ filename,
+ &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (filename);
+}
+
+static void
+pluma_app_init (PlumaApp *app)
+{
+ app->priv = PLUMA_APP_GET_PRIVATE (app);
+
+ load_accels ();
+
+ /* initial lockdown state */
+ app->priv->lockdown = pluma_prefs_manager_get_lockdown ();
+}
+
+static void
+app_weak_notify (gpointer data,
+ GObject *where_the_app_was)
+{
+ gtk_main_quit ();
+}
+
+/**
+ * pluma_app_get_default:
+ *
+ * Returns the #PlumaApp object. This object is a singleton and
+ * represents the running pluma instance.
+ *
+ * Return value: the #PlumaApp pointer
+ */
+PlumaApp *
+pluma_app_get_default (void)
+{
+ static PlumaApp *app = NULL;
+
+ if (app != NULL)
+ return app;
+
+ app = PLUMA_APP (g_object_new (PLUMA_TYPE_APP, NULL));
+
+ g_object_add_weak_pointer (G_OBJECT (app),
+ (gpointer) &app);
+ g_object_weak_ref (G_OBJECT (app),
+ app_weak_notify,
+ NULL);
+
+ return app;
+}
+
+static void
+set_active_window (PlumaApp *app,
+ PlumaWindow *window)
+{
+ app->priv->active_window = window;
+}
+
+static gboolean
+window_focus_in_event (PlumaWindow *window,
+ GdkEventFocus *event,
+ PlumaApp *app)
+{
+ /* updates active_view and active_child when a new toplevel receives focus */
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), FALSE);
+
+ set_active_window (app, window);
+
+ return FALSE;
+}
+
+static gboolean
+window_delete_event (PlumaWindow *window,
+ GdkEvent *event,
+ PlumaApp *app)
+{
+ PlumaWindowState ws;
+
+ ws = pluma_window_get_state (window);
+
+ if (ws &
+ (PLUMA_WINDOW_STATE_SAVING |
+ PLUMA_WINDOW_STATE_PRINTING |
+ PLUMA_WINDOW_STATE_SAVING_SESSION))
+ return TRUE;
+
+ _pluma_cmd_file_quit (NULL, window);
+
+ /* Do not destroy the window */
+ return TRUE;
+}
+
+static void
+window_destroy (PlumaWindow *window,
+ PlumaApp *app)
+{
+ app->priv->windows = g_list_remove (app->priv->windows,
+ window);
+
+ if (window == app->priv->active_window)
+ {
+ set_active_window (app, app->priv->windows != NULL ? app->priv->windows->data : NULL);
+ }
+
+/* CHECK: I don't think we have to disconnect this function, since windows
+ is being destroyed */
+/*
+ g_signal_handlers_disconnect_by_func (window,
+ G_CALLBACK (window_focus_in_event),
+ app);
+ g_signal_handlers_disconnect_by_func (window,
+ G_CALLBACK (window_destroy),
+ app);
+*/
+ if (app->priv->windows == NULL)
+ {
+#ifdef OS_OSX
+ if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "pluma-is-quitting-all")))
+ {
+ /* Create hidden proxy window on OS X to handle the menu */
+ pluma_app_create_window (app, NULL);
+ return;
+ }
+#endif
+ /* Last window is gone... save some settings and exit */
+ ensure_user_config_dir ();
+
+ save_accels ();
+ save_page_setup (app);
+ save_print_settings (app);
+
+ g_object_unref (app);
+ }
+}
+
+/* Generates a unique string for a window role */
+static gchar *
+gen_role (void)
+{
+ GTimeVal result;
+ static gint serial;
+
+ g_get_current_time (&result);
+
+ return g_strdup_printf ("pluma-window-%ld-%ld-%d-%s",
+ result.tv_sec,
+ result.tv_usec,
+ serial++,
+ g_get_host_name ());
+}
+
+static PlumaWindow *
+pluma_app_create_window_real (PlumaApp *app,
+ gboolean set_geometry,
+ const gchar *role)
+{
+ PlumaWindow *window;
+
+ pluma_debug (DEBUG_APP);
+
+ /*
+ * We need to be careful here, there is a race condition:
+ * when another pluma is launched it checks active_window,
+ * so we must do our best to ensure that active_window
+ * is never NULL when at least a window exists.
+ */
+ if (app->priv->windows == NULL)
+ {
+ window = g_object_new (PLUMA_TYPE_WINDOW, NULL);
+ set_active_window (app, window);
+ }
+ else
+ {
+ window = g_object_new (PLUMA_TYPE_WINDOW, NULL);
+ }
+
+ app->priv->windows = g_list_prepend (app->priv->windows,
+ window);
+
+ pluma_debug_message (DEBUG_APP, "Window created");
+
+ if (role != NULL)
+ {
+ gtk_window_set_role (GTK_WINDOW (window), role);
+ }
+ else
+ {
+ gchar *newrole;
+
+ newrole = gen_role ();
+ gtk_window_set_role (GTK_WINDOW (window), newrole);
+ g_free (newrole);
+ }
+
+ if (set_geometry)
+ {
+ GdkWindowState state;
+ gint w, h;
+
+ state = pluma_prefs_manager_get_window_state ();
+
+ if ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0)
+ {
+ pluma_prefs_manager_get_default_window_size (&w, &h);
+ gtk_window_set_default_size (GTK_WINDOW (window), w, h);
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ else
+ {
+ pluma_prefs_manager_get_window_size (&w, &h);
+ gtk_window_set_default_size (GTK_WINDOW (window), w, h);
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ }
+
+ if ((state & GDK_WINDOW_STATE_STICKY ) != 0)
+ gtk_window_stick (GTK_WINDOW (window));
+ else
+ gtk_window_unstick (GTK_WINDOW (window));
+ }
+
+ g_signal_connect (window,
+ "focus_in_event",
+ G_CALLBACK (window_focus_in_event),
+ app);
+ g_signal_connect (window,
+ "delete_event",
+ G_CALLBACK (window_delete_event),
+ app);
+ g_signal_connect (window,
+ "destroy",
+ G_CALLBACK (window_destroy),
+ app);
+
+ return window;
+}
+
+/**
+ * pluma_app_create_window:
+ * @app: the #PlumaApp
+ *
+ * Create a new #PlumaWindow part of @app.
+ *
+ * Return value: the new #PlumaWindow
+ */
+PlumaWindow *
+pluma_app_create_window (PlumaApp *app,
+ GdkScreen *screen)
+{
+ PlumaWindow *window;
+
+ window = pluma_app_create_window_real (app, TRUE, NULL);
+
+ if (screen != NULL)
+ gtk_window_set_screen (GTK_WINDOW (window), screen);
+
+ return window;
+}
+
+/*
+ * Same as _create_window, but doesn't set the geometry.
+ * The session manager takes care of it. Used in mate-session.
+ */
+PlumaWindow *
+_pluma_app_restore_window (PlumaApp *app,
+ const gchar *role)
+{
+ PlumaWindow *window;
+
+ window = pluma_app_create_window_real (app, FALSE, role);
+
+ return window;
+}
+
+/**
+ * pluma_app_get_windows:
+ * @app: the #PlumaApp
+ *
+ * Returns all the windows currently present in #PlumaApp.
+ *
+ * Return value: the list of #PlumaWindows objects. The list
+ * should not be freed
+ */
+const GList *
+pluma_app_get_windows (PlumaApp *app)
+{
+ g_return_val_if_fail (PLUMA_IS_APP (app), NULL);
+
+ return app->priv->windows;
+}
+
+/**
+ * pluma_app_get_active_window:
+ * @app: the #PlumaApp
+ *
+ * Retrives the #PlumaWindow currently active.
+ *
+ * Return value: the active #PlumaWindow
+ */
+PlumaWindow *
+pluma_app_get_active_window (PlumaApp *app)
+{
+ g_return_val_if_fail (PLUMA_IS_APP (app), NULL);
+
+ /* make sure our active window is always realized:
+ * this is needed on startup if we launch two pluma fast
+ * enough that the second instance comes up before the
+ * first one shows its window.
+ */
+ if (!GTK_WIDGET_REALIZED (GTK_WIDGET (app->priv->active_window)))
+ gtk_widget_realize (GTK_WIDGET (app->priv->active_window));
+
+ return app->priv->active_window;
+}
+
+static gboolean
+is_in_viewport (PlumaWindow *window,
+ GdkScreen *screen,
+ gint workspace,
+ gint viewport_x,
+ gint viewport_y)
+{
+ GdkScreen *s;
+ GdkDisplay *display;
+ GdkWindow *gdkwindow;
+ const gchar *cur_name;
+ const gchar *name;
+ gint cur_n;
+ gint n;
+ gint ws;
+ gint sc_width, sc_height;
+ gint x, y, width, height;
+ gint vp_x, vp_y;
+
+ /* Check for screen and display match */
+ display = gdk_screen_get_display (screen);
+ cur_name = gdk_display_get_name (display);
+ cur_n = gdk_screen_get_number (screen);
+
+ s = gtk_window_get_screen (GTK_WINDOW (window));
+ display = gdk_screen_get_display (s);
+ name = gdk_display_get_name (display);
+ n = gdk_screen_get_number (s);
+
+ if (strcmp (cur_name, name) != 0 || cur_n != n)
+ return FALSE;
+
+ /* Check for workspace match */
+ ws = pluma_utils_get_window_workspace (GTK_WINDOW (window));
+ if (ws != workspace && ws != PLUMA_ALL_WORKSPACES)
+ return FALSE;
+
+ /* Check for viewport match */
+ gdkwindow = gtk_widget_get_window (GTK_WIDGET (window));
+ gdk_window_get_position (gdkwindow, &x, &y);
+
+ #if GTK_CHECK_VERSION(3, 0, 0)
+ width = gdk_window_get_width(gdkwindow);
+ height = gdk_window_get_height(gdkwindow);
+ #else
+ gdk_drawable_get_size(gdkwindow, &width, &height);
+ #endif
+
+ pluma_utils_get_current_viewport (screen, &vp_x, &vp_y);
+ x += vp_x;
+ y += vp_y;
+
+ sc_width = gdk_screen_get_width (screen);
+ sc_height = gdk_screen_get_height (screen);
+
+ return x + width * .25 >= viewport_x &&
+ x + width * .75 <= viewport_x + sc_width &&
+ y >= viewport_y &&
+ y + height <= viewport_y + sc_height;
+}
+
+/**
+ * _pluma_app_get_window_in_viewport
+ * @app: the #PlumaApp
+ * @screen: the #GdkScreen
+ * @workspace: the workspace number
+ * @viewport_x: the viewport horizontal origin
+ * @viewport_y: the viewport vertical origin
+ *
+ * Since a workspace can be larger than the screen, it is divided into several
+ * equal parts called viewports. This function retrives the #PlumaWindow in
+ * the given viewport of the given workspace.
+ *
+ * Return value: the #PlumaWindow in the given viewport of the given workspace.
+ */
+PlumaWindow *
+_pluma_app_get_window_in_viewport (PlumaApp *app,
+ GdkScreen *screen,
+ gint workspace,
+ gint viewport_x,
+ gint viewport_y)
+{
+ PlumaWindow *window;
+
+ GList *l;
+
+ g_return_val_if_fail (PLUMA_IS_APP (app), NULL);
+
+ /* first try if the active window */
+ window = app->priv->active_window;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ if (is_in_viewport (window, screen, workspace, viewport_x, viewport_y))
+ return window;
+
+ /* otherwise try to see if there is a window on this workspace */
+ for (l = app->priv->windows; l != NULL; l = l->next)
+ {
+ window = l->data;
+
+ if (is_in_viewport (window, screen, workspace, viewport_x, viewport_y))
+ return window;
+ }
+
+ /* no window on this workspace... create a new one */
+ return pluma_app_create_window (app, screen);
+}
+
+/**
+ * pluma_app_get_documents:
+ * @app: the #PlumaApp
+ *
+ * Returns all the documents currently open in #PlumaApp.
+ *
+ * Return value: a newly allocated list of #PlumaDocument objects
+ */
+GList *
+pluma_app_get_documents (PlumaApp *app)
+{
+ GList *res = NULL;
+ GList *windows;
+
+ g_return_val_if_fail (PLUMA_IS_APP (app), NULL);
+
+ windows = app->priv->windows;
+
+ while (windows != NULL)
+ {
+ res = g_list_concat (res,
+ pluma_window_get_documents (PLUMA_WINDOW (windows->data)));
+
+ windows = g_list_next (windows);
+ }
+
+ return res;
+}
+
+/**
+ * pluma_app_get_views:
+ * @app: the #PlumaApp
+ *
+ * Returns all the views currently present in #PlumaApp.
+ *
+ * Return value: a newly allocated list of #PlumaView objects
+ */
+GList *
+pluma_app_get_views (PlumaApp *app)
+{
+ GList *res = NULL;
+ GList *windows;
+
+ g_return_val_if_fail (PLUMA_IS_APP (app), NULL);
+
+ windows = app->priv->windows;
+
+ while (windows != NULL)
+ {
+ res = g_list_concat (res,
+ pluma_window_get_views (PLUMA_WINDOW (windows->data)));
+
+ windows = g_list_next (windows);
+ }
+
+ return res;
+}
+
+/**
+ * pluma_app_get_lockdown:
+ * @app: a #PlumaApp
+ *
+ * Gets the lockdown mask (see #PlumaLockdownMask) for the application.
+ * The lockdown mask determines which functions are locked down using
+ * the MATE-wise lockdown MateConf keys.
+ **/
+PlumaLockdownMask
+pluma_app_get_lockdown (PlumaApp *app)
+{
+ g_return_val_if_fail (PLUMA_IS_APP (app), PLUMA_LOCKDOWN_ALL);
+
+ return app->priv->lockdown;
+}
+
+static void
+app_lockdown_changed (PlumaApp *app)
+{
+ GList *l;
+
+ for (l = app->priv->windows; l != NULL; l = l->next)
+ _pluma_window_set_lockdown (PLUMA_WINDOW (l->data),
+ app->priv->lockdown);
+
+ g_object_notify (G_OBJECT (app), "lockdown");
+}
+
+void
+_pluma_app_set_lockdown (PlumaApp *app,
+ PlumaLockdownMask lockdown)
+{
+ g_return_if_fail (PLUMA_IS_APP (app));
+
+ app->priv->lockdown = lockdown;
+
+ app_lockdown_changed (app);
+}
+
+void
+_pluma_app_set_lockdown_bit (PlumaApp *app,
+ PlumaLockdownMask bit,
+ gboolean value)
+{
+ g_return_if_fail (PLUMA_IS_APP (app));
+
+ if (value)
+ app->priv->lockdown |= bit;
+ else
+ app->priv->lockdown &= ~bit;
+
+ app_lockdown_changed (app);
+}
+
+/* Returns a copy */
+GtkPageSetup *
+_pluma_app_get_default_page_setup (PlumaApp *app)
+{
+ g_return_val_if_fail (PLUMA_IS_APP (app), NULL);
+
+ if (app->priv->page_setup == NULL)
+ load_page_setup (app);
+
+ return gtk_page_setup_copy (app->priv->page_setup);
+}
+
+void
+_pluma_app_set_default_page_setup (PlumaApp *app,
+ GtkPageSetup *page_setup)
+{
+ g_return_if_fail (PLUMA_IS_APP (app));
+ g_return_if_fail (GTK_IS_PAGE_SETUP (page_setup));
+
+ if (app->priv->page_setup != NULL)
+ g_object_unref (app->priv->page_setup);
+
+ app->priv->page_setup = g_object_ref (page_setup);
+}
+
+/* Returns a copy */
+GtkPrintSettings *
+_pluma_app_get_default_print_settings (PlumaApp *app)
+{
+ g_return_val_if_fail (PLUMA_IS_APP (app), NULL);
+
+ if (app->priv->print_settings == NULL)
+ load_print_settings (app);
+
+ return gtk_print_settings_copy (app->priv->print_settings);
+}
+
+void
+_pluma_app_set_default_print_settings (PlumaApp *app,
+ GtkPrintSettings *settings)
+{
+ g_return_if_fail (PLUMA_IS_APP (app));
+ g_return_if_fail (GTK_IS_PRINT_SETTINGS (settings));
+
+ if (app->priv->print_settings != NULL)
+ g_object_unref (app->priv->print_settings);
+
+ app->priv->print_settings = g_object_ref (settings);
+}
+
diff --git a/pluma/pluma-app.h b/pluma/pluma-app.h
new file mode 100755
index 00000000..ad5b6803
--- /dev/null
+++ b/pluma/pluma-app.h
@@ -0,0 +1,142 @@
+/*
+ * pluma-app.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_APP_H__
+#define __PLUMA_APP_H__
+
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-window.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_APP (pluma_app_get_type())
+#define PLUMA_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_APP, PlumaApp))
+#define PLUMA_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_APP, PlumaAppClass))
+#define PLUMA_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_APP))
+#define PLUMA_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_APP))
+#define PLUMA_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_APP, PlumaAppClass))
+
+/* Private structure type */
+typedef struct _PlumaAppPrivate PlumaAppPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaApp PlumaApp;
+
+struct _PlumaApp
+{
+ GObject object;
+
+ /*< private > */
+ PlumaAppPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaAppClass PlumaAppClass;
+
+struct _PlumaAppClass
+{
+ GObjectClass parent_class;
+};
+
+/*
+ * Lockdown mask definition
+ */
+typedef enum
+{
+ PLUMA_LOCKDOWN_COMMAND_LINE = 1 << 0,
+ PLUMA_LOCKDOWN_PRINTING = 1 << 1,
+ PLUMA_LOCKDOWN_PRINT_SETUP = 1 << 2,
+ PLUMA_LOCKDOWN_SAVE_TO_DISK = 1 << 3,
+ PLUMA_LOCKDOWN_ALL = 0xF
+} PlumaLockdownMask;
+
+/*
+ * Public methods
+ */
+GType pluma_app_get_type (void) G_GNUC_CONST;
+
+PlumaApp *pluma_app_get_default (void);
+
+PlumaWindow *pluma_app_create_window (PlumaApp *app,
+ GdkScreen *screen);
+
+const GList *pluma_app_get_windows (PlumaApp *app);
+PlumaWindow *pluma_app_get_active_window (PlumaApp *app);
+
+/* Returns a newly allocated list with all the documents */
+GList *pluma_app_get_documents (PlumaApp *app);
+
+/* Returns a newly allocated list with all the views */
+GList *pluma_app_get_views (PlumaApp *app);
+
+/* Lockdown state */
+PlumaLockdownMask pluma_app_get_lockdown (PlumaApp *app);
+
+/*
+ * Non exported functions
+ */
+PlumaWindow *_pluma_app_restore_window (PlumaApp *app,
+ const gchar *role);
+PlumaWindow *_pluma_app_get_window_in_viewport (PlumaApp *app,
+ GdkScreen *screen,
+ gint workspace,
+ gint viewport_x,
+ gint viewport_y);
+void _pluma_app_set_lockdown (PlumaApp *app,
+ PlumaLockdownMask lockdown);
+void _pluma_app_set_lockdown_bit (PlumaApp *app,
+ PlumaLockdownMask bit,
+ gboolean value);
+/*
+ * This one is a pluma-window function, but we declare it here to avoid
+ * #include headaches since it needs the PlumaLockdownMask declaration.
+ */
+void _pluma_window_set_lockdown (PlumaWindow *window,
+ PlumaLockdownMask lockdown);
+
+/* global print config */
+GtkPageSetup *_pluma_app_get_default_page_setup (PlumaApp *app);
+void _pluma_app_set_default_page_setup (PlumaApp *app,
+ GtkPageSetup *page_setup);
+GtkPrintSettings *_pluma_app_get_default_print_settings (PlumaApp *app);
+void _pluma_app_set_default_print_settings (PlumaApp *app,
+ GtkPrintSettings *settings);
+
+G_END_DECLS
+
+#endif /* __PLUMA_APP_H__ */
diff --git a/pluma/pluma-close-button.c b/pluma/pluma-close-button.c
new file mode 100755
index 00000000..0213d4d1
--- /dev/null
+++ b/pluma/pluma-close-button.c
@@ -0,0 +1,80 @@
+/*
+ * pluma-close-button.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Paolo Borelli
+ *
+ * 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.
+ */
+
+#include "pluma-close-button.h"
+
+G_DEFINE_TYPE (PlumaCloseButton, pluma_close_button, GTK_TYPE_BUTTON)
+
+static void
+pluma_close_button_style_set (GtkWidget *button,
+ GtkStyle *previous_style)
+{
+ gint h, w;
+
+ gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
+ GTK_ICON_SIZE_MENU, &w, &h);
+
+ gtk_widget_set_size_request (button, w + 2, h + 2);
+
+ GTK_WIDGET_CLASS (pluma_close_button_parent_class)->style_set (button, previous_style);
+}
+
+static void
+pluma_close_button_class_init (PlumaCloseButtonClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->style_set = pluma_close_button_style_set;
+}
+
+static void
+pluma_close_button_init (PlumaCloseButton *button)
+{
+ GtkRcStyle *rcstyle;
+ GtkWidget *image;
+
+ /* make it as small as possible */
+ rcstyle = gtk_rc_style_new ();
+ rcstyle->xthickness = rcstyle->ythickness = 0;
+ gtk_widget_modify_style (GTK_WIDGET (button), rcstyle);
+ g_object_unref (rcstyle);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+
+ gtk_container_add (GTK_CONTAINER (button), image);
+}
+
+GtkWidget *
+pluma_close_button_new ()
+{
+ PlumaCloseButton *button;
+
+ button = g_object_new (PLUMA_TYPE_CLOSE_BUTTON,
+ "relief", GTK_RELIEF_NONE,
+ "focus-on-click", FALSE,
+ NULL);
+
+ return GTK_WIDGET (button);
+}
+
diff --git a/pluma/pluma-close-button.h b/pluma/pluma-close-button.h
new file mode 100755
index 00000000..27996d74
--- /dev/null
+++ b/pluma/pluma-close-button.h
@@ -0,0 +1,56 @@
+/*
+ * pluma-close-button.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Paolo Borelli
+ *
+ * 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 __PLUMA_CLOSE_BUTTON_H__
+#define __PLUMA_CLOSE_BUTTON_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_CLOSE_BUTTON (pluma_close_button_get_type ())
+#define PLUMA_CLOSE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_CLOSE_BUTTON, PlumaCloseButton))
+#define PLUMA_CLOSE_BUTTON_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_CLOSE_BUTTON, PlumaCloseButton const))
+#define PLUMA_CLOSE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_CLOSE_BUTTON, PlumaCloseButtonClass))
+#define PLUMA_IS_CLOSE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_CLOSE_BUTTON))
+#define PLUMA_IS_CLOSE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_CLOSE_BUTTON))
+#define PLUMA_CLOSE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_CLOSE_BUTTON, PlumaCloseButtonClass))
+
+typedef struct _PlumaCloseButton PlumaCloseButton;
+typedef struct _PlumaCloseButtonClass PlumaCloseButtonClass;
+typedef struct _PlumaCloseButtonPrivate PlumaCloseButtonPrivate;
+
+struct _PlumaCloseButton {
+ GtkButton parent;
+};
+
+struct _PlumaCloseButtonClass {
+ GtkButtonClass parent_class;
+};
+
+GType pluma_close_button_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_close_button_new (void);
+
+G_END_DECLS
+
+#endif /* __PLUMA_CLOSE_BUTTON_H__ */
diff --git a/pluma/pluma-commands-documents.c b/pluma/pluma-commands-documents.c
new file mode 100755
index 00000000..62f8b9aa
--- /dev/null
+++ b/pluma/pluma-commands-documents.c
@@ -0,0 +1,87 @@
+/*
+ * pluma-documents-commands.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "pluma-commands.h"
+#include "pluma-window.h"
+#include "pluma-notebook.h"
+#include "pluma-debug.h"
+
+void
+_pluma_cmd_documents_previous_document (GtkAction *action,
+ PlumaWindow *window)
+{
+ GtkNotebook *notebook;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ notebook = GTK_NOTEBOOK (_pluma_window_get_notebook (window));
+ gtk_notebook_prev_page (notebook);
+}
+
+void
+_pluma_cmd_documents_next_document (GtkAction *action,
+ PlumaWindow *window)
+{
+ GtkNotebook *notebook;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ notebook = GTK_NOTEBOOK (_pluma_window_get_notebook (window));
+ gtk_notebook_next_page (notebook);
+}
+
+void
+_pluma_cmd_documents_move_to_new_window (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaNotebook *old_notebook;
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_window_get_active_tab (window);
+
+ if (tab == NULL)
+ return;
+
+ old_notebook = PLUMA_NOTEBOOK (_pluma_window_get_notebook (window));
+
+ g_return_if_fail (gtk_notebook_get_n_pages (GTK_NOTEBOOK (old_notebook)) > 1);
+
+ _pluma_window_move_tab_to_new_window (window, tab);
+}
diff --git a/pluma/pluma-commands-edit.c b/pluma/pluma-commands-edit.c
new file mode 100755
index 00000000..94189f39
--- /dev/null
+++ b/pluma/pluma-commands-edit.c
@@ -0,0 +1,174 @@
+/*
+ * pluma-commands-edit.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "pluma-commands.h"
+#include "pluma-window.h"
+#include "pluma-debug.h"
+#include "pluma-view.h"
+#include "dialogs/pluma-preferences-dialog.h"
+
+void
+_pluma_cmd_edit_undo (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+ GtkSourceBuffer *active_document;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ active_document = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view)));
+
+ gtk_source_buffer_undo (active_document);
+
+ pluma_view_scroll_to_cursor (active_view);
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_pluma_cmd_edit_redo (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+ GtkSourceBuffer *active_document;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ active_document = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view)));
+
+ gtk_source_buffer_redo (active_document);
+
+ pluma_view_scroll_to_cursor (active_view);
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_pluma_cmd_edit_cut (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ pluma_view_cut_clipboard (active_view);
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_pluma_cmd_edit_copy (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ pluma_view_copy_clipboard (active_view);
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_pluma_cmd_edit_paste (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ pluma_view_paste_clipboard (active_view);
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_pluma_cmd_edit_delete (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ pluma_view_delete_selection (active_view);
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_pluma_cmd_edit_select_all (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view);
+
+ pluma_view_select_all (active_view);
+
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+void
+_pluma_cmd_edit_preferences (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ pluma_show_preferences_dialog (window);
+}
diff --git a/pluma/pluma-commands-file-print.c b/pluma/pluma-commands-file-print.c
new file mode 100755
index 00000000..4a976ec9
--- /dev/null
+++ b/pluma/pluma-commands-file-print.c
@@ -0,0 +1,91 @@
+/*
+ * pluma-commands-file-print.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "pluma-commands.h"
+#include "pluma-window.h"
+#include "pluma-tab.h"
+#include "pluma-debug.h"
+
+#if !GTK_CHECK_VERSION (2, 17, 4)
+void
+_pluma_cmd_file_page_setup (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_window_get_active_tab (window);
+ if (tab == NULL)
+ return;
+
+ _pluma_tab_page_setup (tab);
+}
+#endif
+
+void
+_pluma_cmd_file_print_preview (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_window_get_active_tab (window);
+ if (tab == NULL)
+ return;
+
+ _pluma_tab_print_preview (tab);
+}
+
+void
+_pluma_cmd_file_print (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_window_get_active_tab (window);
+ if (tab == NULL)
+ return;
+
+ _pluma_tab_print (tab);
+}
+
diff --git a/pluma/pluma-commands-file.c b/pluma/pluma-commands-file.c
new file mode 100755
index 00000000..3e32a495
--- /dev/null
+++ b/pluma/pluma-commands-file.c
@@ -0,0 +1,1885 @@
+/*
+ * pluma-commands-file.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h> /* For strlen and strcmp */
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "pluma-commands.h"
+#include "pluma-window.h"
+#include "pluma-window-private.h"
+#include "pluma-statusbar.h"
+#include "pluma-debug.h"
+#include "pluma-utils.h"
+#include "pluma-file-chooser-dialog.h"
+#include "dialogs/pluma-close-confirmation-dialog.h"
+
+
+/* Defined constants */
+#define PLUMA_OPEN_DIALOG_KEY "pluma-open-dialog-key"
+#define PLUMA_TAB_TO_SAVE_AS "pluma-tab-to-save-as"
+#define PLUMA_LIST_OF_TABS_TO_SAVE_AS "pluma-list-of-tabs-to-save-as"
+#define PLUMA_IS_CLOSING_ALL "pluma-is-closing-all"
+#define PLUMA_IS_QUITTING "pluma-is-quitting"
+#define PLUMA_IS_CLOSING_TAB "pluma-is-closing-tab"
+#define PLUMA_IS_QUITTING_ALL "pluma-is-quitting-all"
+
+static void tab_state_changed_while_saving (PlumaTab *tab,
+ GParamSpec *pspec,
+ PlumaWindow *window);
+
+void
+_pluma_cmd_file_new (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ pluma_window_create_tab (window, TRUE);
+}
+
+static PlumaTab *
+get_tab_from_file (GList *docs, GFile *file)
+{
+ PlumaTab *tab = NULL;
+
+ while (docs != NULL)
+ {
+ PlumaDocument *d;
+ GFile *l;
+
+ d = PLUMA_DOCUMENT (docs->data);
+
+ l = pluma_document_get_location (d);
+ if (l != NULL)
+ {
+ if (g_file_equal (l, file))
+ {
+ tab = pluma_tab_get_from_document (d);
+ g_object_unref (l);
+ break;
+ }
+
+ g_object_unref (l);
+ }
+
+ docs = g_list_next (docs);
+ }
+
+ return tab;
+}
+
+static gboolean
+is_duplicated_file (GSList *files, GFile *file)
+{
+ while (files != NULL)
+ {
+ if (g_file_equal (files->data, file))
+ return TRUE;
+
+ files = g_slist_next (files);
+ }
+
+ return FALSE;
+}
+
+/* File loading */
+static gint
+load_file_list (PlumaWindow *window,
+ GSList *files,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create)
+{
+ PlumaTab *tab;
+ gint loaded_files = 0; /* Number of files to load */
+ gboolean jump_to = TRUE; /* Whether to jump to the new tab */
+ GList *win_docs;
+ GSList *files_to_load = NULL;
+ GSList *l;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ win_docs = pluma_window_get_documents (window);
+
+ /* Remove the uris corresponding to documents already open
+ * in "window" and remove duplicates from "uris" list */
+ for (l = files; l != NULL; l = l->next)
+ {
+ if (!is_duplicated_file (files_to_load, l->data))
+ {
+ tab = get_tab_from_file (win_docs, l->data);
+ if (tab != NULL)
+ {
+ if (l == files)
+ {
+ pluma_window_set_active_tab (window, tab);
+ jump_to = FALSE;
+
+ if (line_pos > 0)
+ {
+ PlumaDocument *doc;
+ PlumaView *view;
+
+ doc = pluma_tab_get_document (tab);
+ view = pluma_tab_get_view (tab);
+
+ /* document counts lines starting from 0 */
+ pluma_document_goto_line (doc, line_pos - 1);
+ pluma_view_scroll_to_cursor (view);
+ }
+ }
+
+ ++loaded_files;
+ }
+ else
+ {
+ files_to_load = g_slist_prepend (files_to_load,
+ l->data);
+ }
+ }
+ }
+
+ g_list_free (win_docs);
+
+ if (files_to_load == NULL)
+ return loaded_files;
+
+ files_to_load = g_slist_reverse (files_to_load);
+ l = files_to_load;
+
+ tab = pluma_window_get_active_tab (window);
+ if (tab != NULL)
+ {
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+
+ if (pluma_document_is_untouched (doc) &&
+ (pluma_tab_get_state (tab) == PLUMA_TAB_STATE_NORMAL))
+ {
+ gchar *uri;
+
+ // FIXME: pass the GFile to tab when api is there
+ uri = g_file_get_uri (l->data);
+ _pluma_tab_load (tab,
+ uri,
+ encoding,
+ line_pos,
+ create);
+ g_free (uri);
+
+ l = g_slist_next (l);
+ jump_to = FALSE;
+
+ ++loaded_files;
+ }
+ }
+
+ while (l != NULL)
+ {
+ gchar *uri;
+
+ g_return_val_if_fail (l->data != NULL, 0);
+
+ // FIXME: pass the GFile to tab when api is there
+ uri = g_file_get_uri (l->data);
+ tab = pluma_window_create_tab_from_uri (window,
+ uri,
+ encoding,
+ line_pos,
+ create,
+ jump_to);
+ g_free (uri);
+
+ if (tab != NULL)
+ {
+ jump_to = FALSE;
+ ++loaded_files;
+ }
+
+ l = g_slist_next (l);
+ }
+
+ if (loaded_files == 1)
+ {
+ PlumaDocument *doc;
+ gchar *uri_for_display;
+
+ g_return_val_if_fail (tab != NULL, loaded_files);
+
+ doc = pluma_tab_get_document (tab);
+ uri_for_display = pluma_document_get_uri_for_display (doc);
+
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ _("Loading file '%s'\342\200\246"),
+ uri_for_display);
+
+ g_free (uri_for_display);
+ }
+ else
+ {
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ ngettext("Loading %d file\342\200\246",
+ "Loading %d files\342\200\246",
+ loaded_files),
+ loaded_files);
+ }
+
+ /* Free uris_to_load. Note that l points to the first element of uris_to_load */
+ g_slist_free (files_to_load);
+
+ return loaded_files;
+}
+
+
+// FIXME: we should expose API with GFile and just make the uri
+// variants backward compat wrappers
+
+static gint
+load_uri_list (PlumaWindow *window,
+ const GSList *uris,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create)
+{
+ GSList *files = NULL;
+ const GSList *u;
+ gint ret;
+
+ for (u = uris; u != NULL; u = u->next)
+ {
+ gchar *uri = u->data;
+
+ if (pluma_utils_is_valid_uri (uri))
+ files = g_slist_prepend (files, g_file_new_for_uri (uri));
+ else
+ g_warning ("invalid uri: %s", uri);
+ }
+ files = g_slist_reverse (files);
+
+ ret = load_file_list (window, files, encoding, line_pos, create);
+
+ g_slist_foreach (files, (GFunc) g_object_unref, NULL);
+ g_slist_free (files);
+
+ return ret;
+}
+
+/**
+ * pluma_commands_load_uri:
+ *
+ * Do nothing if URI does not exist
+ */
+void
+pluma_commands_load_uri (PlumaWindow *window,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos)
+{
+ GSList *uris = NULL;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+ g_return_if_fail (uri != NULL);
+ g_return_if_fail (pluma_utils_is_valid_uri (uri));
+
+ pluma_debug_message (DEBUG_COMMANDS, "Loading URI '%s'", uri);
+
+ uris = g_slist_prepend (uris, (gchar *)uri);
+
+ load_uri_list (window, uris, encoding, line_pos, FALSE);
+
+ g_slist_free (uris);
+}
+
+/**
+ * pluma_commands_load_uris:
+ *
+ * Ignore non-existing URIs
+ */
+gint
+pluma_commands_load_uris (PlumaWindow *window,
+ const GSList *uris,
+ const PlumaEncoding *encoding,
+ gint line_pos)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), 0);
+ g_return_val_if_fail ((uris != NULL) && (uris->data != NULL), 0);
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ return load_uri_list (window, uris, encoding, line_pos, FALSE);
+}
+
+/*
+ * This should become public once we convert all api to GFile:
+ */
+static gint
+pluma_commands_load_files (PlumaWindow *window,
+ GSList *files,
+ const PlumaEncoding *encoding,
+ gint line_pos)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), 0);
+ g_return_val_if_fail ((files != NULL) && (files->data != NULL), 0);
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ return load_file_list (window, files, encoding, line_pos, FALSE);
+}
+
+/*
+ * From the command line we can specify a line position for the
+ * first doc. Beside specifying a not existing uri creates a
+ * titled document.
+ */
+gint
+_pluma_cmd_load_files_from_prompt (PlumaWindow *window,
+ GSList *files,
+ const PlumaEncoding *encoding,
+ gint line_pos)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ return load_file_list (window, files, encoding, line_pos, TRUE);
+}
+
+static void
+open_dialog_destroyed (PlumaWindow *window,
+ PlumaFileChooserDialog *dialog)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_OPEN_DIALOG_KEY,
+ NULL);
+}
+
+static void
+open_dialog_response_cb (PlumaFileChooserDialog *dialog,
+ gint response_id,
+ PlumaWindow *window)
+{
+ GSList *files;
+ const PlumaEncoding *encoding;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ return;
+ }
+
+ files = gtk_file_chooser_get_files (GTK_FILE_CHOOSER (dialog));
+ g_return_if_fail (files != NULL);
+
+ encoding = pluma_file_chooser_dialog_get_encoding (dialog);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ /* Remember the folder we navigated to */
+ _pluma_window_set_default_location (window, files->data);
+
+ pluma_commands_load_files (window,
+ files,
+ encoding,
+ 0);
+
+ g_slist_foreach (files, (GFunc) g_object_unref, NULL);
+ g_slist_free (files);
+}
+
+void
+_pluma_cmd_file_open (GtkAction *action,
+ PlumaWindow *window)
+{
+ GtkWidget *open_dialog;
+ gpointer data;
+ PlumaDocument *doc;
+ GFile *default_path = NULL;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ data = g_object_get_data (G_OBJECT (window), PLUMA_OPEN_DIALOG_KEY);
+
+ if (data != NULL)
+ {
+ g_return_if_fail (PLUMA_IS_FILE_CHOOSER_DIALOG (data));
+
+ gtk_window_present (GTK_WINDOW (data));
+
+ return;
+ }
+
+ /* Translators: "Open Files" is the title of the file chooser window */
+ open_dialog = pluma_file_chooser_dialog_new (_("Open Files"),
+ GTK_WINDOW (window),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ NULL,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_OK,
+ NULL);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_OPEN_DIALOG_KEY,
+ open_dialog);
+
+ g_object_weak_ref (G_OBJECT (open_dialog),
+ (GWeakNotify) open_dialog_destroyed,
+ window);
+
+ /* Set the curret folder uri */
+ doc = pluma_window_get_active_document (window);
+ if (doc != NULL)
+ {
+ GFile *file;
+
+ file = pluma_document_get_location (doc);
+
+ if (file != NULL)
+ {
+ default_path = g_file_get_parent (file);
+ g_object_unref (file);
+ }
+ }
+
+ if (default_path == NULL)
+ default_path = _pluma_window_get_default_location (window);
+
+ if (default_path != NULL)
+ {
+ gchar *uri;
+
+ uri = g_file_get_uri (default_path);
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (open_dialog),
+ uri);
+
+ g_free (uri);
+ g_object_unref (default_path);
+ }
+
+ g_signal_connect (open_dialog,
+ "response",
+ G_CALLBACK (open_dialog_response_cb),
+ window);
+
+ gtk_widget_show (open_dialog);
+}
+
+/* File saving */
+static void file_save_as (PlumaTab *tab, PlumaWindow *window);
+
+static gboolean
+is_read_only (GFile *location)
+{
+ gboolean ret = TRUE; /* default to read only */
+ GFileInfo *info;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ info = g_file_query_info (location,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (info != NULL)
+ {
+ if (g_file_info_has_attribute (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ ret = !g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
+ }
+
+ g_object_unref (info);
+ }
+
+ return ret;
+}
+
+/* FIXME: modify this dialog to be similar to the one provided by gtk+ for
+ * already existing files - Paolo (Oct. 11, 2005) */
+static gboolean
+replace_read_only_file (GtkWindow *parent, GFile *file)
+{
+ GtkWidget *dialog;
+ gint ret;
+ gchar *parse_name;
+ gchar *name_for_display;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ parse_name = g_file_get_parse_name (file);
+
+ /* Truncate the name so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the name doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ name_for_display = pluma_utils_str_middle_truncate (parse_name, 50);
+ g_free (parse_name);
+
+ dialog = gtk_message_dialog_new (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("The file \"%s\" is read-only."),
+ name_for_display);
+ g_free (name_for_display);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("Do you want to try to replace it "
+ "with the one you are saving?"));
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+
+ pluma_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Replace"),
+ GTK_STOCK_SAVE_AS,
+ GTK_RESPONSE_YES);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_CANCEL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ ret = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return (ret == GTK_RESPONSE_YES);
+}
+
+static void
+save_dialog_response_cb (PlumaFileChooserDialog *dialog,
+ gint response_id,
+ PlumaWindow *window)
+{
+ GFile *file;
+ const PlumaEncoding *encoding;
+ PlumaTab *tab;
+ gpointer data;
+ GSList *tabs_to_save_as;
+ PlumaDocumentNewlineType newline_type;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = PLUMA_TAB (g_object_get_data (G_OBJECT (dialog),
+ PLUMA_TAB_TO_SAVE_AS));
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ goto save_next_tab;
+ }
+
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ g_return_if_fail (file != NULL);
+
+ encoding = pluma_file_chooser_dialog_get_encoding (dialog);
+ newline_type = pluma_file_chooser_dialog_get_newline_type (dialog);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (tab != NULL)
+ {
+ PlumaDocument *doc;
+ gchar *parse_name;
+ gchar *uri;
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ parse_name = g_file_get_parse_name (file);
+
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ _("Saving file '%s'\342\200\246"),
+ parse_name);
+
+ g_free (parse_name);
+
+ /* let's remember the dir we navigated too,
+ * even if the saving fails... */
+ _pluma_window_set_default_location (window, file);
+
+ // FIXME: pass the GFile to tab when api is there
+ uri = g_file_get_uri (file);
+ _pluma_tab_save_as (tab, uri, encoding, newline_type);
+ g_free (uri);
+ }
+
+ g_object_unref (file);
+
+save_next_tab:
+
+ data = g_object_get_data (G_OBJECT (window),
+ PLUMA_LIST_OF_TABS_TO_SAVE_AS);
+ if (data == NULL)
+ return;
+
+ /* Save As the next tab of the list (we are Saving All files) */
+ tabs_to_save_as = (GSList *)data;
+ g_return_if_fail (tab == PLUMA_TAB (tabs_to_save_as->data));
+
+ /* Remove the first item of the list */
+ tabs_to_save_as = g_slist_delete_link (tabs_to_save_as,
+ tabs_to_save_as);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_LIST_OF_TABS_TO_SAVE_AS,
+ tabs_to_save_as);
+
+ if (tabs_to_save_as != NULL)
+ {
+ tab = PLUMA_TAB (tabs_to_save_as->data);
+
+ if (GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (tab),
+ PLUMA_IS_CLOSING_TAB)) == TRUE)
+ {
+ g_object_set_data (G_OBJECT (tab),
+ PLUMA_IS_CLOSING_TAB,
+ NULL);
+
+ /* Trace tab state changes */
+ g_signal_connect (tab,
+ "notify::state",
+ G_CALLBACK (tab_state_changed_while_saving),
+ window);
+ }
+
+ pluma_window_set_active_tab (window, tab);
+ file_save_as (tab, window);
+ }
+}
+
+static GtkFileChooserConfirmation
+confirm_overwrite_callback (GtkFileChooser *dialog,
+ gpointer data)
+{
+ gchar *uri;
+ GFile *file;
+ GtkFileChooserConfirmation res;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ uri = gtk_file_chooser_get_uri (dialog);
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+
+ if (is_read_only (file))
+ {
+ if (replace_read_only_file (GTK_WINDOW (dialog), file))
+ res = GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME;
+ else
+ res = GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN;
+ }
+ else
+ {
+ /* fall back to the default confirmation dialog */
+ res = GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM;
+ }
+
+ g_object_unref (file);
+
+ return res;
+}
+
+static void
+file_save_as (PlumaTab *tab,
+ PlumaWindow *window)
+{
+ GtkWidget *save_dialog;
+ GtkWindowGroup *wg;
+ PlumaDocument *doc;
+ GFile *file;
+ gboolean uri_set = FALSE;
+ const PlumaEncoding *encoding;
+ PlumaDocumentNewlineType newline_type;
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ save_dialog = pluma_file_chooser_dialog_new (_("Save As\342\200\246"),
+ GTK_WINDOW (window),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ NULL,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (save_dialog),
+ TRUE);
+ g_signal_connect (save_dialog,
+ "confirm-overwrite",
+ G_CALLBACK (confirm_overwrite_callback),
+ NULL);
+
+ wg = pluma_window_get_group (window);
+
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (save_dialog));
+
+ /* Save As dialog is modal to its main window */
+ gtk_window_set_modal (GTK_WINDOW (save_dialog), TRUE);
+
+ /* Set the suggested file name */
+ doc = pluma_tab_get_document (tab);
+ file = pluma_document_get_location (doc);
+
+ if (file != NULL)
+ {
+ uri_set = gtk_file_chooser_set_file (GTK_FILE_CHOOSER (save_dialog),
+ file,
+ NULL);
+
+ g_object_unref (file);
+ }
+
+
+ if (!uri_set)
+ {
+ GFile *default_path;
+ gchar *docname;
+
+ default_path = _pluma_window_get_default_location (window);
+ docname = pluma_document_get_short_name_for_display (doc);
+
+ if (default_path != NULL)
+ {
+ gchar *uri;
+
+ uri = g_file_get_uri (default_path);
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog),
+ uri);
+
+ g_free (uri);
+ g_object_unref (default_path);
+ }
+
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog),
+ docname);
+
+ g_free (docname);
+ }
+
+ /* Set suggested encoding */
+ encoding = pluma_document_get_encoding (doc);
+ g_return_if_fail (encoding != NULL);
+
+ newline_type = pluma_document_get_newline_type (doc);
+
+ pluma_file_chooser_dialog_set_encoding (PLUMA_FILE_CHOOSER_DIALOG (save_dialog),
+ encoding);
+
+ pluma_file_chooser_dialog_set_newline_type (PLUMA_FILE_CHOOSER_DIALOG (save_dialog),
+ newline_type);
+
+ g_object_set_data (G_OBJECT (save_dialog),
+ PLUMA_TAB_TO_SAVE_AS,
+ tab);
+
+ g_signal_connect (save_dialog,
+ "response",
+ G_CALLBACK (save_dialog_response_cb),
+ window);
+
+ gtk_widget_show (save_dialog);
+}
+
+static void
+file_save (PlumaTab *tab,
+ PlumaWindow *window)
+{
+ PlumaDocument *doc;
+ gchar *uri_for_display;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ if (pluma_document_is_untitled (doc) ||
+ pluma_document_get_readonly (doc))
+ {
+ pluma_debug_message (DEBUG_COMMANDS, "Untitled or Readonly");
+
+ file_save_as (tab, window);
+
+ return;
+ }
+
+ uri_for_display = pluma_document_get_uri_for_display (doc);
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ _("Saving file '%s'\342\200\246"),
+ uri_for_display);
+
+ g_free (uri_for_display);
+
+ _pluma_tab_save (tab);
+}
+
+void
+_pluma_cmd_file_save (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_window_get_active_tab (window);
+ if (tab == NULL)
+ return;
+
+ file_save (tab, window);
+}
+
+void
+_pluma_cmd_file_save_as (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_window_get_active_tab (window);
+ if (tab == NULL)
+ return;
+
+ file_save_as (tab, window);
+}
+
+static gboolean
+document_needs_saving (PlumaDocument *doc)
+{
+ if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ return TRUE;
+
+ /* we check if it was deleted only for local files
+ * since for remote files it may hang */
+ if (pluma_document_is_local (doc) && pluma_document_get_deleted (doc))
+ return TRUE;
+
+ return FALSE;
+}
+
+/*
+ * The docs in the list must belong to the same PlumaWindow.
+ */
+void
+_pluma_cmd_file_save_documents_list (PlumaWindow *window,
+ GList *docs)
+{
+ GList *l;
+ GSList *tabs_to_save_as = NULL;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (!(pluma_window_get_state (window) &
+ (PLUMA_WINDOW_STATE_PRINTING |
+ PLUMA_WINDOW_STATE_SAVING_SESSION)));
+
+ l = docs;
+ while (l != NULL)
+ {
+ PlumaDocument *doc;
+ PlumaTab *t;
+ PlumaTabState state;
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT (l->data));
+
+ doc = PLUMA_DOCUMENT (l->data);
+ t = pluma_tab_get_from_document (doc);
+ state = pluma_tab_get_state (t);
+
+ g_return_if_fail (state != PLUMA_TAB_STATE_PRINTING);
+ g_return_if_fail (state != PLUMA_TAB_STATE_PRINT_PREVIEWING);
+ g_return_if_fail (state != PLUMA_TAB_STATE_CLOSING);
+
+ if ((state == PLUMA_TAB_STATE_NORMAL) ||
+ (state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW) ||
+ (state == PLUMA_TAB_STATE_GENERIC_NOT_EDITABLE))
+ {
+ /* FIXME: manage the case of local readonly files owned by the
+ user is running pluma - Paolo (Dec. 8, 2005) */
+ if (pluma_document_is_untitled (doc) ||
+ pluma_document_get_readonly (doc))
+ {
+ if (document_needs_saving (doc))
+ {
+ tabs_to_save_as = g_slist_prepend (tabs_to_save_as,
+ t);
+ }
+ }
+ else
+ {
+ file_save (t, window);
+ }
+ }
+ else
+ {
+ /* If the state is:
+ - PLUMA_TAB_STATE_LOADING: we do not save since we are sure the file is unmodified
+ - PLUMA_TAB_STATE_REVERTING: we do not save since the user wants
+ to return back to the version of the file she previously saved
+ - PLUMA_TAB_STATE_SAVING: well, we are already saving (no need to save again)
+ - PLUMA_TAB_STATE_PRINTING, PLUMA_TAB_STATE_PRINT_PREVIEWING: there is not a
+ real reason for not saving in this case, we do not save to avoid to run
+ two operations using the message area at the same time (may be we can remove
+ this limitation in the future). Note that SaveAll, ClosAll
+ and Quit are unsensitive if the window state is PRINTING.
+ - PLUMA_TAB_STATE_GENERIC_ERROR: we do not save since the document contains
+ errors (I don't think this is a very frequent case, we should probably remove
+ this state)
+ - PLUMA_TAB_STATE_LOADING_ERROR: there is nothing to save
+ - PLUMA_TAB_STATE_REVERTING_ERROR: there is nothing to save and saving the current
+ document will overwrite the copy of the file the user wants to go back to
+ - PLUMA_TAB_STATE_SAVING_ERROR: we do not save since we just failed to save, so there is
+ no reason to automatically retry... we wait for user intervention
+ - PLUMA_TAB_STATE_CLOSING: this state is invalid in this case
+ */
+
+ gchar *uri_for_display;
+
+ uri_for_display = pluma_document_get_uri_for_display (doc);
+ pluma_debug_message (DEBUG_COMMANDS,
+ "File '%s' not saved. State: %d",
+ uri_for_display,
+ state);
+ g_free (uri_for_display);
+ }
+
+ l = g_list_next (l);
+ }
+
+ if (tabs_to_save_as != NULL)
+ {
+ PlumaTab *tab;
+
+ tabs_to_save_as = g_slist_reverse (tabs_to_save_as );
+
+ g_return_if_fail (g_object_get_data (G_OBJECT (window),
+ PLUMA_LIST_OF_TABS_TO_SAVE_AS) == NULL);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_LIST_OF_TABS_TO_SAVE_AS,
+ tabs_to_save_as);
+
+ tab = PLUMA_TAB (tabs_to_save_as->data);
+
+ pluma_window_set_active_tab (window, tab);
+ file_save_as (tab, window);
+ }
+}
+
+void
+pluma_commands_save_all_documents (PlumaWindow *window)
+{
+ GList *docs;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ docs = pluma_window_get_documents (window);
+
+ _pluma_cmd_file_save_documents_list (window, docs);
+
+ g_list_free (docs);
+}
+
+void
+_pluma_cmd_file_save_all (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_commands_save_all_documents (window);
+}
+
+void
+pluma_commands_save_document (PlumaWindow *window,
+ PlumaDocument *document)
+{
+ PlumaTab *tab;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+ g_return_if_fail (PLUMA_IS_DOCUMENT (document));
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_tab_get_from_document (document);
+ file_save (tab, window);
+}
+
+/* File revert */
+static void
+do_revert (PlumaWindow *window,
+ PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ gchar *docname;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ doc = pluma_tab_get_document (tab);
+ docname = pluma_document_get_short_name_for_display (doc);
+
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ _("Reverting the document '%s'\342\200\246"),
+ docname);
+
+ g_free (docname);
+
+ _pluma_tab_revert (tab);
+}
+
+static void
+revert_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ /* FIXME: we are relying on the fact that the dialog is
+ modal so the active tab can't be changed...
+ not very nice - Paolo (Oct 11, 2005) */
+ tab = pluma_window_get_active_tab (window);
+ if (tab == NULL)
+ return;
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ do_revert (window, tab);
+ }
+}
+
+static GtkWidget *
+revert_dialog (PlumaWindow *window,
+ PlumaDocument *doc)
+{
+ GtkWidget *dialog;
+ gchar *docname;
+ gchar *primary_msg;
+ gchar *secondary_msg;
+ glong seconds;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ docname = pluma_document_get_short_name_for_display (doc);
+ primary_msg = g_strdup_printf (_("Revert unsaved changes to document '%s'?"),
+ docname);
+ g_free (docname);
+
+ seconds = MAX (1, _pluma_document_get_seconds_since_last_save_or_load (doc));
+
+ if (seconds < 55)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last %ld second "
+ "will be permanently lost.",
+ "Changes made to the document in the last %ld seconds "
+ "will be permanently lost.",
+ seconds),
+ seconds);
+ }
+ else if (seconds < 75) /* 55 <= seconds < 75 */
+ {
+ secondary_msg = g_strdup (_("Changes made to the document in the last minute "
+ "will be permanently lost."));
+ }
+ else if (seconds < 110) /* 75 <= seconds < 110 */
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last minute and "
+ "%ld second will be permanently lost.",
+ "Changes made to the document in the last minute and "
+ "%ld seconds will be permanently lost.",
+ seconds - 60 ),
+ seconds - 60);
+ }
+ else if (seconds < 3600)
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last %ld minute "
+ "will be permanently lost.",
+ "Changes made to the document in the last %ld minutes "
+ "will be permanently lost.",
+ seconds / 60),
+ seconds / 60);
+ }
+ else if (seconds < 7200)
+ {
+ gint minutes;
+ seconds -= 3600;
+
+ minutes = seconds / 60;
+ if (minutes < 5)
+ {
+ secondary_msg = g_strdup (_("Changes made to the document in the last hour "
+ "will be permanently lost."));
+ }
+ else
+ {
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last hour and "
+ "%d minute will be permanently lost.",
+ "Changes made to the document in the last hour and "
+ "%d minutes will be permanently lost.",
+ minutes),
+ minutes);
+ }
+ }
+ else
+ {
+ gint hours;
+
+ hours = seconds / 3600;
+
+ secondary_msg = g_strdup_printf (
+ ngettext ("Changes made to the document in the last %d hour "
+ "will be permanently lost.",
+ "Changes made to the document in the last %d hours "
+ "will be permanently lost.",
+ hours),
+ hours);
+ }
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ "%s", primary_msg);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", secondary_msg);
+ g_free (primary_msg);
+ g_free (secondary_msg);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+
+ pluma_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Revert"),
+ GTK_STOCK_REVERT_TO_SAVED,
+ GTK_RESPONSE_OK);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_CANCEL);
+
+ return dialog;
+}
+
+void
+_pluma_cmd_file_revert (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+ PlumaDocument *doc;
+ GtkWidget *dialog;
+ GtkWindowGroup *wg;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_window_get_active_tab (window);
+ g_return_if_fail (tab != NULL);
+
+ /* If we are already displaying a notification
+ * reverting will drop local modifications, do
+ * not bug the user further */
+ if (pluma_tab_get_state (tab) == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
+ {
+ do_revert (window, tab);
+ return;
+ }
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (doc != NULL);
+ g_return_if_fail (!pluma_document_is_untitled (doc));
+
+ dialog = revert_dialog (window, doc);
+
+ wg = pluma_window_get_group (window);
+
+ gtk_window_group_add_window (wg, GTK_WINDOW (dialog));
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (revert_dialog_response_cb),
+ window);
+
+ gtk_widget_show (dialog);
+}
+
+/* Close tab */
+static gboolean
+really_close_tab (PlumaTab *tab)
+{
+ GtkWidget *toplevel;
+ PlumaWindow *window;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_val_if_fail (pluma_tab_get_state (tab) == PLUMA_TAB_STATE_CLOSING,
+ FALSE);
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tab));
+ g_return_val_if_fail (PLUMA_IS_WINDOW (toplevel), FALSE);
+
+ window = PLUMA_WINDOW (toplevel);
+
+ pluma_window_close_tab (window, tab);
+
+ if (pluma_window_get_active_tab (window) == NULL)
+ {
+ gboolean is_quitting;
+
+ is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING));
+
+ if (is_quitting)
+ gtk_widget_destroy (GTK_WIDGET (window));
+ }
+
+ return FALSE;
+}
+
+static void
+tab_state_changed_while_saving (PlumaTab *tab,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ PlumaTabState ts;
+
+ ts = pluma_tab_get_state (tab);
+
+ pluma_debug_message (DEBUG_COMMANDS, "State while saving: %d\n", ts);
+
+ /* When the state become NORMAL, it means the saving operation is
+ finished */
+ if (ts == PLUMA_TAB_STATE_NORMAL)
+ {
+ PlumaDocument *doc;
+
+ g_signal_handlers_disconnect_by_func (tab,
+ G_CALLBACK (tab_state_changed_while_saving),
+ window);
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (doc != NULL);
+
+ /* If the saving operation failed or was interrupted, then the
+ document is still "modified" -> do not close the tab */
+ if (document_needs_saving (doc))
+ return;
+
+ /* Close the document only if it has been succesfully saved.
+ Tab state is set to CLOSING (it is a state without exiting
+ transitions) and the tab is closed in a idle handler */
+ _pluma_tab_mark_for_closing (tab);
+
+ g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+ (GSourceFunc)really_close_tab,
+ tab,
+ NULL);
+ }
+}
+
+static void
+save_and_close (PlumaTab *tab,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ /* Trace tab state changes */
+ g_signal_connect (tab,
+ "notify::state",
+ G_CALLBACK (tab_state_changed_while_saving),
+ window);
+
+ file_save (tab, window);
+}
+
+static void
+save_as_and_close (PlumaTab *tab,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_object_set_data (G_OBJECT (tab),
+ PLUMA_IS_CLOSING_TAB,
+ NULL);
+
+ /* Trace tab state changes */
+ g_signal_connect (tab,
+ "notify::state",
+ G_CALLBACK (tab_state_changed_while_saving),
+ window);
+
+ pluma_window_set_active_tab (window, tab);
+ file_save_as (tab, window);
+}
+
+static void
+save_and_close_all_documents (const GList *docs,
+ PlumaWindow *window)
+{
+ GList *tabs;
+ GList *l;
+ GSList *sl;
+ GSList *tabs_to_save_as;
+ GSList *tabs_to_save_and_close;
+ GList *tabs_to_close;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (!(pluma_window_get_state (window) & PLUMA_WINDOW_STATE_PRINTING));
+
+ tabs = gtk_container_get_children (
+ GTK_CONTAINER (_pluma_window_get_notebook (window)));
+
+ tabs_to_save_as = NULL;
+ tabs_to_save_and_close = NULL;
+ tabs_to_close = NULL;
+
+ l = tabs;
+ while (l != NULL)
+ {
+ PlumaTab *t;
+ PlumaTabState state;
+ PlumaDocument *doc;
+
+ t = PLUMA_TAB (l->data);
+
+ state = pluma_tab_get_state (t);
+ doc = pluma_tab_get_document (t);
+
+ /* If the state is: ([*] invalid states)
+ - PLUMA_TAB_STATE_NORMAL: close (and if needed save)
+ - PLUMA_TAB_STATE_LOADING: close, we are sure the file is unmodified
+ - PLUMA_TAB_STATE_REVERTING: since the user wants
+ to return back to the version of the file she previously saved, we can close
+ without saving (CHECK: are we sure this is the right behavior, suppose the case
+ the original file has been deleted)
+ - [*] PLUMA_TAB_STATE_SAVING: invalid, ClosAll
+ and Quit are unsensitive if the window state is SAVING.
+ - [*] PLUMA_TAB_STATE_PRINTING, PLUMA_TAB_STATE_PRINT_PREVIEWING: there is not a
+ real reason for not closing in this case, we do not save to avoid to run
+ two operations using the message area at the same time (may be we can remove
+ this limitation in the future). Note that ClosAll
+ and Quit are unsensitive if the window state is PRINTING.
+ - PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW: close (and if needed save)
+ - PLUMA_TAB_STATE_LOADING_ERROR: close without saving (if the state is LOADING_ERROR then the
+ document is not modified)
+ - PLUMA_TAB_STATE_REVERTING_ERROR: we do not close since the document contains errors
+ - PLUMA_TAB_STATE_SAVING_ERROR: we do not close since the document contains errors
+ - PLUMA_TAB_STATE_GENERIC_ERROR: we do not close since the document contains
+ errors (CHECK: we should problably remove this state)
+ - [*] PLUMA_TAB_STATE_CLOSING: this state is invalid in this case
+ */
+
+ g_return_if_fail (state != PLUMA_TAB_STATE_PRINTING);
+ g_return_if_fail (state != PLUMA_TAB_STATE_PRINT_PREVIEWING);
+ g_return_if_fail (state != PLUMA_TAB_STATE_CLOSING);
+ g_return_if_fail (state != PLUMA_TAB_STATE_SAVING);
+
+ if ((state != PLUMA_TAB_STATE_SAVING_ERROR) &&
+ (state != PLUMA_TAB_STATE_GENERIC_ERROR) &&
+ (state != PLUMA_TAB_STATE_REVERTING_ERROR))
+ {
+ if ((g_list_index ((GList *)docs, doc) >= 0) &&
+ (state != PLUMA_TAB_STATE_LOADING) &&
+ (state != PLUMA_TAB_STATE_LOADING_ERROR) &&
+ (state != PLUMA_TAB_STATE_REVERTING)) /* CHECK: is this the right behavior with REVERTING ?*/
+ {
+ /* The document must be saved before closing */
+ g_return_if_fail (document_needs_saving (doc));
+
+ /* FIXME: manage the case of local readonly files owned by the
+ user is running pluma - Paolo (Dec. 8, 2005) */
+ if (pluma_document_is_untitled (doc) ||
+ pluma_document_get_readonly (doc))
+ {
+ g_object_set_data (G_OBJECT (t),
+ PLUMA_IS_CLOSING_TAB,
+ GBOOLEAN_TO_POINTER (TRUE));
+
+ tabs_to_save_as = g_slist_prepend (tabs_to_save_as,
+ t);
+ }
+ else
+ {
+ tabs_to_save_and_close = g_slist_prepend (tabs_to_save_and_close,
+ t);
+ }
+ }
+ else
+ {
+ /* The document must be closed without saving */
+ tabs_to_close = g_list_prepend (tabs_to_close,
+ t);
+ }
+ }
+
+ l = g_list_next (l);
+ }
+
+ g_list_free (tabs);
+
+ /* Close all tabs to close (in a sync way) */
+ pluma_window_close_tabs (window, tabs_to_close);
+ g_list_free (tabs_to_close);
+
+ /* Save and close all the files in tabs_to_save_and_close */
+ sl = tabs_to_save_and_close;
+ while (sl != NULL)
+ {
+ save_and_close (PLUMA_TAB (sl->data),
+ window);
+ sl = g_slist_next (sl);
+ }
+ g_slist_free (tabs_to_save_and_close);
+
+ /* Save As and close all the files in tabs_to_save_as */
+ if (tabs_to_save_as != NULL)
+ {
+ PlumaTab *tab;
+
+ tabs_to_save_as = g_slist_reverse (tabs_to_save_as );
+
+ g_return_if_fail (g_object_get_data (G_OBJECT (window),
+ PLUMA_LIST_OF_TABS_TO_SAVE_AS) == NULL);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_LIST_OF_TABS_TO_SAVE_AS,
+ tabs_to_save_as);
+
+ tab = PLUMA_TAB (tabs_to_save_as->data);
+
+ save_as_and_close (tab, window);
+ }
+}
+
+static void
+save_and_close_document (const GList *docs,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (docs->next == NULL);
+
+ tab = pluma_tab_get_from_document (PLUMA_DOCUMENT (docs->data));
+ g_return_if_fail (tab != NULL);
+
+ save_and_close (tab, window);
+}
+
+static void
+close_all_tabs (PlumaWindow *window)
+{
+ gboolean is_quitting;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ /* There is no document to save -> close all tabs */
+ pluma_window_close_all_tabs (window);
+
+ is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING));
+
+ if (is_quitting)
+ gtk_widget_destroy (GTK_WIDGET (window));
+
+ return;
+}
+
+static void
+close_document (PlumaWindow *window,
+ PlumaDocument *doc)
+{
+ PlumaTab *tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ tab = pluma_tab_get_from_document (doc);
+ g_return_if_fail (tab != NULL);
+
+ pluma_window_close_tab (window, tab);
+}
+
+static void
+close_confirmation_dialog_response_handler (PlumaCloseConfirmationDialog *dlg,
+ gint response_id,
+ PlumaWindow *window)
+{
+ GList *selected_documents;
+ gboolean is_closing_all;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ is_closing_all = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
+ PLUMA_IS_CLOSING_ALL));
+
+ gtk_widget_hide (GTK_WIDGET (dlg));
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_YES: /* Save and Close */
+ selected_documents = pluma_close_confirmation_dialog_get_selected_documents (dlg);
+ if (selected_documents == NULL)
+ {
+ if (is_closing_all)
+ {
+ /* There is no document to save -> close all tabs */
+ /* We call gtk_widget_destroy before close_all_tabs
+ * because close_all_tabs could destroy the pluma window */
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+
+ close_all_tabs (window);
+
+ return;
+ }
+ else
+ g_return_if_reached ();
+ }
+ else
+ {
+ if (is_closing_all)
+ {
+ save_and_close_all_documents (selected_documents,
+ window);
+ }
+ else
+ {
+ save_and_close_document (selected_documents,
+ window);
+ }
+ }
+
+ g_list_free (selected_documents);
+
+ break;
+
+ case GTK_RESPONSE_NO: /* Close without Saving */
+ if (is_closing_all)
+ {
+ /* We call gtk_widget_destroy before close_all_tabs
+ * because close_all_tabs could destroy the pluma window */
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+
+ close_all_tabs (window);
+
+ return;
+ }
+ else
+ {
+ const GList *unsaved_documents;
+
+ unsaved_documents = pluma_close_confirmation_dialog_get_unsaved_documents (dlg);
+ g_return_if_fail (unsaved_documents->next == NULL);
+
+ close_document (window,
+ PLUMA_DOCUMENT (unsaved_documents->data));
+ }
+
+ break;
+ default: /* Do not close */
+
+ /* Reset is_quitting flag */
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING,
+ GBOOLEAN_TO_POINTER (FALSE));
+
+#ifdef OS_OSX
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING_ALL,
+ GINT_TO_POINTER (FALSE));
+#endif
+ break;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+}
+
+/* Returns TRUE if the tab can be immediately closed */
+static gboolean
+tab_can_close (PlumaTab *tab,
+ GtkWindow *window)
+{
+ PlumaDocument *doc;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ doc = pluma_tab_get_document (tab);
+
+ if (!_pluma_tab_can_close (tab))
+ {
+ GtkWidget *dlg;
+
+ dlg = pluma_close_confirmation_dialog_new_single (
+ window,
+ doc,
+ FALSE);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (close_confirmation_dialog_response_handler),
+ window);
+
+ gtk_widget_show (dlg);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* CHECK: we probably need this one public for plugins...
+ * maybe even a _list variant. Or maybe it's better make
+ * pluma_window_close_tab always run the confirm dialog?
+ * we should not allow closing a tab without resetting the
+ * PLUMA_IS_CLOSING_ALL flag!
+ */
+void
+_pluma_cmd_file_close_tab (PlumaTab *tab,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (GTK_WIDGET (window) == gtk_widget_get_toplevel (GTK_WIDGET (tab)));
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_CLOSING_ALL,
+ GBOOLEAN_TO_POINTER (FALSE));
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING,
+ GBOOLEAN_TO_POINTER (FALSE));
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING_ALL,
+ GINT_TO_POINTER (FALSE));
+
+
+ if (tab_can_close (tab, GTK_WINDOW (window)))
+ pluma_window_close_tab (window, tab);
+}
+
+void
+_pluma_cmd_file_close (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaTab *active_tab;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_tab = pluma_window_get_active_tab (window);
+
+ if (active_tab == NULL)
+ {
+#ifdef OS_OSX
+ /* Close the window on OS X */
+ gtk_widget_destroy (GTK_WIDGET (window));
+#endif
+ return;
+ }
+
+ _pluma_cmd_file_close_tab (active_tab, window);
+}
+
+/* Close all tabs */
+static void
+file_close_all (PlumaWindow *window,
+ gboolean is_quitting)
+{
+ GList *unsaved_docs;
+ GtkWidget *dlg;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (!(pluma_window_get_state (window) &
+ (PLUMA_WINDOW_STATE_SAVING |
+ PLUMA_WINDOW_STATE_PRINTING |
+ PLUMA_WINDOW_STATE_SAVING_SESSION)));
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_CLOSING_ALL,
+ GBOOLEAN_TO_POINTER (TRUE));
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING,
+ GBOOLEAN_TO_POINTER (is_quitting));
+
+ unsaved_docs = pluma_window_get_unsaved_documents (window);
+
+ if (unsaved_docs == NULL)
+ {
+ /* There is no document to save -> close all tabs */
+ pluma_window_close_all_tabs (window);
+
+ if (is_quitting)
+ gtk_widget_destroy (GTK_WIDGET (window));
+
+ return;
+ }
+
+ if (unsaved_docs->next == NULL)
+ {
+ /* There is only one unsaved document */
+ PlumaTab *tab;
+ PlumaDocument *doc;
+
+ doc = PLUMA_DOCUMENT (unsaved_docs->data);
+
+ tab = pluma_tab_get_from_document (doc);
+ g_return_if_fail (tab != NULL);
+
+ pluma_window_set_active_tab (window, tab);
+
+ dlg = pluma_close_confirmation_dialog_new_single (
+ GTK_WINDOW (window),
+ doc,
+ FALSE);
+ }
+ else
+ {
+ dlg = pluma_close_confirmation_dialog_new (GTK_WINDOW (window),
+ unsaved_docs,
+ FALSE);
+ }
+
+ g_list_free (unsaved_docs);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (close_confirmation_dialog_response_handler),
+ window);
+
+ gtk_widget_show (dlg);
+}
+
+void
+_pluma_cmd_file_close_all (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_return_if_fail (!(pluma_window_get_state (window) &
+ (PLUMA_WINDOW_STATE_SAVING |
+ PLUMA_WINDOW_STATE_PRINTING |
+ PLUMA_WINDOW_STATE_SAVING_SESSION)));
+
+ file_close_all (window, FALSE);
+}
+
+/* Quit */
+#ifdef OS_OSX
+static void
+quit_all ()
+{
+ GList *windows;
+ GList *item;
+ PlumaApp *app;
+
+ app = pluma_app_get_default ();
+ windows = g_list_copy ((GList *)pluma_app_get_windows (app));
+
+ for (item = windows; item; item = g_list_next (item))
+ {
+ PlumaWindow *window = PLUMA_WINDOW (item->data);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_IS_QUITTING_ALL,
+ GINT_TO_POINTER (TRUE));
+
+ if (!(pluma_window_get_state (window) &
+ (PLUMA_WINDOW_STATE_SAVING |
+ PLUMA_WINDOW_STATE_PRINTING |
+ PLUMA_WINDOW_STATE_SAVING_SESSION)))
+ {
+ file_close_all (window, TRUE);
+ }
+ }
+
+ g_list_free (windows);
+}
+#endif
+
+void
+_pluma_cmd_file_quit (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+#ifdef OS_OSX
+ if (action != NULL)
+ {
+ quit_all ();
+ return;
+ }
+#endif
+
+ g_return_if_fail (!(pluma_window_get_state (window) &
+ (PLUMA_WINDOW_STATE_SAVING |
+ PLUMA_WINDOW_STATE_PRINTING |
+ PLUMA_WINDOW_STATE_SAVING_SESSION)));
+
+ file_close_all (window, TRUE);
+}
diff --git a/pluma/pluma-commands-help.c b/pluma/pluma-commands-help.c
new file mode 100755
index 00000000..66835bd2
--- /dev/null
+++ b/pluma/pluma-commands-help.c
@@ -0,0 +1,116 @@
+/*
+ * pluma-help-commands.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "pluma-commands.h"
+#include "pluma-debug.h"
+#include "pluma-help.h"
+#include "pluma-dirs.h"
+
+void
+_pluma_cmd_help_contents (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ pluma_help_display (GTK_WINDOW (window), NULL, NULL);
+}
+
+void
+_pluma_cmd_help_about (GtkAction *action,
+ PlumaWindow *window)
+{
+ static const gchar * const authors[] = {
+ "Paolo Maggi <[email protected]>",
+ "Paolo Borelli <[email protected]>",
+ "Steve Fr\303\251cinaux <[email protected]>",
+ "Jesse van den Kieboom <[email protected]>",
+ "Ignacio Casal Quinteiro <[email protected]>",
+ "James Willcox <[email protected]>",
+ "Chema Celorio",
+ "Federico Mena Quintero <[email protected]>",
+ NULL
+ };
+
+ static const gchar * const documenters[] = {
+ "Sun MATE Documentation Team <[email protected]>",
+ "Eric Baudais <[email protected]>",
+ NULL
+ };
+
+ static const gchar copyright[] = \
+ "Copyright \xc2\xa9 1998-2000 Evan Lawrence, Alex Robert\n"
+ "Copyright \xc2\xa9 2000-2002 Chema Celorio, Paolo Maggi\n"
+ "Copyright \xc2\xa9 2003-2006 Paolo Maggi\n"
+ "Copyright \xc2\xa9 2004-2010 Paolo Borelli, Jesse van den Kieboom\nSteve Fr\303\251cinaux, Ignacio Casal Quinteiro"
+ "Copyright \xc2\xa9 2011 Perberos";
+
+ static const gchar comments[] = \
+ N_("pluma is a small and lightweight text editor for the "
+ "MATE Desktop");
+
+ GdkPixbuf *logo;
+ gchar *data_dir;
+ gchar *logo_file;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ data_dir = pluma_dirs_get_pluma_data_dir ();
+ logo_file = g_build_filename (data_dir,
+ "logo",
+ "pluma.png",
+ NULL);
+ g_free (data_dir);
+ logo = gdk_pixbuf_new_from_file (logo_file, NULL);
+ g_free (logo_file);
+
+ gtk_show_about_dialog (GTK_WINDOW (window),
+ "program-name", "Pluma",
+ "authors", authors,
+ "comments", _(comments),
+ "copyright", copyright,
+ "documenters", documenters,
+ "logo", logo,
+ "translator-credits", _("translator-credits"),
+ "version", VERSION,
+ "website", "http://matsusoft.com.ar/projects/mate/",
+ NULL);
+
+ if (logo)
+ g_object_unref (logo);
+}
diff --git a/pluma/pluma-commands-search.c b/pluma/pluma-commands-search.c
new file mode 100755
index 00000000..5bdcd92d
--- /dev/null
+++ b/pluma/pluma-commands-search.c
@@ -0,0 +1,716 @@
+/*
+ * pluma-search-commands.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2006 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "pluma-commands.h"
+#include "pluma-debug.h"
+#include "pluma-statusbar.h"
+#include "pluma-window.h"
+#include "pluma-window-private.h"
+#include "pluma-utils.h"
+#include "dialogs/pluma-search-dialog.h"
+
+#define PLUMA_SEARCH_DIALOG_KEY "pluma-search-dialog-key"
+#define PLUMA_LAST_SEARCH_DATA_KEY "pluma-last-search-data-key"
+
+typedef struct _LastSearchData LastSearchData;
+struct _LastSearchData
+{
+ gint x;
+ gint y;
+};
+
+static void
+last_search_data_free (LastSearchData *data)
+{
+ g_slice_free (LastSearchData, data);
+}
+
+static void
+last_search_data_restore_position (PlumaSearchDialog *dlg)
+{
+ LastSearchData *data;
+
+ data = g_object_get_data (G_OBJECT (dlg), PLUMA_LAST_SEARCH_DATA_KEY);
+
+ if (data != NULL)
+ {
+ gtk_window_move (GTK_WINDOW (dlg),
+ data->x,
+ data->y);
+ }
+}
+
+static void
+last_search_data_store_position (PlumaSearchDialog *dlg)
+{
+ LastSearchData *data;
+
+ data = g_object_get_data (G_OBJECT (dlg), PLUMA_LAST_SEARCH_DATA_KEY);
+
+ if (data == NULL)
+ {
+ data = g_slice_new (LastSearchData);
+
+ g_object_set_data_full (G_OBJECT (dlg),
+ PLUMA_LAST_SEARCH_DATA_KEY,
+ data,
+ (GDestroyNotify) last_search_data_free);
+ }
+
+ gtk_window_get_position (GTK_WINDOW (dlg),
+ &data->x,
+ &data->y);
+}
+
+/* Use occurences only for Replace All */
+static void
+text_found (PlumaWindow *window,
+ gint occurrences)
+{
+ if (occurrences > 1)
+ {
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ ngettext("Found and replaced %d occurrence",
+ "Found and replaced %d occurrences",
+ occurrences),
+ occurrences);
+ }
+ else
+ {
+ if (occurrences == 1)
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ _("Found and replaced one occurrence"));
+ else
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ " ");
+ }
+}
+
+#define MAX_MSG_LENGTH 40
+static void
+text_not_found (PlumaWindow *window,
+ const gchar *text)
+{
+ gchar *searched;
+
+ searched = pluma_utils_str_end_truncate (text, MAX_MSG_LENGTH);
+
+ pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->generic_message_cid,
+ /* Translators: %s is replaced by the text
+ entered by the user in the search box */
+ _("\"%s\" not found"), searched);
+ g_free (searched);
+}
+
+static gboolean
+run_search (PlumaView *view,
+ gboolean wrap_around,
+ gboolean search_backwards)
+{
+ PlumaDocument *doc;
+ GtkTextIter start_iter;
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found = FALSE;
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ if (!search_backwards)
+ {
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ NULL,
+ &start_iter);
+
+ found = pluma_document_search_forward (doc,
+ &start_iter,
+ NULL,
+ &match_start,
+ &match_end);
+ }
+ else
+ {
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start_iter,
+ NULL);
+
+ found = pluma_document_search_backward (doc,
+ NULL,
+ &start_iter,
+ &match_start,
+ &match_end);
+ }
+
+ if (!found && wrap_around)
+ {
+ if (!search_backwards)
+ found = pluma_document_search_forward (doc,
+ NULL,
+ NULL, /* FIXME: set the end_inter */
+ &match_start,
+ &match_end);
+ else
+ found = pluma_document_search_backward (doc,
+ NULL, /* FIXME: set the start_inter */
+ NULL,
+ &match_start,
+ &match_end);
+ }
+
+ if (found)
+ {
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc),
+ &match_start);
+
+ gtk_text_buffer_move_mark_by_name (GTK_TEXT_BUFFER (doc),
+ "selection_bound",
+ &match_end);
+
+ pluma_view_scroll_to_cursor (view);
+ }
+ else
+ {
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc),
+ &start_iter);
+ }
+
+ return found;
+}
+
+static void
+do_find (PlumaSearchDialog *dialog,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+ PlumaDocument *doc;
+ gchar *search_text;
+ const gchar *entry_text;
+ gboolean match_case;
+ gboolean entire_word;
+ gboolean wrap_around;
+ gboolean search_backwards;
+ guint flags = 0;
+ guint old_flags = 0;
+ gboolean found;
+
+ /* TODO: make the dialog insensitive when all the tabs are closed
+ * and assert here that the view is not NULL */
+ active_view = pluma_window_get_active_view (window);
+ if (active_view == NULL)
+ return;
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view)));
+
+ entry_text = pluma_search_dialog_get_search_text (dialog);
+
+ match_case = pluma_search_dialog_get_match_case (dialog);
+ entire_word = pluma_search_dialog_get_entire_word (dialog);
+ search_backwards = pluma_search_dialog_get_backwards (dialog);
+ wrap_around = pluma_search_dialog_get_wrap_around (dialog);
+
+ PLUMA_SEARCH_SET_CASE_SENSITIVE (flags, match_case);
+ PLUMA_SEARCH_SET_ENTIRE_WORD (flags, entire_word);
+
+ search_text = pluma_document_get_search_text (doc, &old_flags);
+
+ if ((search_text == NULL) ||
+ (strcmp (search_text, entry_text) != 0) ||
+ (flags != old_flags))
+ {
+ pluma_document_set_search_text (doc, entry_text, flags);
+ }
+
+ g_free (search_text);
+
+ found = run_search (active_view,
+ wrap_around,
+ search_backwards);
+
+ if (found)
+ text_found (window, 0);
+ else
+ text_not_found (window, entry_text);
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE,
+ found);
+}
+
+/* FIXME: move in pluma-document.c and share it with pluma-view */
+static gboolean
+get_selected_text (GtkTextBuffer *doc,
+ gchar **selected_text,
+ gint *len)
+{
+ GtkTextIter start, end;
+
+ g_return_val_if_fail (selected_text != NULL, FALSE);
+ g_return_val_if_fail (*selected_text == NULL, FALSE);
+
+ if (!gtk_text_buffer_get_selection_bounds (doc, &start, &end))
+ {
+ if (len != NULL)
+ len = 0;
+
+ return FALSE;
+ }
+
+ *selected_text = gtk_text_buffer_get_slice (doc, &start, &end, TRUE);
+
+ if (len != NULL)
+ *len = g_utf8_strlen (*selected_text, -1);
+
+ return TRUE;
+}
+
+static void
+replace_selected_text (GtkTextBuffer *buffer,
+ const gchar *replace)
+{
+ g_return_if_fail (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL));
+ g_return_if_fail (replace != NULL);
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ gtk_text_buffer_delete_selection (buffer, FALSE, TRUE);
+
+ gtk_text_buffer_insert_at_cursor (buffer, replace, strlen (replace));
+
+ gtk_text_buffer_end_user_action (buffer);
+}
+
+static void
+do_replace (PlumaSearchDialog *dialog,
+ PlumaWindow *window)
+{
+ PlumaDocument *doc;
+ const gchar *search_entry_text;
+ const gchar *replace_entry_text;
+ gchar *unescaped_search_text;
+ gchar *unescaped_replace_text;
+ gchar *selected_text = NULL;
+ gboolean match_case;
+
+ doc = pluma_window_get_active_document (window);
+ if (doc == NULL)
+ return;
+
+ search_entry_text = pluma_search_dialog_get_search_text (dialog);
+ g_return_if_fail ((search_entry_text) != NULL);
+ g_return_if_fail ((*search_entry_text) != '\0');
+
+ /* replace text may be "", we just delete */
+ replace_entry_text = pluma_search_dialog_get_replace_text (dialog);
+ g_return_if_fail ((replace_entry_text) != NULL);
+
+ unescaped_search_text = pluma_utils_unescape_search_text (search_entry_text);
+
+ get_selected_text (GTK_TEXT_BUFFER (doc),
+ &selected_text,
+ NULL);
+
+ match_case = pluma_search_dialog_get_match_case (dialog);
+
+ if ((selected_text == NULL) ||
+ (match_case && (strcmp (selected_text, unescaped_search_text) != 0)) ||
+ (!match_case && !g_utf8_caselessnmatch (selected_text,
+ unescaped_search_text,
+ strlen (selected_text),
+ strlen (unescaped_search_text)) != 0))
+ {
+ do_find (dialog, window);
+ g_free (unescaped_search_text);
+ g_free (selected_text);
+
+ return;
+ }
+
+ unescaped_replace_text = pluma_utils_unescape_search_text (replace_entry_text);
+ replace_selected_text (GTK_TEXT_BUFFER (doc), unescaped_replace_text);
+
+ g_free (unescaped_search_text);
+ g_free (selected_text);
+ g_free (unescaped_replace_text);
+
+ do_find (dialog, window);
+}
+
+static void
+do_replace_all (PlumaSearchDialog *dialog,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+ PlumaDocument *doc;
+ const gchar *search_entry_text;
+ const gchar *replace_entry_text;
+ gboolean match_case;
+ gboolean entire_word;
+ guint flags = 0;
+ gint count;
+
+ active_view = pluma_window_get_active_view (window);
+ if (active_view == NULL)
+ return;
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view)));
+
+ search_entry_text = pluma_search_dialog_get_search_text (dialog);
+ g_return_if_fail ((search_entry_text) != NULL);
+ g_return_if_fail ((*search_entry_text) != '\0');
+
+ /* replace text may be "", we just delete all occurrencies */
+ replace_entry_text = pluma_search_dialog_get_replace_text (dialog);
+ g_return_if_fail ((replace_entry_text) != NULL);
+
+ match_case = pluma_search_dialog_get_match_case (dialog);
+ entire_word = pluma_search_dialog_get_entire_word (dialog);
+
+ PLUMA_SEARCH_SET_CASE_SENSITIVE (flags, match_case);
+ PLUMA_SEARCH_SET_ENTIRE_WORD (flags, entire_word);
+
+ count = pluma_document_replace_all (doc,
+ search_entry_text,
+ replace_entry_text,
+ flags);
+
+ if (count > 0)
+ {
+ text_found (window, count);
+ }
+ else
+ {
+ text_not_found (window, search_entry_text);
+ }
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE,
+ FALSE);
+}
+
+static void
+search_dialog_response_cb (PlumaSearchDialog *dialog,
+ gint response_id,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ switch (response_id)
+ {
+ case PLUMA_SEARCH_DIALOG_FIND_RESPONSE:
+ do_find (dialog, window);
+ break;
+ case PLUMA_SEARCH_DIALOG_REPLACE_RESPONSE:
+ do_replace (dialog, window);
+ break;
+ case PLUMA_SEARCH_DIALOG_REPLACE_ALL_RESPONSE:
+ do_replace_all (dialog, window);
+ break;
+ default:
+ last_search_data_store_position (dialog);
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ }
+}
+
+static gboolean
+search_dialog_delete_event_cb (GtkWidget *widget,
+ GdkEventAny *event,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ /* prevent destruction */
+ return TRUE;
+}
+
+static void
+search_dialog_destroyed (PlumaWindow *window,
+ PlumaSearchDialog *dialog)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_SEARCH_DIALOG_KEY,
+ NULL);
+ g_object_set_data (G_OBJECT (dialog),
+ PLUMA_LAST_SEARCH_DATA_KEY,
+ NULL);
+}
+
+static GtkWidget *
+create_dialog (PlumaWindow *window, gboolean show_replace)
+{
+ GtkWidget *dialog;
+
+ dialog = pluma_search_dialog_new (GTK_WINDOW (window), show_replace);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (search_dialog_response_cb),
+ window);
+ g_signal_connect (dialog,
+ "delete-event",
+ G_CALLBACK (search_dialog_delete_event_cb),
+ NULL);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_SEARCH_DIALOG_KEY,
+ dialog);
+
+ g_object_weak_ref (G_OBJECT (dialog),
+ (GWeakNotify) search_dialog_destroyed,
+ window);
+
+ return dialog;
+}
+
+void
+_pluma_cmd_search_find (GtkAction *action,
+ PlumaWindow *window)
+{
+ gpointer data;
+ GtkWidget *search_dialog;
+ PlumaDocument *doc;
+ gboolean selection_exists;
+ gchar *find_text = NULL;
+ gint sel_len;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ data = g_object_get_data (G_OBJECT (window), PLUMA_SEARCH_DIALOG_KEY);
+
+ if (data == NULL)
+ {
+ search_dialog = create_dialog (window, FALSE);
+ }
+ else
+ {
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (data));
+
+ search_dialog = GTK_WIDGET (data);
+
+ /* turn the dialog into a find dialog if needed */
+ if (pluma_search_dialog_get_show_replace (PLUMA_SEARCH_DIALOG (search_dialog)))
+ pluma_search_dialog_set_show_replace (PLUMA_SEARCH_DIALOG (search_dialog),
+ FALSE);
+ }
+
+ doc = pluma_window_get_active_document (window);
+ g_return_if_fail (doc != NULL);
+
+ selection_exists = get_selected_text (GTK_TEXT_BUFFER (doc),
+ &find_text,
+ &sel_len);
+
+ if (selection_exists && find_text != NULL && sel_len < 80)
+ {
+ pluma_search_dialog_set_search_text (PLUMA_SEARCH_DIALOG (search_dialog),
+ find_text);
+ g_free (find_text);
+ }
+ else
+ {
+ g_free (find_text);
+ }
+
+ gtk_widget_show (search_dialog);
+ last_search_data_restore_position (PLUMA_SEARCH_DIALOG (search_dialog));
+ pluma_search_dialog_present_with_time (PLUMA_SEARCH_DIALOG (search_dialog),
+ GDK_CURRENT_TIME);
+}
+
+void
+_pluma_cmd_search_replace (GtkAction *action,
+ PlumaWindow *window)
+{
+ gpointer data;
+ GtkWidget *replace_dialog;
+ PlumaDocument *doc;
+ gboolean selection_exists;
+ gchar *find_text = NULL;
+ gint sel_len;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ data = g_object_get_data (G_OBJECT (window), PLUMA_SEARCH_DIALOG_KEY);
+
+ if (data == NULL)
+ {
+ replace_dialog = create_dialog (window, TRUE);
+ }
+ else
+ {
+ g_return_if_fail (PLUMA_IS_SEARCH_DIALOG (data));
+
+ replace_dialog = GTK_WIDGET (data);
+
+ /* turn the dialog into a find dialog if needed */
+ if (!pluma_search_dialog_get_show_replace (PLUMA_SEARCH_DIALOG (replace_dialog)))
+ pluma_search_dialog_set_show_replace (PLUMA_SEARCH_DIALOG (replace_dialog),
+ TRUE);
+ }
+
+ doc = pluma_window_get_active_document (window);
+ g_return_if_fail (doc != NULL);
+
+ selection_exists = get_selected_text (GTK_TEXT_BUFFER (doc),
+ &find_text,
+ &sel_len);
+
+ if (selection_exists && find_text != NULL && sel_len < 80)
+ {
+ pluma_search_dialog_set_search_text (PLUMA_SEARCH_DIALOG (replace_dialog),
+ find_text);
+ g_free (find_text);
+ }
+ else
+ {
+ g_free (find_text);
+ }
+
+ gtk_widget_show (replace_dialog);
+ last_search_data_restore_position (PLUMA_SEARCH_DIALOG (replace_dialog));
+ pluma_search_dialog_present_with_time (PLUMA_SEARCH_DIALOG (replace_dialog),
+ GDK_CURRENT_TIME);
+}
+
+static void
+do_find_again (PlumaWindow *window,
+ gboolean backward)
+{
+ PlumaView *active_view;
+ gboolean wrap_around = TRUE;
+ gpointer data;
+
+ active_view = pluma_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ data = g_object_get_data (G_OBJECT (window), PLUMA_SEARCH_DIALOG_KEY);
+
+ if (data != NULL)
+ wrap_around = pluma_search_dialog_get_wrap_around (PLUMA_SEARCH_DIALOG (data));
+
+ run_search (active_view,
+ wrap_around,
+ backward);
+}
+
+void
+_pluma_cmd_search_find_next (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ do_find_again (window, FALSE);
+}
+
+void
+_pluma_cmd_search_find_prev (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ do_find_again (window, TRUE);
+}
+
+void
+_pluma_cmd_search_clear_highlight (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaDocument *doc;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ doc = pluma_window_get_active_document (window);
+ pluma_document_set_search_text (PLUMA_DOCUMENT (doc),
+ "",
+ PLUMA_SEARCH_DONT_SET_FLAGS);
+}
+
+void
+_pluma_cmd_search_goto_line (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ if (active_view == NULL)
+ return;
+
+ /* Focus the view if needed: we need to focus the view otherwise
+ activating the binding for goto line has no effect */
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+
+
+ /* goto line is builtin in PlumaView, just activate
+ * the corrisponding binding.
+ */
+ gtk_bindings_activate (GTK_OBJECT (active_view),
+ GDK_i,
+ GDK_CONTROL_MASK);
+}
+
+void
+_pluma_cmd_search_incremental_search (GtkAction *action,
+ PlumaWindow *window)
+{
+ PlumaView *active_view;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ active_view = pluma_window_get_active_view (window);
+ if (active_view == NULL)
+ return;
+
+ /* Focus the view if needed: we need to focus the view otherwise
+ activating the binding for incremental search has no effect */
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+
+ /* incremental search is builtin in PlumaView, just activate
+ * the corrisponding binding.
+ */
+ gtk_bindings_activate (GTK_OBJECT (active_view),
+ GDK_k,
+ GDK_CONTROL_MASK);
+}
diff --git a/pluma/pluma-commands-view.c b/pluma/pluma-commands-view.c
new file mode 100755
index 00000000..2e5cfd66
--- /dev/null
+++ b/pluma/pluma-commands-view.c
@@ -0,0 +1,154 @@
+/*
+ * pluma-view-commands.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "pluma-commands.h"
+#include "pluma-debug.h"
+#include "pluma-window.h"
+#include "pluma-window-private.h"
+
+
+void
+_pluma_cmd_view_show_toolbar (GtkAction *action,
+ PlumaWindow *window)
+{
+ gboolean visible;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ if (visible)
+ gtk_widget_show (window->priv->toolbar);
+ else
+ gtk_widget_hide (window->priv->toolbar);
+}
+
+void
+_pluma_cmd_view_show_statusbar (GtkAction *action,
+ PlumaWindow *window)
+{
+ gboolean visible;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ if (visible)
+ gtk_widget_show (window->priv->statusbar);
+ else
+ gtk_widget_hide (window->priv->statusbar);
+}
+
+void
+_pluma_cmd_view_show_side_pane (GtkAction *action,
+ PlumaWindow *window)
+{
+ gboolean visible;
+ PlumaPanel *panel;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ panel = pluma_window_get_side_panel (window);
+
+ if (visible)
+ {
+ gtk_widget_show (GTK_WIDGET (panel));
+ gtk_widget_grab_focus (GTK_WIDGET (panel));
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (panel));
+ }
+}
+
+void
+_pluma_cmd_view_show_bottom_pane (GtkAction *action,
+ PlumaWindow *window)
+{
+ gboolean visible;
+ PlumaPanel *panel;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ panel = pluma_window_get_bottom_panel (window);
+
+ if (visible)
+ {
+ gtk_widget_show (GTK_WIDGET (panel));
+ gtk_widget_grab_focus (GTK_WIDGET (panel));
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (panel));
+ }
+}
+
+void
+_pluma_cmd_view_toggle_fullscreen_mode (GtkAction *action,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_COMMANDS);
+
+ if (_pluma_window_is_fullscreen (window))
+ _pluma_window_unfullscreen (window);
+ else
+ _pluma_window_fullscreen (window);
+}
+
+void
+_pluma_cmd_view_leave_fullscreen_mode (GtkAction *action,
+ PlumaWindow *window)
+{
+ GtkAction *view_action;
+
+ view_action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "ViewFullscreen");
+ g_signal_handlers_block_by_func
+ (view_action, G_CALLBACK (_pluma_cmd_view_toggle_fullscreen_mode),
+ window);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (view_action),
+ FALSE);
+ _pluma_window_unfullscreen (window);
+ g_signal_handlers_unblock_by_func
+ (view_action, G_CALLBACK (_pluma_cmd_view_toggle_fullscreen_mode),
+ window);
+}
diff --git a/pluma/pluma-commands.h b/pluma/pluma-commands.h
new file mode 100755
index 00000000..92552b22
--- /dev/null
+++ b/pluma/pluma-commands.h
@@ -0,0 +1,166 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-commands.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_COMMANDS_H__
+#define __PLUMA_COMMANDS_H__
+
+#include <gtk/gtk.h>
+#include <pluma/pluma-window.h>
+
+G_BEGIN_DECLS
+
+/* Do nothing if URI does not exist */
+void pluma_commands_load_uri (PlumaWindow *window,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos);
+
+/* Ignore non-existing URIs */
+gint pluma_commands_load_uris (PlumaWindow *window,
+ const GSList *uris,
+ const PlumaEncoding *encoding,
+ gint line_pos);
+
+void pluma_commands_save_document (PlumaWindow *window,
+ PlumaDocument *document);
+
+void pluma_commands_save_all_documents (PlumaWindow *window);
+
+/*
+ * Non-exported functions
+ */
+
+/* Create titled documens for non-existing URIs */
+gint _pluma_cmd_load_files_from_prompt (PlumaWindow *window,
+ GSList *files,
+ const PlumaEncoding *encoding,
+ gint line_pos);
+
+void _pluma_cmd_file_new (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_open (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_save (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_save_as (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_save_all (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_revert (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_open_uri (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_print_preview (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_print (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_close (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_close_all (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_file_quit (GtkAction *action,
+ PlumaWindow *window);
+
+void _pluma_cmd_edit_undo (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_edit_redo (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_edit_cut (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_edit_copy (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_edit_paste (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_edit_delete (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_edit_select_all (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_edit_preferences (GtkAction *action,
+ PlumaWindow *window);
+
+void _pluma_cmd_view_show_toolbar (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_view_show_statusbar (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_view_show_side_pane (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_view_show_bottom_pane (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_view_toggle_fullscreen_mode (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_view_leave_fullscreen_mode (GtkAction *action,
+ PlumaWindow *window);
+
+void _pluma_cmd_search_find (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_search_find_next (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_search_find_prev (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_search_replace (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_search_clear_highlight (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_search_goto_line (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_search_incremental_search (GtkAction *action,
+ PlumaWindow *window);
+
+void _pluma_cmd_documents_previous_document (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_documents_next_document (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_documents_move_to_new_window (GtkAction *action,
+ PlumaWindow *window);
+
+void _pluma_cmd_help_contents (GtkAction *action,
+ PlumaWindow *window);
+void _pluma_cmd_help_about (GtkAction *action,
+ PlumaWindow *window);
+
+void _pluma_cmd_file_close_tab (PlumaTab *tab,
+ PlumaWindow *window);
+
+void _pluma_cmd_file_save_documents_list (PlumaWindow *window,
+ GList *docs);
+
+
+#if !GTK_CHECK_VERSION (2, 17, 4)
+void _pluma_cmd_file_page_setup (GtkAction *action,
+ PlumaWindow *window);
+#endif
+
+
+G_END_DECLS
+
+#endif /* __PLUMA_COMMANDS_H__ */
diff --git a/pluma/pluma-debug.c b/pluma/pluma-debug.c
new file mode 100755
index 00000000..ecb9d751
--- /dev/null
+++ b/pluma/pluma-debug.c
@@ -0,0 +1,159 @@
+/*
+ * pluma-debug.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002 - 2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include "pluma-debug.h"
+
+#define ENABLE_PROFILING
+
+#ifdef ENABLE_PROFILING
+static GTimer *timer = NULL;
+static gdouble last = 0.0;
+#endif
+
+static PlumaDebugSection debug = PLUMA_NO_DEBUG;
+
+void
+pluma_debug_init (void)
+{
+ if (g_getenv ("PLUMA_DEBUG") != NULL)
+ {
+ /* enable all debugging */
+ debug = ~PLUMA_NO_DEBUG;
+ goto out;
+ }
+
+ if (g_getenv ("PLUMA_DEBUG_VIEW") != NULL)
+ debug = debug | PLUMA_DEBUG_VIEW;
+ if (g_getenv ("PLUMA_DEBUG_SEARCH") != NULL)
+ debug = debug | PLUMA_DEBUG_SEARCH;
+ if (g_getenv ("PLUMA_DEBUG_PREFS") != NULL)
+ debug = debug | PLUMA_DEBUG_PREFS;
+ if (g_getenv ("PLUMA_DEBUG_PRINT") != NULL)
+ debug = debug | PLUMA_DEBUG_PRINT;
+ if (g_getenv ("PLUMA_DEBUG_PLUGINS") != NULL)
+ debug = debug | PLUMA_DEBUG_PLUGINS;
+ if (g_getenv ("PLUMA_DEBUG_TAB") != NULL)
+ debug = debug | PLUMA_DEBUG_TAB;
+ if (g_getenv ("PLUMA_DEBUG_DOCUMENT") != NULL)
+ debug = debug | PLUMA_DEBUG_DOCUMENT;
+ if (g_getenv ("PLUMA_DEBUG_COMMANDS") != NULL)
+ debug = debug | PLUMA_DEBUG_COMMANDS;
+ if (g_getenv ("PLUMA_DEBUG_APP") != NULL)
+ debug = debug | PLUMA_DEBUG_APP;
+ if (g_getenv ("PLUMA_DEBUG_SESSION") != NULL)
+ debug = debug | PLUMA_DEBUG_SESSION;
+ if (g_getenv ("PLUMA_DEBUG_UTILS") != NULL)
+ debug = debug | PLUMA_DEBUG_UTILS;
+ if (g_getenv ("PLUMA_DEBUG_METADATA") != NULL)
+ debug = debug | PLUMA_DEBUG_METADATA;
+ if (g_getenv ("PLUMA_DEBUG_WINDOW") != NULL)
+ debug = debug | PLUMA_DEBUG_WINDOW;
+ if (g_getenv ("PLUMA_DEBUG_LOADER") != NULL)
+ debug = debug | PLUMA_DEBUG_LOADER;
+ if (g_getenv ("PLUMA_DEBUG_SAVER") != NULL)
+ debug = debug | PLUMA_DEBUG_SAVER;
+
+out:
+
+#ifdef ENABLE_PROFILING
+ if (debug != PLUMA_NO_DEBUG)
+ timer = g_timer_new ();
+#endif
+ return;
+}
+
+void
+pluma_debug_message (PlumaDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function,
+ const gchar *format, ...)
+{
+ if (G_UNLIKELY (debug & section))
+ {
+#ifdef ENABLE_PROFILING
+ gdouble seconds;
+#endif
+
+ va_list args;
+ gchar *msg;
+
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ msg = g_strdup_vprintf (format, args);
+ va_end (args);
+
+#ifdef ENABLE_PROFILING
+ g_return_if_fail (timer != NULL);
+
+ seconds = g_timer_elapsed (timer, NULL);
+ g_print ("[%f (%f)] %s:%d (%s) %s\n",
+ seconds, seconds - last, file, line, function, msg);
+ last = seconds;
+#else
+ g_print ("%s:%d (%s) %s\n", file, line, function, msg);
+#endif
+
+ fflush (stdout);
+
+ g_free (msg);
+ }
+}
+
+void pluma_debug (PlumaDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function)
+{
+ if (G_UNLIKELY (debug & section))
+ {
+#ifdef ENABLE_PROFILING
+ gdouble seconds;
+
+ g_return_if_fail (timer != NULL);
+
+ seconds = g_timer_elapsed (timer, NULL);
+ g_print ("[%f (%f)] %s:%d (%s)\n",
+ seconds, seconds - last, file, line, function);
+ last = seconds;
+#else
+ g_print ("%s:%d (%s)\n", file, line, function);
+#endif
+ fflush (stdout);
+ }
+}
diff --git a/pluma/pluma-debug.h b/pluma/pluma-debug.h
new file mode 100755
index 00000000..d86432db
--- /dev/null
+++ b/pluma/pluma-debug.h
@@ -0,0 +1,93 @@
+/*
+ * pluma-debug.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002 - 2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_DEBUG_H__
+#define __PLUMA_DEBUG_H__
+
+#include <glib.h>
+
+/*
+ * Set an environmental var of the same name to turn on
+ * debugging output. Setting PLUMA_DEBUG will turn on all
+ * sections.
+ */
+typedef enum {
+ PLUMA_NO_DEBUG = 0,
+ PLUMA_DEBUG_VIEW = 1 << 0,
+ PLUMA_DEBUG_SEARCH = 1 << 1,
+ PLUMA_DEBUG_PRINT = 1 << 2,
+ PLUMA_DEBUG_PREFS = 1 << 3,
+ PLUMA_DEBUG_PLUGINS = 1 << 4,
+ PLUMA_DEBUG_TAB = 1 << 5,
+ PLUMA_DEBUG_DOCUMENT = 1 << 6,
+ PLUMA_DEBUG_COMMANDS = 1 << 7,
+ PLUMA_DEBUG_APP = 1 << 8,
+ PLUMA_DEBUG_SESSION = 1 << 9,
+ PLUMA_DEBUG_UTILS = 1 << 10,
+ PLUMA_DEBUG_METADATA = 1 << 11,
+ PLUMA_DEBUG_WINDOW = 1 << 12,
+ PLUMA_DEBUG_LOADER = 1 << 13,
+ PLUMA_DEBUG_SAVER = 1 << 14
+} PlumaDebugSection;
+
+
+#define DEBUG_VIEW PLUMA_DEBUG_VIEW, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_SEARCH PLUMA_DEBUG_SEARCH, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_PRINT PLUMA_DEBUG_PRINT, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_PREFS PLUMA_DEBUG_PREFS, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_PLUGINS PLUMA_DEBUG_PLUGINS, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_TAB PLUMA_DEBUG_TAB, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_DOCUMENT PLUMA_DEBUG_DOCUMENT,__FILE__, __LINE__, G_STRFUNC
+#define DEBUG_COMMANDS PLUMA_DEBUG_COMMANDS,__FILE__, __LINE__, G_STRFUNC
+#define DEBUG_APP PLUMA_DEBUG_APP, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_SESSION PLUMA_DEBUG_SESSION, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_UTILS PLUMA_DEBUG_UTILS, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_METADATA PLUMA_DEBUG_METADATA,__FILE__, __LINE__, G_STRFUNC
+#define DEBUG_WINDOW PLUMA_DEBUG_WINDOW, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_LOADER PLUMA_DEBUG_LOADER, __FILE__, __LINE__, G_STRFUNC
+#define DEBUG_SAVER PLUMA_DEBUG_SAVER, __FILE__, __LINE__, G_STRFUNC
+
+void pluma_debug_init (void);
+
+void pluma_debug (PlumaDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function);
+
+void pluma_debug_message (PlumaDebugSection section,
+ const gchar *file,
+ gint line,
+ const gchar *function,
+ const gchar *format, ...) G_GNUC_PRINTF(5, 6);
+
+
+#endif /* __PLUMA_DEBUG_H__ */
diff --git a/pluma/pluma-dirs.c b/pluma/pluma-dirs.c
new file mode 100755
index 00000000..643b26dc
--- /dev/null
+++ b/pluma/pluma-dirs.c
@@ -0,0 +1,320 @@
+/*
+ * pluma-dirs.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2008 Ignacio Casal Quinteiro
+ *
+ * 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 "pluma-dirs.h"
+
+#ifdef OS_OSX
+#include <ige-mac-bundle.h>
+#endif
+
+gchar *
+pluma_dirs_get_user_config_dir (void)
+{
+ gchar *config_dir = NULL;
+
+#ifndef G_OS_WIN32
+ const gchar *envvar;
+ const gchar *home;
+
+ /* Support old libmate env var */
+ envvar = g_getenv ("MATE22_USER_DIR");
+ if (envvar != NULL)
+ {
+ config_dir = g_build_filename (envvar,
+ "pluma",
+ NULL);
+
+ }
+ else
+ {
+ home = g_get_home_dir ();
+
+ if (home != NULL)
+ {
+ config_dir = g_build_filename (home,
+ ".mate2",
+ "pluma",
+ NULL);
+ }
+ }
+#else
+ config_dir = g_build_filename (g_get_user_config_dir (),
+ "pluma",
+ NULL);
+#endif
+
+ return config_dir;
+}
+
+gchar *
+pluma_dirs_get_user_cache_dir (void)
+{
+ const gchar *cache_dir;
+
+ cache_dir = g_get_user_cache_dir ();
+
+ return g_build_filename (cache_dir,
+ "pluma",
+ NULL);
+}
+
+gchar *
+pluma_dirs_get_user_plugins_dir (void)
+{
+ gchar *config_dir;
+ gchar *plugin_dir;
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+
+ plugin_dir = g_build_filename (config_dir,
+ "plugins",
+ NULL);
+ g_free (config_dir);
+
+ return plugin_dir;
+}
+
+gchar *
+pluma_dirs_get_user_accels_file (void)
+{
+ gchar *accels = NULL;
+
+#ifndef G_OS_WIN32
+ const gchar *envvar;
+ const gchar *home;
+
+ /* on linux accels are stored in .mate2/accels
+ * for historic reasons (backward compat with the
+ * old libmate that took care of saving them */
+
+ /* Support old libmate env var */
+ envvar = g_getenv ("MATE22_USER_DIR");
+ if (envvar != NULL)
+ {
+ accels = g_build_filename (envvar,
+ "accels",
+ "pluma",
+ NULL);
+ }
+ else
+ {
+ home = g_get_home_dir ();
+
+ if (home != NULL)
+ {
+ accels = g_build_filename (home,
+ ".mate2",
+ "accels",
+ "pluma",
+ NULL);
+ }
+ }
+#else
+ {
+ gchar *config_dir = NULL;
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+ accels = g_build_filename (config_dir,
+ "accels",
+ "pluma",
+ NULL);
+
+ g_free (config_dir);
+ }
+#endif
+
+ return accels;
+}
+
+gchar *
+pluma_dirs_get_pluma_data_dir (void)
+{
+ gchar *data_dir;
+
+#ifdef G_OS_WIN32
+ gchar *win32_dir;
+
+ win32_dir = g_win32_get_package_installation_directory_of_module (NULL);
+
+ data_dir = g_build_filename (win32_dir,
+ "share",
+ "pluma-2",
+ NULL);
+
+ g_free (win32_dir);
+#elif defined (OS_OSX)
+ IgeMacBundle *bundle = ige_mac_bundle_get_default ();
+
+ if (ige_mac_bundle_get_is_app_bundle (bundle))
+ {
+ const gchar *bundle_data_dir = ige_mac_bundle_get_datadir (bundle);
+
+ data_dir = g_build_filename (bundle_data_dir,
+ "pluma-2",
+ NULL);
+ }
+ else
+ {
+ data_dir = g_build_filename (DATADIR, "pluma-2", NULL);
+ }
+#else
+ data_dir = g_build_filename (DATADIR,
+ "pluma-2",
+ NULL);
+#endif
+
+ return data_dir;
+}
+
+gchar *
+pluma_dirs_get_pluma_locale_dir (void)
+{
+ gchar *locale_dir;
+
+#ifdef G_OS_WIN32
+ gchar *win32_dir;
+
+ win32_dir = g_win32_get_package_installation_directory_of_module (NULL);
+
+ locale_dir = g_build_filename (win32_dir,
+ "share",
+ "locale",
+ NULL);
+
+ g_free (win32_dir);
+#elif defined (OS_OSX)
+ IgeMacBundle *bundle = ige_mac_bundle_get_default ();
+
+ if (ige_mac_bundle_get_is_app_bundle (bundle))
+ {
+ locale_dir = g_strdup (ige_mac_bundle_get_localedir (bundle));
+ }
+ else
+ {
+ locale_dir = g_build_filename (DATADIR,
+ "locale",
+ NULL);
+ }
+#else
+ locale_dir = g_build_filename (DATADIR,
+ "locale",
+ NULL);
+#endif
+
+ return locale_dir;
+}
+
+gchar *
+pluma_dirs_get_pluma_lib_dir (void)
+{
+ gchar *lib_dir;
+
+#ifdef G_OS_WIN32
+ gchar *win32_dir;
+
+ win32_dir = g_win32_get_package_installation_directory_of_module (NULL);
+
+ lib_dir = g_build_filename (win32_dir,
+ "lib",
+ "pluma-2",
+ NULL);
+
+ g_free (win32_dir);
+#elif defined (OS_OSX)
+ IgeMacBundle *bundle = ige_mac_bundle_get_default ();
+
+ if (ige_mac_bundle_get_is_app_bundle (bundle))
+ {
+ const gchar *path = ige_mac_bundle_get_resourcesdir (bundle);
+ lib_dir = g_build_filename (path,
+ "lib",
+ "pluma-2",
+ NULL);
+ }
+ else
+ {
+ lib_dir = g_build_filename (LIBDIR,
+ "pluma-2",
+ NULL);
+ }
+#else
+ lib_dir = g_build_filename (LIBDIR,
+ "pluma-2",
+ NULL);
+#endif
+
+ return lib_dir;
+}
+
+gchar *
+pluma_dirs_get_pluma_plugins_dir (void)
+{
+ gchar *lib_dir;
+ gchar *plugin_dir;
+
+ lib_dir = pluma_dirs_get_pluma_lib_dir ();
+
+ plugin_dir = g_build_filename (lib_dir,
+ "plugins",
+ NULL);
+ g_free (lib_dir);
+
+ return plugin_dir;
+}
+
+gchar *
+pluma_dirs_get_pluma_plugin_loaders_dir (void)
+{
+ gchar *lib_dir;
+ gchar *loader_dir;
+
+ lib_dir = pluma_dirs_get_pluma_lib_dir ();
+
+ loader_dir = g_build_filename (lib_dir,
+ "plugin-loaders",
+ NULL);
+ g_free (lib_dir);
+
+ return loader_dir;
+}
+
+gchar *
+pluma_dirs_get_ui_file (const gchar *file)
+{
+ gchar *datadir;
+ gchar *ui_file;
+
+ g_return_val_if_fail (file != NULL, NULL);
+
+ datadir = pluma_dirs_get_pluma_data_dir ();
+ ui_file = g_build_filename (datadir,
+ "ui",
+ file,
+ NULL);
+ g_free (datadir);
+
+ return ui_file;
+}
diff --git a/pluma/pluma-dirs.h b/pluma/pluma-dirs.h
new file mode 100755
index 00000000..909931fb
--- /dev/null
+++ b/pluma/pluma-dirs.h
@@ -0,0 +1,54 @@
+/*
+ * pluma-dirs.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2008 Ignacio Casal Quinteiro
+ *
+ * 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 __PLUMA_DIRS_H__
+#define __PLUMA_DIRS_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gchar *pluma_dirs_get_user_config_dir (void);
+
+gchar *pluma_dirs_get_user_cache_dir (void);
+
+gchar *pluma_dirs_get_user_plugins_dir (void);
+
+gchar *pluma_dirs_get_user_accels_file (void);
+
+gchar *pluma_dirs_get_pluma_data_dir (void);
+
+gchar *pluma_dirs_get_pluma_locale_dir (void);
+
+gchar *pluma_dirs_get_pluma_lib_dir (void);
+
+gchar *pluma_dirs_get_pluma_plugins_dir (void);
+
+gchar *pluma_dirs_get_pluma_plugin_loaders_dir
+ (void);
+
+gchar *pluma_dirs_get_ui_file (const gchar *file);
+
+G_END_DECLS
+
+#endif /* __PLUMA_DIRS_H__ */
diff --git a/pluma/pluma-document-input-stream.c b/pluma/pluma-document-input-stream.c
new file mode 100755
index 00000000..947386c9
--- /dev/null
+++ b/pluma/pluma-document-input-stream.c
@@ -0,0 +1,479 @@
+/*
+ * pluma-document-input-stream.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * pluma 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.
+ *
+ * pluma 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 pluma; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <string.h>
+#include "pluma-document-input-stream.h"
+#include "pluma-enum-types.h"
+
+/* NOTE: never use async methods on this stream, the stream is just
+ * a wrapper around GtkTextBuffer api so that we can use GIO Stream
+ * methods, but the undelying code operates on a GtkTextBuffer, so
+ * there is no I/O involved and should be accessed only by the main
+ * thread */
+
+
+G_DEFINE_TYPE (PlumaDocumentInputStream, pluma_document_input_stream, G_TYPE_INPUT_STREAM);
+
+struct _PlumaDocumentInputStreamPrivate
+{
+ GtkTextBuffer *buffer;
+ GtkTextMark *pos;
+ gint bytes_partial;
+
+ PlumaDocumentNewlineType newline_type;
+
+ guint newline_added : 1;
+ guint is_initialized : 1;
+};
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER,
+ PROP_NEWLINE_TYPE
+};
+
+static gssize pluma_document_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean pluma_document_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+
+static void
+pluma_document_input_stream_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentInputStream *stream = PLUMA_DOCUMENT_INPUT_STREAM (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUFFER:
+ stream->priv->buffer = GTK_TEXT_BUFFER (g_value_get_object (value));
+ break;
+
+ case PROP_NEWLINE_TYPE:
+ stream->priv->newline_type = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_input_stream_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentInputStream *stream = PLUMA_DOCUMENT_INPUT_STREAM (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_object (value, stream->priv->buffer);
+ break;
+
+ case PROP_NEWLINE_TYPE:
+ g_value_set_enum (value, stream->priv->newline_type);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_input_stream_class_init (PlumaDocumentInputStreamClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (PlumaDocumentInputStreamPrivate));
+
+ gobject_class->get_property = pluma_document_input_stream_get_property;
+ gobject_class->set_property = pluma_document_input_stream_set_property;
+
+ stream_class->read_fn = pluma_document_input_stream_read;
+ stream_class->close_fn = pluma_document_input_stream_close;
+
+ g_object_class_install_property (gobject_class,
+ PROP_BUFFER,
+ g_param_spec_object ("buffer",
+ "Buffer",
+ "The buffer which is read",
+ GTK_TYPE_TEXT_BUFFER,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * PlumaDocumentInputStream:newline-type:
+ *
+ * The :newline-type property determines what is considered
+ * as a line ending when reading complete lines from the stream.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_NEWLINE_TYPE,
+ g_param_spec_enum ("newline-type",
+ "Newline type",
+ "The accepted types of line ending",
+ PLUMA_TYPE_DOCUMENT_NEWLINE_TYPE,
+ PLUMA_DOCUMENT_NEWLINE_TYPE_LF,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+pluma_document_input_stream_init (PlumaDocumentInputStream *stream)
+{
+ stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream,
+ PLUMA_TYPE_DOCUMENT_INPUT_STREAM,
+ PlumaDocumentInputStreamPrivate);
+}
+
+static gsize
+get_new_line_size (PlumaDocumentInputStream *stream)
+{
+ gsize ret;
+
+ switch (stream->priv->newline_type)
+ {
+ case PLUMA_DOCUMENT_NEWLINE_TYPE_CR:
+ case PLUMA_DOCUMENT_NEWLINE_TYPE_LF:
+ ret = 1;
+ break;
+
+ case PLUMA_DOCUMENT_NEWLINE_TYPE_CR_LF:
+ ret = 2;
+ break;
+
+ default:
+ g_warn_if_reached ();
+ ret = 1;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * pluma_document_input_stream_new:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Reads the data from @buffer.
+ *
+ * Returns: a new #GInputStream to read @buffer
+ */
+GInputStream *
+pluma_document_input_stream_new (GtkTextBuffer *buffer,
+ PlumaDocumentNewlineType type)
+{
+ PlumaDocumentInputStream *stream;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+ stream = g_object_new (PLUMA_TYPE_DOCUMENT_INPUT_STREAM,
+ "buffer", buffer,
+ "newline-type", type,
+ NULL);
+
+ return G_INPUT_STREAM (stream);
+}
+
+gsize
+pluma_document_input_stream_get_total_size (PlumaDocumentInputStream *stream)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_INPUT_STREAM (stream), 0);
+
+ return gtk_text_buffer_get_char_count (stream->priv->buffer);
+}
+
+gsize
+pluma_document_input_stream_tell (PlumaDocumentInputStream *stream)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_INPUT_STREAM (stream), 0);
+
+ /* FIXME: is this potentially inefficient? If yes, we could keep
+ track of the offset internally, assuming the mark doesn't move
+ during the operation */
+ if (!stream->priv->is_initialized)
+ {
+ return 0;
+ }
+ else
+ {
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_iter_at_mark (stream->priv->buffer,
+ &iter,
+ stream->priv->pos);
+ return gtk_text_iter_get_offset (&iter);
+ }
+}
+
+static const gchar *
+get_new_line (PlumaDocumentInputStream *stream)
+{
+ const gchar *ret;
+
+ switch (stream->priv->newline_type)
+ {
+ case PLUMA_DOCUMENT_NEWLINE_TYPE_CR:
+ ret = "\r";
+ break;
+
+ case PLUMA_DOCUMENT_NEWLINE_TYPE_LF:
+ ret = "\n";
+ break;
+
+ case PLUMA_DOCUMENT_NEWLINE_TYPE_CR_LF:
+ ret = "\r\n";
+ break;
+
+ default:
+ g_warn_if_reached ();
+ ret = "\n";
+ break;
+ }
+
+ return ret;
+}
+
+static gsize
+read_line (PlumaDocumentInputStream *stream,
+ gchar *outbuf,
+ gsize space_left)
+{
+ GtkTextIter start, next, end;
+ gchar *buf;
+ gint bytes; /* int since it's what iter_get_offset returns */
+ gsize bytes_to_write, newline_size, read;
+ const gchar *newline;
+ gboolean is_last;
+
+ gtk_text_buffer_get_iter_at_mark (stream->priv->buffer,
+ &start,
+ stream->priv->pos);
+
+ if (gtk_text_iter_is_end (&start))
+ return 0;
+
+ end = next = start;
+ newline = get_new_line (stream);
+
+ /* Check needed for empty lines */
+ if (!gtk_text_iter_ends_line (&end))
+ gtk_text_iter_forward_to_line_end (&end);
+
+ gtk_text_iter_forward_line (&next);
+
+ buf = gtk_text_iter_get_slice (&start, &end);
+
+ /* the bytes of a line includes also the newline, so with the
+ offsets we remove the newline and we add the new newline size */
+ bytes = gtk_text_iter_get_bytes_in_line (&start) - stream->priv->bytes_partial;
+
+ /* bytes_in_line includes the newlines, so we remove that assuming that
+ they are single byte characters */
+ bytes = bytes - (gtk_text_iter_get_offset (&next) - gtk_text_iter_get_offset (&end));
+ is_last = gtk_text_iter_is_end (&end);
+
+ /* bytes_to_write contains the amount of bytes we would like to write.
+ This means its the amount of bytes in the line (without the newline
+ in the buffer) + the amount of bytes for the newline we want to
+ write (newline_size) */
+ bytes_to_write = bytes;
+
+ /* do not add the new newline_size for the last line */
+ newline_size = get_new_line_size (stream);
+ if (!is_last)
+ bytes_to_write += newline_size;
+
+ if (bytes_to_write > space_left)
+ {
+ gchar *ptr;
+ gint char_offset;
+ gint written;
+ gsize to_write;
+
+ /* Here the line does not fit in the buffer, we thus write
+ the amount of bytes we can still fit, storing the position
+ for the next read with the mark. Do not try to write the
+ new newline in this case, it will be handled in the next
+ iteration */
+ to_write = MIN (space_left, bytes);
+ ptr = buf;
+ written = 0;
+ char_offset = 0;
+
+ while (written < to_write)
+ {
+ gint w;
+
+ ptr = g_utf8_next_char (ptr);
+ w = (ptr - buf);
+ if (w > to_write)
+ {
+ break;
+ }
+ else
+ {
+ written = w;
+ ++char_offset;
+ }
+ }
+
+ memcpy (outbuf, buf, written);
+
+ /* Note: offset is one past what we wrote */
+ gtk_text_iter_forward_chars (&start, char_offset);
+ stream->priv->bytes_partial += written;
+ read = written;
+ }
+ else
+ {
+ /* First just copy the bytes without the newline */
+ memcpy (outbuf, buf, bytes);
+
+ /* Then add the newline, but not for the last line */
+ if (!is_last)
+ {
+ memcpy (outbuf + bytes, newline, newline_size);
+ }
+
+ start = next;
+ stream->priv->bytes_partial = 0;
+ read = bytes_to_write;
+ }
+
+ gtk_text_buffer_move_mark (stream->priv->buffer,
+ stream->priv->pos,
+ &start);
+
+ g_free (buf);
+ return read;
+}
+
+static gssize
+pluma_document_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ PlumaDocumentInputStream *dstream;
+ GtkTextIter iter;
+ gssize space_left, read, n;
+
+ dstream = PLUMA_DOCUMENT_INPUT_STREAM (stream);
+
+ if (count < 6)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
+ "Not enougth space in destination");
+ return -1;
+ }
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+
+ /* Initialize the mark to the first char in the text buffer */
+ if (!dstream->priv->is_initialized)
+ {
+ gtk_text_buffer_get_start_iter (dstream->priv->buffer, &iter);
+ dstream->priv->pos = gtk_text_buffer_create_mark (dstream->priv->buffer,
+ NULL,
+ &iter,
+ FALSE);
+
+ dstream->priv->is_initialized = TRUE;
+ }
+
+ space_left = count;
+ read = 0;
+
+ do
+ {
+ n = read_line (dstream, buffer + read, space_left);
+ read += n;
+ space_left -= n;
+ } while (space_left > 0 && n != 0 && dstream->priv->bytes_partial == 0);
+
+ /* Make sure that non-empty files are always terminated with \n (see bug #95676).
+ * Note that we strip the trailing \n when loading the file */
+ gtk_text_buffer_get_iter_at_mark (dstream->priv->buffer,
+ &iter,
+ dstream->priv->pos);
+
+ if (gtk_text_iter_is_end (&iter) &&
+ !gtk_text_iter_is_start (&iter))
+ {
+ gssize newline_size;
+
+ newline_size = get_new_line_size (dstream);
+
+ if (space_left >= newline_size &&
+ !dstream->priv->newline_added)
+ {
+ const gchar *newline;
+
+ newline = get_new_line (dstream);
+
+ memcpy (buffer + read, newline, newline_size);
+
+ read += newline_size;
+ dstream->priv->newline_added = TRUE;
+ }
+ }
+
+ return read;
+}
+
+static gboolean
+pluma_document_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ PlumaDocumentInputStream *dstream = PLUMA_DOCUMENT_INPUT_STREAM (stream);
+
+ dstream->priv->newline_added = FALSE;
+
+ if (dstream->priv->is_initialized)
+ {
+ gtk_text_buffer_delete_mark (dstream->priv->buffer, dstream->priv->pos);
+ }
+
+ return TRUE;
+}
diff --git a/pluma/pluma-document-input-stream.h b/pluma/pluma-document-input-stream.h
new file mode 100755
index 00000000..0297c489
--- /dev/null
+++ b/pluma/pluma-document-input-stream.h
@@ -0,0 +1,68 @@
+/*
+ * pluma-document-input-stream.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * pluma 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.
+ *
+ * pluma 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 pluma; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef __PLUMA_DOCUMENT_INPUT_STREAM_H__
+#define __PLUMA_DOCUMENT_INPUT_STREAM_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "pluma-document.h"
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_DOCUMENT_INPUT_STREAM (pluma_document_input_stream_get_type ())
+#define PLUMA_DOCUMENT_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_DOCUMENT_INPUT_STREAM, PlumaDocumentInputStream))
+#define PLUMA_DOCUMENT_INPUT_STREAM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_DOCUMENT_INPUT_STREAM, PlumaDocumentInputStream const))
+#define PLUMA_DOCUMENT_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_DOCUMENT_INPUT_STREAM, PlumaDocumentInputStreamClass))
+#define PLUMA_IS_DOCUMENT_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_DOCUMENT_INPUT_STREAM))
+#define PLUMA_IS_DOCUMENT_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_DOCUMENT_INPUT_STREAM))
+#define PLUMA_DOCUMENT_INPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_DOCUMENT_INPUT_STREAM, PlumaDocumentInputStreamClass))
+
+typedef struct _PlumaDocumentInputStream PlumaDocumentInputStream;
+typedef struct _PlumaDocumentInputStreamClass PlumaDocumentInputStreamClass;
+typedef struct _PlumaDocumentInputStreamPrivate PlumaDocumentInputStreamPrivate;
+
+struct _PlumaDocumentInputStream
+{
+ GInputStream parent;
+
+ PlumaDocumentInputStreamPrivate *priv;
+};
+
+struct _PlumaDocumentInputStreamClass
+{
+ GInputStreamClass parent_class;
+};
+
+GType pluma_document_input_stream_get_type (void) G_GNUC_CONST;
+
+GInputStream *pluma_document_input_stream_new (GtkTextBuffer *buffer,
+ PlumaDocumentNewlineType type);
+
+gsize pluma_document_input_stream_get_total_size (PlumaDocumentInputStream *stream);
+
+gsize pluma_document_input_stream_tell (PlumaDocumentInputStream *stream);
+
+G_END_DECLS
+
+#endif /* __PLUMA_DOCUMENT_INPUT_STREAM_H__ */
diff --git a/pluma/pluma-document-loader.c b/pluma/pluma-document-loader.c
new file mode 100755
index 00000000..3c062c60
--- /dev/null
+++ b/pluma/pluma-document-loader.c
@@ -0,0 +1,357 @@
+/*
+ * pluma-document-loader.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "pluma-document-loader.h"
+#include "pluma-debug.h"
+#include "pluma-metadata-manager.h"
+#include "pluma-utils.h"
+#include "pluma-marshal.h"
+#include "pluma-enum-types.h"
+
+/* Those are for the the pluma_document_loader_new() factory */
+#include "pluma-gio-document-loader.h"
+
+G_DEFINE_ABSTRACT_TYPE(PlumaDocumentLoader, pluma_document_loader, G_TYPE_OBJECT)
+
+/* Signals */
+
+enum {
+ LOADING,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* Properties */
+
+enum
+{
+ PROP_0,
+ PROP_DOCUMENT,
+ PROP_URI,
+ PROP_ENCODING,
+ PROP_NEWLINE_TYPE
+};
+
+static void
+pluma_document_loader_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentLoader *loader = PLUMA_DOCUMENT_LOADER (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ g_return_if_fail (loader->document == NULL);
+ loader->document = g_value_get_object (value);
+ break;
+ case PROP_URI:
+ g_return_if_fail (loader->uri == NULL);
+ loader->uri = g_value_dup_string (value);
+ break;
+ case PROP_ENCODING:
+ g_return_if_fail (loader->encoding == NULL);
+ loader->encoding = g_value_get_boxed (value);
+ break;
+ case PROP_NEWLINE_TYPE:
+ loader->auto_detected_newline_type = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_loader_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentLoader *loader = PLUMA_DOCUMENT_LOADER (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ g_value_set_object (value, loader->document);
+ break;
+ case PROP_URI:
+ g_value_set_string (value, loader->uri);
+ break;
+ case PROP_ENCODING:
+ g_value_set_boxed (value, pluma_document_loader_get_encoding (loader));
+ break;
+ case PROP_NEWLINE_TYPE:
+ g_value_set_enum (value, loader->auto_detected_newline_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_loader_finalize (GObject *object)
+{
+ PlumaDocumentLoader *loader = PLUMA_DOCUMENT_LOADER (object);
+
+ g_free (loader->uri);
+
+ if (loader->info)
+ g_object_unref (loader->info);
+
+ G_OBJECT_CLASS (pluma_document_loader_parent_class)->finalize (object);
+}
+
+static void
+pluma_document_loader_dispose (GObject *object)
+{
+ PlumaDocumentLoader *loader = PLUMA_DOCUMENT_LOADER (object);
+
+ if (loader->info != NULL)
+ {
+ g_object_unref (loader->info);
+ loader->info = NULL;
+ }
+
+ G_OBJECT_CLASS (pluma_document_loader_parent_class)->dispose (object);
+}
+
+static void
+pluma_document_loader_class_init (PlumaDocumentLoaderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_document_loader_finalize;
+ object_class->dispose = pluma_document_loader_dispose;
+ object_class->get_property = pluma_document_loader_get_property;
+ object_class->set_property = pluma_document_loader_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_DOCUMENT,
+ g_param_spec_object ("document",
+ "Document",
+ "The PlumaDocument this PlumaDocumentLoader is associated with",
+ PLUMA_TYPE_DOCUMENT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_URI,
+ g_param_spec_string ("uri",
+ "URI",
+ "The URI this PlumaDocumentLoader loads the document from",
+ "",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_ENCODING,
+ g_param_spec_boxed ("encoding",
+ "Encoding",
+ "The encoding of the saved file",
+ PLUMA_TYPE_ENCODING,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_NEWLINE_TYPE,
+ g_param_spec_enum ("newline-type",
+ "Newline type",
+ "The accepted types of line ending",
+ PLUMA_TYPE_DOCUMENT_NEWLINE_TYPE,
+ PLUMA_DOCUMENT_NEWLINE_TYPE_LF,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB));
+
+ signals[LOADING] =
+ g_signal_new ("loading",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentLoaderClass, loading),
+ NULL, NULL,
+ pluma_marshal_VOID__BOOLEAN_POINTER,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_BOOLEAN,
+ G_TYPE_POINTER);
+}
+
+static void
+pluma_document_loader_init (PlumaDocumentLoader *loader)
+{
+ loader->used = FALSE;
+ loader->auto_detected_newline_type = PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT;
+}
+
+void
+pluma_document_loader_loading (PlumaDocumentLoader *loader,
+ gboolean completed,
+ GError *error)
+{
+ /* the object will be unrefed in the callback of the loading signal
+ * (when completed == TRUE), so we need to prevent finalization.
+ */
+ if (completed)
+ {
+ g_object_ref (loader);
+ }
+
+ g_signal_emit (loader, signals[LOADING], 0, completed, error);
+
+ if (completed)
+ {
+ if (error == NULL)
+ pluma_debug_message (DEBUG_LOADER, "load completed");
+ else
+ pluma_debug_message (DEBUG_LOADER, "load failed");
+
+ g_object_unref (loader);
+ }
+}
+
+/* This is a factory method that returns an appopriate loader
+ * for the given uri.
+ */
+PlumaDocumentLoader *
+pluma_document_loader_new (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding)
+{
+ PlumaDocumentLoader *loader;
+ GType loader_type;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ /* At the moment we just use gio loader in all cases...
+ * In the future it would be great to have a PolicyKit
+ * loader to get permission to save systen files etc */
+ loader_type = PLUMA_TYPE_GIO_DOCUMENT_LOADER;
+
+ loader = PLUMA_DOCUMENT_LOADER (g_object_new (loader_type,
+ "document", doc,
+ "uri", uri,
+ "encoding", encoding,
+ NULL));
+
+ return loader;
+}
+
+/* If enconding == NULL, the encoding will be autodetected */
+void
+pluma_document_loader_load (PlumaDocumentLoader *loader)
+{
+ pluma_debug (DEBUG_LOADER);
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader));
+
+ /* the loader can be used just once, then it must be thrown away */
+ g_return_if_fail (loader->used == FALSE);
+ loader->used = TRUE;
+
+ PLUMA_DOCUMENT_LOADER_GET_CLASS (loader)->load (loader);
+}
+
+gboolean
+pluma_document_loader_cancel (PlumaDocumentLoader *loader)
+{
+ pluma_debug (DEBUG_LOADER);
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader), FALSE);
+
+ return PLUMA_DOCUMENT_LOADER_GET_CLASS (loader)->cancel (loader);
+}
+
+PlumaDocument *
+pluma_document_loader_get_document (PlumaDocumentLoader *loader)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader), NULL);
+
+ return loader->document;
+}
+
+/* Returns STDIN_URI if loading from stdin */
+const gchar *
+pluma_document_loader_get_uri (PlumaDocumentLoader *loader)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader), NULL);
+
+ return loader->uri;
+}
+
+goffset
+pluma_document_loader_get_bytes_read (PlumaDocumentLoader *loader)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader), 0);
+
+ return PLUMA_DOCUMENT_LOADER_GET_CLASS (loader)->get_bytes_read (loader);
+}
+
+const PlumaEncoding *
+pluma_document_loader_get_encoding (PlumaDocumentLoader *loader)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader), NULL);
+
+ if (loader->encoding != NULL)
+ return loader->encoding;
+
+ g_return_val_if_fail (loader->auto_detected_encoding != NULL,
+ pluma_encoding_get_current ());
+
+ return loader->auto_detected_encoding;
+}
+
+PlumaDocumentNewlineType
+pluma_document_loader_get_newline_type (PlumaDocumentLoader *loader)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader),
+ PLUMA_DOCUMENT_NEWLINE_TYPE_LF);
+
+ return loader->auto_detected_newline_type;
+}
+
+GFileInfo *
+pluma_document_loader_get_info (PlumaDocumentLoader *loader)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_LOADER (loader), NULL);
+
+ return loader->info;
+}
diff --git a/pluma/pluma-document-loader.h b/pluma/pluma-document-loader.h
new file mode 100755
index 00000000..74e2b7e2
--- /dev/null
+++ b/pluma/pluma-document-loader.h
@@ -0,0 +1,130 @@
+/*
+ * pluma-document-loader.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_DOCUMENT_LOADER_H__
+#define __PLUMA_DOCUMENT_LOADER_H__
+
+#include <pluma/pluma-document.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_DOCUMENT_LOADER (pluma_document_loader_get_type())
+#define PLUMA_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_DOCUMENT_LOADER, PlumaDocumentLoader))
+#define PLUMA_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_DOCUMENT_LOADER, PlumaDocumentLoaderClass))
+#define PLUMA_IS_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_DOCUMENT_LOADER))
+#define PLUMA_IS_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_DOCUMENT_LOADER))
+#define PLUMA_DOCUMENT_LOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_DOCUMENT_LOADER, PlumaDocumentLoaderClass))
+
+/* Private structure type */
+typedef struct _PlumaDocumentLoaderPrivate PlumaDocumentLoaderPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaDocumentLoader PlumaDocumentLoader;
+
+struct _PlumaDocumentLoader
+{
+ GObject object;
+
+ PlumaDocument *document;
+ gboolean used;
+
+ /* Info on the current file */
+ GFileInfo *info;
+ gchar *uri;
+ const PlumaEncoding *encoding;
+ const PlumaEncoding *auto_detected_encoding;
+ PlumaDocumentNewlineType auto_detected_newline_type;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaDocumentLoaderClass PlumaDocumentLoaderClass;
+
+struct _PlumaDocumentLoaderClass
+{
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (* loading) (PlumaDocumentLoader *loader,
+ gboolean completed,
+ const GError *error);
+
+ /* VTable */
+ void (* load) (PlumaDocumentLoader *loader);
+ gboolean (* cancel) (PlumaDocumentLoader *loader);
+ goffset (* get_bytes_read) (PlumaDocumentLoader *loader);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_document_loader_get_type (void) G_GNUC_CONST;
+
+/* If enconding == NULL, the encoding will be autodetected */
+PlumaDocumentLoader *pluma_document_loader_new (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding);
+
+void pluma_document_loader_loading (PlumaDocumentLoader *loader,
+ gboolean completed,
+ GError *error);
+
+void pluma_document_loader_load (PlumaDocumentLoader *loader);
+#if 0
+gboolean pluma_document_loader_load_from_stdin (PlumaDocumentLoader *loader);
+#endif
+gboolean pluma_document_loader_cancel (PlumaDocumentLoader *loader);
+
+PlumaDocument *pluma_document_loader_get_document (PlumaDocumentLoader *loader);
+
+/* Returns STDIN_URI if loading from stdin */
+#define STDIN_URI "stdin:"
+const gchar *pluma_document_loader_get_uri (PlumaDocumentLoader *loader);
+
+const PlumaEncoding *pluma_document_loader_get_encoding (PlumaDocumentLoader *loader);
+
+PlumaDocumentNewlineType pluma_document_loader_get_newline_type (PlumaDocumentLoader *loader);
+
+goffset pluma_document_loader_get_bytes_read (PlumaDocumentLoader *loader);
+
+/* You can get from the info: content_type, time_modified, standard_size, access_can_write
+ and also the metadata*/
+GFileInfo *pluma_document_loader_get_info (PlumaDocumentLoader *loader);
+
+G_END_DECLS
+
+#endif /* __PLUMA_DOCUMENT_LOADER_H__ */
diff --git a/pluma/pluma-document-output-stream.c b/pluma/pluma-document-output-stream.c
new file mode 100755
index 00000000..06aa728d
--- /dev/null
+++ b/pluma/pluma-document-output-stream.c
@@ -0,0 +1,391 @@
+/*
+ * pluma-document-output-stream.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * pluma 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.
+ *
+ * pluma 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 pluma; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "pluma-document-output-stream.h"
+
+/* NOTE: never use async methods on this stream, the stream is just
+ * a wrapper around GtkTextBuffer api so that we can use GIO Stream
+ * methods, but the undelying code operates on a GtkTextBuffer, so
+ * there is no I/O involved and should be accessed only by the main
+ * thread */
+
+#define PLUMA_DOCUMENT_OUTPUT_STREAM_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object),\
+ PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM,\
+ PlumaDocumentOutputStreamPrivate))
+
+#define MAX_UNICHAR_LEN 6
+
+struct _PlumaDocumentOutputStreamPrivate
+{
+ PlumaDocument *doc;
+ GtkTextIter pos;
+
+ gchar *buffer;
+ gsize buflen;
+
+ guint is_initialized : 1;
+ guint is_closed : 1;
+};
+
+enum
+{
+ PROP_0,
+ PROP_DOCUMENT
+};
+
+G_DEFINE_TYPE (PlumaDocumentOutputStream, pluma_document_output_stream, G_TYPE_OUTPUT_STREAM)
+
+static gssize pluma_document_output_stream_write (GOutputStream *stream,
+ const void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+
+static gboolean pluma_document_output_stream_close (GOutputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+
+static void
+pluma_document_output_stream_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ stream->priv->doc = PLUMA_DOCUMENT (g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_output_stream_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ g_value_set_object (value, stream->priv->doc);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_output_stream_finalize (GObject *object)
+{
+ PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
+
+ g_free (stream->priv->buffer);
+
+ G_OBJECT_CLASS (pluma_document_output_stream_parent_class)->finalize (object);
+}
+
+static void
+pluma_document_output_stream_constructed (GObject *object)
+{
+ PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
+
+ if (!stream->priv->doc)
+ {
+ g_critical ("This should never happen, a problem happened constructing the Document Output Stream!");
+ return;
+ }
+
+ /* Init the undoable action */
+ gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
+ /* clear the buffer */
+ gtk_text_buffer_set_text (GTK_TEXT_BUFFER (stream->priv->doc),
+ "", 0);
+ gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->doc),
+ FALSE);
+
+ gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
+}
+
+static void
+pluma_document_output_stream_class_init (PlumaDocumentOutputStreamClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
+
+ object_class->get_property = pluma_document_output_stream_get_property;
+ object_class->set_property = pluma_document_output_stream_set_property;
+ object_class->finalize = pluma_document_output_stream_finalize;
+ object_class->constructed = pluma_document_output_stream_constructed;
+
+ stream_class->write_fn = pluma_document_output_stream_write;
+ stream_class->close_fn = pluma_document_output_stream_close;
+
+ g_object_class_install_property (object_class,
+ PROP_DOCUMENT,
+ g_param_spec_object ("document",
+ "Document",
+ "The document which is written",
+ PLUMA_TYPE_DOCUMENT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (object_class, sizeof (PlumaDocumentOutputStreamPrivate));
+}
+
+static void
+pluma_document_output_stream_init (PlumaDocumentOutputStream *stream)
+{
+ stream->priv = PLUMA_DOCUMENT_OUTPUT_STREAM_GET_PRIVATE (stream);
+
+ stream->priv->buffer = NULL;
+ stream->priv->buflen = 0;
+
+ stream->priv->is_initialized = FALSE;
+ stream->priv->is_closed = FALSE;
+}
+
+static PlumaDocumentNewlineType
+get_newline_type (GtkTextIter *end)
+{
+ PlumaDocumentNewlineType res;
+ GtkTextIter copy;
+ gunichar c;
+
+ copy = *end;
+ c = gtk_text_iter_get_char (&copy);
+
+ if (g_unichar_break_type (c) == G_UNICODE_BREAK_CARRIAGE_RETURN)
+ {
+ if (gtk_text_iter_forward_char (&copy) &&
+ g_unichar_break_type (gtk_text_iter_get_char (&copy)) == G_UNICODE_BREAK_LINE_FEED)
+ {
+ res = PLUMA_DOCUMENT_NEWLINE_TYPE_CR_LF;
+ }
+ else
+ {
+ res = PLUMA_DOCUMENT_NEWLINE_TYPE_CR;
+ }
+ }
+ else
+ {
+ res = PLUMA_DOCUMENT_NEWLINE_TYPE_LF;
+ }
+
+ return res;
+}
+
+GOutputStream *
+pluma_document_output_stream_new (PlumaDocument *doc)
+{
+ return G_OUTPUT_STREAM (g_object_new (PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM,
+ "document", doc, NULL));
+}
+
+PlumaDocumentNewlineType
+pluma_document_output_stream_detect_newline_type (PlumaDocumentOutputStream *stream)
+{
+ PlumaDocumentNewlineType type;
+ GtkTextIter iter;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_OUTPUT_STREAM (stream),
+ PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT);
+
+ type = PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT;
+
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (stream->priv->doc),
+ &iter);
+
+ if (gtk_text_iter_ends_line (&iter) || gtk_text_iter_forward_to_line_end (&iter))
+ {
+ type = get_newline_type (&iter);
+ }
+
+ return type;
+}
+
+/* If the last char is a newline, remove it from the buffer (otherwise
+ GtkTextView shows it as an empty line). See bug #324942. */
+static void
+remove_ending_newline (PlumaDocumentOutputStream *stream)
+{
+ GtkTextIter end;
+ GtkTextIter start;
+
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (stream->priv->doc), &end);
+ start = end;
+
+ gtk_text_iter_set_line_offset (&start, 0);
+
+ if (gtk_text_iter_ends_line (&start) &&
+ gtk_text_iter_backward_line (&start))
+ {
+ if (!gtk_text_iter_ends_line (&start))
+ {
+ gtk_text_iter_forward_to_line_end (&start);
+ }
+
+ /* Delete the empty line which is from 'start' to 'end' */
+ gtk_text_buffer_delete (GTK_TEXT_BUFFER (stream->priv->doc),
+ &start,
+ &end);
+ }
+}
+
+static void
+end_append_text_to_document (PlumaDocumentOutputStream *stream)
+{
+ remove_ending_newline (stream);
+
+ gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->doc),
+ FALSE);
+
+ gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
+}
+
+static gssize
+pluma_document_output_stream_write (GOutputStream *stream,
+ const void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ PlumaDocumentOutputStream *ostream;
+ gchar *text;
+ gsize len;
+ gboolean freetext = FALSE;
+ const gchar *end;
+ gsize nvalid;
+ gboolean valid;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+
+ ostream = PLUMA_DOCUMENT_OUTPUT_STREAM (stream);
+
+ if (!ostream->priv->is_initialized)
+ {
+ /* Init the undoable action */
+ gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (ostream->priv->doc));
+
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (ostream->priv->doc),
+ &ostream->priv->pos);
+ ostream->priv->is_initialized = TRUE;
+ }
+
+ if (ostream->priv->buflen > 0)
+ {
+ len = ostream->priv->buflen + count;
+ text = g_new (gchar , len + 1);
+ memcpy (text, ostream->priv->buffer, ostream->priv->buflen);
+ memcpy (text + ostream->priv->buflen, buffer, count);
+ text[len] = '\0';
+ g_free (ostream->priv->buffer);
+ ostream->priv->buffer = NULL;
+ ostream->priv->buflen = 0;
+ freetext = TRUE;
+ }
+ else
+ {
+ text = (gchar *) buffer;
+ len = count;
+ }
+
+ /* validate */
+ valid = g_utf8_validate (text, len, &end);
+ nvalid = end - text;
+
+ if (!valid)
+ {
+ gsize remainder;
+
+ remainder = len - nvalid;
+
+ if ((remainder < MAX_UNICHAR_LEN) &&
+ (g_utf8_get_char_validated (text + nvalid, remainder) == (gunichar)-2))
+ {
+ ostream->priv->buffer = g_strndup (end, remainder);
+ ostream->priv->buflen = remainder;
+ len -= remainder;
+ }
+ else
+ {
+ /* TODO: we cuould escape invalid text and tag it in red
+ * and make the doc readonly.
+ */
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ _("Invalid UTF-8 sequence in input"));
+
+ if (freetext)
+ g_free (text);
+
+ return -1;
+ }
+ }
+
+ gtk_text_buffer_insert (GTK_TEXT_BUFFER (ostream->priv->doc),
+ &ostream->priv->pos, text, len);
+
+ if (freetext)
+ g_free (text);
+
+ return count;
+}
+
+static gboolean
+pluma_document_output_stream_close (GOutputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ PlumaDocumentOutputStream *ostream = PLUMA_DOCUMENT_OUTPUT_STREAM (stream);
+
+ if (!ostream->priv->is_closed && ostream->priv->is_initialized)
+ {
+ end_append_text_to_document (ostream);
+ ostream->priv->is_closed = TRUE;
+ }
+
+ if (ostream->priv->buflen > 0)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ _("Incomplete UTF-8 sequence in input"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/pluma/pluma-document-output-stream.h b/pluma/pluma-document-output-stream.h
new file mode 100755
index 00000000..04071a98
--- /dev/null
+++ b/pluma/pluma-document-output-stream.h
@@ -0,0 +1,64 @@
+/*
+ * pluma-document-output-stream.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * pluma 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.
+ *
+ * pluma 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 pluma; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef __PLUMA_DOCUMENT_OUTPUT_STREAM_H__
+#define __PLUMA_DOCUMENT_OUTPUT_STREAM_H__
+
+#include <gio/gio.h>
+#include "pluma-document.h"
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM (pluma_document_output_stream_get_type ())
+#define PLUMA_DOCUMENT_OUTPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM, PlumaDocumentOutputStream))
+#define PLUMA_DOCUMENT_OUTPUT_STREAM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM, PlumaDocumentOutputStream const))
+#define PLUMA_DOCUMENT_OUTPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM, PlumaDocumentOutputStreamClass))
+#define PLUMA_IS_DOCUMENT_OUTPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM))
+#define PLUMA_IS_DOCUMENT_OUTPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM))
+#define PLUMA_DOCUMENT_OUTPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM, PlumaDocumentOutputStreamClass))
+
+typedef struct _PlumaDocumentOutputStream PlumaDocumentOutputStream;
+typedef struct _PlumaDocumentOutputStreamClass PlumaDocumentOutputStreamClass;
+typedef struct _PlumaDocumentOutputStreamPrivate PlumaDocumentOutputStreamPrivate;
+
+struct _PlumaDocumentOutputStream
+{
+ GOutputStream parent;
+
+ PlumaDocumentOutputStreamPrivate *priv;
+};
+
+struct _PlumaDocumentOutputStreamClass
+{
+ GOutputStreamClass parent_class;
+};
+
+GType pluma_document_output_stream_get_type (void) G_GNUC_CONST;
+
+GOutputStream *pluma_document_output_stream_new (PlumaDocument *doc);
+
+PlumaDocumentNewlineType pluma_document_output_stream_detect_newline_type (PlumaDocumentOutputStream *stream);
+
+G_END_DECLS
+
+#endif /* __PLUMA_DOCUMENT_OUTPUT_STREAM_H__ */
diff --git a/pluma/pluma-document-saver.c b/pluma/pluma-document-saver.c
new file mode 100755
index 00000000..8c0eebc5
--- /dev/null
+++ b/pluma/pluma-document-saver.c
@@ -0,0 +1,359 @@
+/*
+ * pluma-document-saver.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005-2006 - Paolo Borelli and Paolo Maggi
+ * Copyright (C) 2007 - Paolo Borelli, Paolo Maggi, Steve Frécinaux
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+
+#include "pluma-document-saver.h"
+#include "pluma-debug.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-marshal.h"
+#include "pluma-utils.h"
+#include "pluma-enum-types.h"
+#include "pluma-gio-document-saver.h"
+
+G_DEFINE_ABSTRACT_TYPE(PlumaDocumentSaver, pluma_document_saver, G_TYPE_OBJECT)
+
+/* Signals */
+
+enum {
+ SAVING,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* Properties */
+
+enum {
+ PROP_0,
+ PROP_DOCUMENT,
+ PROP_URI,
+ PROP_ENCODING,
+ PROP_NEWLINE_TYPE,
+ PROP_FLAGS
+};
+
+static void
+pluma_document_saver_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentSaver *saver = PLUMA_DOCUMENT_SAVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ g_return_if_fail (saver->document == NULL);
+ saver->document = g_value_get_object (value);
+ break;
+ case PROP_URI:
+ g_return_if_fail (saver->uri == NULL);
+ saver->uri = g_value_dup_string (value);
+ break;
+ case PROP_ENCODING:
+ g_return_if_fail (saver->encoding == NULL);
+ saver->encoding = g_value_get_boxed (value);
+ break;
+ case PROP_NEWLINE_TYPE:
+ saver->newline_type = g_value_get_enum (value);
+ break;
+ case PROP_FLAGS:
+ saver->flags = g_value_get_flags (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_saver_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentSaver *saver = PLUMA_DOCUMENT_SAVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ g_value_set_object (value, saver->document);
+ break;
+ case PROP_URI:
+ g_value_set_string (value, saver->uri);
+ break;
+ case PROP_ENCODING:
+ g_value_set_boxed (value, saver->encoding);
+ break;
+ case PROP_NEWLINE_TYPE:
+ g_value_set_enum (value, saver->newline_type);
+ break;
+ case PROP_FLAGS:
+ g_value_set_flags (value, saver->flags);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_saver_finalize (GObject *object)
+{
+ PlumaDocumentSaver *saver = PLUMA_DOCUMENT_SAVER (object);
+
+ g_free (saver->uri);
+
+ G_OBJECT_CLASS (pluma_document_saver_parent_class)->finalize (object);
+}
+
+static void
+pluma_document_saver_dispose (GObject *object)
+{
+ PlumaDocumentSaver *saver = PLUMA_DOCUMENT_SAVER (object);
+
+ if (saver->info != NULL)
+ {
+ g_object_unref (saver->info);
+ saver->info = NULL;
+ }
+
+ G_OBJECT_CLASS (pluma_document_saver_parent_class)->dispose (object);
+}
+
+static void
+pluma_document_saver_class_init (PlumaDocumentSaverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_document_saver_finalize;
+ object_class->dispose = pluma_document_saver_dispose;
+ object_class->set_property = pluma_document_saver_set_property;
+ object_class->get_property = pluma_document_saver_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_DOCUMENT,
+ g_param_spec_object ("document",
+ "Document",
+ "The PlumaDocument this PlumaDocumentSaver is associated with",
+ PLUMA_TYPE_DOCUMENT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_URI,
+ g_param_spec_string ("uri",
+ "URI",
+ "The URI this PlumaDocumentSaver saves the document to",
+ "",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_ENCODING,
+ g_param_spec_boxed ("encoding",
+ "URI",
+ "The encoding of the saved file",
+ PLUMA_TYPE_ENCODING,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_NEWLINE_TYPE,
+ g_param_spec_enum ("newline-type",
+ "Newline type",
+ "The accepted types of line ending",
+ PLUMA_TYPE_DOCUMENT_NEWLINE_TYPE,
+ PLUMA_DOCUMENT_NEWLINE_TYPE_LF,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_FLAGS,
+ g_param_spec_flags ("flags",
+ "Flags",
+ "The flags for the saving operation",
+ PLUMA_TYPE_DOCUMENT_SAVE_FLAGS,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ signals[SAVING] =
+ g_signal_new ("saving",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentSaverClass, saving),
+ NULL, NULL,
+ pluma_marshal_VOID__BOOLEAN_POINTER,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_BOOLEAN,
+ G_TYPE_POINTER);
+}
+
+static void
+pluma_document_saver_init (PlumaDocumentSaver *saver)
+{
+ saver->used = FALSE;
+}
+
+PlumaDocumentSaver *
+pluma_document_saver_new (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentNewlineType newline_type,
+ PlumaDocumentSaveFlags flags)
+{
+ PlumaDocumentSaver *saver;
+ GType saver_type;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ saver_type = PLUMA_TYPE_GIO_DOCUMENT_SAVER;
+
+ if (encoding == NULL)
+ encoding = pluma_encoding_get_utf8 ();
+
+ saver = PLUMA_DOCUMENT_SAVER (g_object_new (saver_type,
+ "document", doc,
+ "uri", uri,
+ "encoding", encoding,
+ "newline_type", newline_type,
+ "flags", flags,
+ NULL));
+
+ return saver;
+}
+
+void
+pluma_document_saver_saving (PlumaDocumentSaver *saver,
+ gboolean completed,
+ GError *error)
+{
+ /* the object will be unrefed in the callback of the saving
+ * signal, so we need to prevent finalization.
+ */
+ if (completed)
+ {
+ g_object_ref (saver);
+ }
+
+ g_signal_emit (saver, signals[SAVING], 0, completed, error);
+
+ if (completed)
+ {
+ if (error == NULL)
+ pluma_debug_message (DEBUG_SAVER, "save completed");
+ else
+ pluma_debug_message (DEBUG_SAVER, "save failed");
+
+ g_object_unref (saver);
+ }
+}
+
+void
+pluma_document_saver_save (PlumaDocumentSaver *saver,
+ GTimeVal *old_mtime)
+{
+ pluma_debug (DEBUG_SAVER);
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT_SAVER (saver));
+ g_return_if_fail (saver->uri != NULL && strlen (saver->uri) > 0);
+
+ g_return_if_fail (saver->used == FALSE);
+ saver->used = TRUE;
+
+ // CHECK:
+ // - sanity check a max len for the uri?
+ // report async (in an idle handler) or sync (bool ret)
+ // async is extra work here, sync is special casing in the caller
+
+ /* never keep backup of autosaves */
+ if ((saver->flags & PLUMA_DOCUMENT_SAVE_PRESERVE_BACKUP) != 0)
+ saver->keep_backup = FALSE;
+ else
+ saver->keep_backup = pluma_prefs_manager_get_create_backup_copy ();
+
+ PLUMA_DOCUMENT_SAVER_GET_CLASS (saver)->save (saver, old_mtime);
+}
+
+PlumaDocument *
+pluma_document_saver_get_document (PlumaDocumentSaver *saver)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_SAVER (saver), NULL);
+
+ return saver->document;
+}
+
+const gchar *
+pluma_document_saver_get_uri (PlumaDocumentSaver *saver)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_SAVER (saver), NULL);
+
+ return saver->uri;
+}
+
+/* Returns 0 if file size is unknown */
+goffset
+pluma_document_saver_get_file_size (PlumaDocumentSaver *saver)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_SAVER (saver), 0);
+
+ return PLUMA_DOCUMENT_SAVER_GET_CLASS (saver)->get_file_size (saver);
+}
+
+goffset
+pluma_document_saver_get_bytes_written (PlumaDocumentSaver *saver)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_SAVER (saver), 0);
+
+ return PLUMA_DOCUMENT_SAVER_GET_CLASS (saver)->get_bytes_written (saver);
+}
+
+GFileInfo *
+pluma_document_saver_get_info (PlumaDocumentSaver *saver)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT_SAVER (saver), NULL);
+
+ return saver->info;
+}
diff --git a/pluma/pluma-document-saver.h b/pluma/pluma-document-saver.h
new file mode 100755
index 00000000..c9d9f54e
--- /dev/null
+++ b/pluma/pluma-document-saver.h
@@ -0,0 +1,133 @@
+/*
+ * pluma-document-saver.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyrhing (C) 2007 - Paolo Maggi, Steve Frécinaux
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_DOCUMENT_SAVER_H__
+#define __PLUMA_DOCUMENT_SAVER_H__
+
+#include <pluma/pluma-document.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_DOCUMENT_SAVER (pluma_document_saver_get_type())
+#define PLUMA_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_DOCUMENT_SAVER, PlumaDocumentSaver))
+#define PLUMA_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_DOCUMENT_SAVER, PlumaDocumentSaverClass))
+#define PLUMA_IS_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_DOCUMENT_SAVER))
+#define PLUMA_IS_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_DOCUMENT_SAVER))
+#define PLUMA_DOCUMENT_SAVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_DOCUMENT_SAVER, PlumaDocumentSaverClass))
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaDocumentSaver PlumaDocumentSaver;
+
+struct _PlumaDocumentSaver
+{
+ GObject object;
+
+ /*< private >*/
+ GFileInfo *info;
+ PlumaDocument *document;
+ gboolean used;
+
+ gchar *uri;
+ const PlumaEncoding *encoding;
+ PlumaDocumentNewlineType newline_type;
+
+ PlumaDocumentSaveFlags flags;
+
+ gboolean keep_backup;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaDocumentSaverClass PlumaDocumentSaverClass;
+
+struct _PlumaDocumentSaverClass
+{
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (* saving) (PlumaDocumentSaver *saver,
+ gboolean completed,
+ const GError *error);
+
+ /* VTable */
+ void (* save) (PlumaDocumentSaver *saver,
+ GTimeVal *old_mtime);
+ goffset (* get_file_size) (PlumaDocumentSaver *saver);
+ goffset (* get_bytes_written) (PlumaDocumentSaver *saver);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_document_saver_get_type (void) G_GNUC_CONST;
+
+/* If enconding == NULL, the encoding will be autodetected */
+PlumaDocumentSaver *pluma_document_saver_new (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentNewlineType newline_type,
+ PlumaDocumentSaveFlags flags);
+
+void pluma_document_saver_saving (PlumaDocumentSaver *saver,
+ gboolean completed,
+ GError *error);
+void pluma_document_saver_save (PlumaDocumentSaver *saver,
+ GTimeVal *old_mtime);
+
+#if 0
+void pluma_document_saver_cancel (PlumaDocumentSaver *saver);
+#endif
+
+PlumaDocument *pluma_document_saver_get_document (PlumaDocumentSaver *saver);
+
+const gchar *pluma_document_saver_get_uri (PlumaDocumentSaver *saver);
+
+/* If backup_uri is NULL no backup will be made */
+const gchar *pluma_document_saver_get_backup_uri (PlumaDocumentSaver *saver);
+void *pluma_document_saver_set_backup_uri (PlumaDocumentSaver *saver,
+ const gchar *backup_uri);
+
+/* Returns 0 if file size is unknown */
+goffset pluma_document_saver_get_file_size (PlumaDocumentSaver *saver);
+
+goffset pluma_document_saver_get_bytes_written (PlumaDocumentSaver *saver);
+
+GFileInfo *pluma_document_saver_get_info (PlumaDocumentSaver *saver);
+
+G_END_DECLS
+
+#endif /* __PLUMA_DOCUMENT_SAVER_H__ */
diff --git a/pluma/pluma-document.c b/pluma/pluma-document.c
new file mode 100755
index 00000000..2a57b594
--- /dev/null
+++ b/pluma/pluma-document.c
@@ -0,0 +1,2732 @@
+/*
+ * pluma-document.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksourceiter.h>
+
+#include "pluma-prefs-manager-app.h"
+#include "pluma-document.h"
+#include "pluma-debug.h"
+#include "pluma-utils.h"
+#include "pluma-language-manager.h"
+#include "pluma-style-scheme-manager.h"
+#include "pluma-document-loader.h"
+#include "pluma-document-saver.h"
+#include "pluma-marshal.h"
+#include "pluma-enum-types.h"
+#include "plumatextregion.h"
+
+#ifndef ENABLE_GVFS_METADATA
+#include "pluma-metadata-manager.h"
+#else
+#define METADATA_QUERY "metadata::*"
+#endif
+
+#undef ENABLE_PROFILE
+
+#ifdef ENABLE_PROFILE
+#define PROFILE(x) x
+#else
+#define PROFILE(x)
+#endif
+
+PROFILE (static GTimer *timer = NULL)
+
+#ifdef MAXPATHLEN
+#define PLUMA_MAX_PATH_LEN MAXPATHLEN
+#elif defined (PATH_MAX)
+#define PLUMA_MAX_PATH_LEN PATH_MAX
+#else
+#define PLUMA_MAX_PATH_LEN 2048
+#endif
+
+#define PLUMA_DOCUMENT_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_DOCUMENT, PlumaDocumentPrivate))
+
+static void pluma_document_load_real (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create);
+static void pluma_document_save_real (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentSaveFlags flags);
+static void to_search_region_range (PlumaDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end);
+static void insert_text_cb (PlumaDocument *doc,
+ GtkTextIter *pos,
+ const gchar *text,
+ gint length);
+
+static void delete_range_cb (PlumaDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end);
+
+struct _PlumaDocumentPrivate
+{
+ gchar *uri;
+ gint untitled_number;
+ gchar *short_name;
+
+ GFileInfo *metadata_info;
+
+ const PlumaEncoding *encoding;
+
+ gchar *content_type;
+
+ GTimeVal mtime;
+ GTimeVal time_of_last_save_or_load;
+
+ guint search_flags;
+ gchar *search_text;
+ gint num_of_lines_search_text;
+
+ PlumaDocumentNewlineType newline_type;
+
+ /* Temp data while loading */
+ PlumaDocumentLoader *loader;
+ gboolean create; /* Create file if uri points
+ * to a non existing file */
+ const PlumaEncoding *requested_encoding;
+ gint requested_line_pos;
+
+ /* Saving stuff */
+ PlumaDocumentSaver *saver;
+
+ /* Search highlighting support variables */
+ PlumaTextRegion *to_search_region;
+ GtkTextTag *found_tag;
+
+ /* Mount operation factory */
+ PlumaMountOperationFactory mount_operation_factory;
+ gpointer mount_operation_userdata;
+
+ gint readonly : 1;
+ gint last_save_was_manually : 1;
+ gint language_set_by_user : 1;
+ gint stop_cursor_moved_emission : 1;
+ gint dispose_has_run : 1;
+};
+
+enum {
+ PROP_0,
+
+ PROP_URI,
+ PROP_SHORTNAME,
+ PROP_CONTENT_TYPE,
+ PROP_MIME_TYPE,
+ PROP_READ_ONLY,
+ PROP_ENCODING,
+ PROP_CAN_SEARCH_AGAIN,
+ PROP_ENABLE_SEARCH_HIGHLIGHTING,
+ PROP_NEWLINE_TYPE
+};
+
+enum {
+ CURSOR_MOVED,
+ LOAD,
+ LOADING,
+ LOADED,
+ SAVE,
+ SAVING,
+ SAVED,
+ SEARCH_HIGHLIGHT_UPDATED,
+ LAST_SIGNAL
+};
+
+static guint document_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE(PlumaDocument, pluma_document, GTK_TYPE_SOURCE_BUFFER)
+
+GQuark
+pluma_document_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0))
+ quark = g_quark_from_static_string ("pluma_io_load_error");
+
+ return quark;
+}
+
+static GHashTable *allocated_untitled_numbers = NULL;
+
+static gint
+get_untitled_number (void)
+{
+ gint i = 1;
+
+ if (allocated_untitled_numbers == NULL)
+ allocated_untitled_numbers = g_hash_table_new (NULL, NULL);
+
+ g_return_val_if_fail (allocated_untitled_numbers != NULL, -1);
+
+ while (TRUE)
+ {
+ if (g_hash_table_lookup (allocated_untitled_numbers, GINT_TO_POINTER (i)) == NULL)
+ {
+ g_hash_table_insert (allocated_untitled_numbers,
+ GINT_TO_POINTER (i),
+ GINT_TO_POINTER (i));
+
+ return i;
+ }
+
+ ++i;
+ }
+}
+
+static void
+release_untitled_number (gint n)
+{
+ g_return_if_fail (allocated_untitled_numbers != NULL);
+
+ g_hash_table_remove (allocated_untitled_numbers, GINT_TO_POINTER (n));
+}
+
+static void
+pluma_document_dispose (GObject *object)
+{
+ PlumaDocument *doc = PLUMA_DOCUMENT (object);
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ /* Metadata must be saved here and not in finalize
+ * because the language is gone by the time finalize runs.
+ * beside if some plugin prevents proper finalization by
+ * holding a ref to the doc, we still save the metadata */
+ if ((!doc->priv->dispose_has_run) && (doc->priv->uri != NULL))
+ {
+ GtkTextIter iter;
+ gchar *position;
+ const gchar *language = NULL;
+
+ if (doc->priv->language_set_by_user)
+ {
+ GtkSourceLanguage *lang;
+
+ lang = pluma_document_get_language (doc);
+
+ if (lang == NULL)
+ language = "_NORMAL_";
+ else
+ language = gtk_source_language_get_id (lang);
+ }
+
+ gtk_text_buffer_get_iter_at_mark (
+ GTK_TEXT_BUFFER (doc),
+ &iter,
+ gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (doc)));
+
+ position = g_strdup_printf ("%d",
+ gtk_text_iter_get_offset (&iter));
+
+ if (language == NULL)
+ pluma_document_set_metadata (doc, PLUMA_METADATA_ATTRIBUTE_POSITION,
+ position, NULL);
+ else
+ pluma_document_set_metadata (doc, PLUMA_METADATA_ATTRIBUTE_POSITION,
+ position, PLUMA_METADATA_ATTRIBUTE_LANGUAGE,
+ language, NULL);
+ g_free (position);
+ }
+
+ if (doc->priv->loader)
+ {
+ g_object_unref (doc->priv->loader);
+ doc->priv->loader = NULL;
+ }
+
+ if (doc->priv->metadata_info != NULL)
+ {
+ g_object_unref (doc->priv->metadata_info);
+ doc->priv->metadata_info = NULL;
+ }
+
+ doc->priv->dispose_has_run = TRUE;
+
+ G_OBJECT_CLASS (pluma_document_parent_class)->dispose (object);
+}
+
+static void
+pluma_document_finalize (GObject *object)
+{
+ PlumaDocument *doc = PLUMA_DOCUMENT (object);
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ if (doc->priv->untitled_number > 0)
+ {
+ g_return_if_fail (doc->priv->uri == NULL);
+ release_untitled_number (doc->priv->untitled_number);
+ }
+
+ g_free (doc->priv->uri);
+ g_free (doc->priv->content_type);
+ g_free (doc->priv->search_text);
+
+ if (doc->priv->to_search_region != NULL)
+ {
+ /* we can't delete marks if we're finalizing the buffer */
+ pluma_text_region_destroy (doc->priv->to_search_region, FALSE);
+ }
+
+ G_OBJECT_CLASS (pluma_document_parent_class)->finalize (object);
+}
+
+static void
+pluma_document_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocument *doc = PLUMA_DOCUMENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_URI:
+ g_value_set_string (value, doc->priv->uri);
+ break;
+ case PROP_SHORTNAME:
+ g_value_take_string (value, pluma_document_get_short_name_for_display (doc));
+ break;
+ case PROP_CONTENT_TYPE:
+ g_value_take_string (value, pluma_document_get_content_type (doc));
+ break;
+ case PROP_MIME_TYPE:
+ g_value_take_string (value, pluma_document_get_mime_type (doc));
+ break;
+ case PROP_READ_ONLY:
+ g_value_set_boolean (value, doc->priv->readonly);
+ break;
+ case PROP_ENCODING:
+ g_value_set_boxed (value, doc->priv->encoding);
+ break;
+ case PROP_CAN_SEARCH_AGAIN:
+ g_value_set_boolean (value, pluma_document_get_can_search_again (doc));
+ break;
+ case PROP_ENABLE_SEARCH_HIGHLIGHTING:
+ g_value_set_boolean (value, pluma_document_get_enable_search_highlighting (doc));
+ break;
+ case PROP_NEWLINE_TYPE:
+ g_value_set_enum (value, doc->priv->newline_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_document_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocument *doc = PLUMA_DOCUMENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_ENABLE_SEARCH_HIGHLIGHTING:
+ pluma_document_set_enable_search_highlighting (doc,
+ g_value_get_boolean (value));
+ break;
+ case PROP_NEWLINE_TYPE:
+ pluma_document_set_newline_type (doc,
+ g_value_get_enum (value));
+ break;
+ case PROP_SHORTNAME:
+ pluma_document_set_short_name_for_display (doc,
+ g_value_get_string (value));
+ break;
+ case PROP_CONTENT_TYPE:
+ pluma_document_set_content_type (doc,
+ g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+emit_cursor_moved (PlumaDocument *doc)
+{
+ if (!doc->priv->stop_cursor_moved_emission)
+ {
+ g_signal_emit (doc,
+ document_signals[CURSOR_MOVED],
+ 0);
+ }
+}
+
+static void
+pluma_document_mark_set (GtkTextBuffer *buffer,
+ const GtkTextIter *iter,
+ GtkTextMark *mark)
+{
+ PlumaDocument *doc = PLUMA_DOCUMENT (buffer);
+
+ if (GTK_TEXT_BUFFER_CLASS (pluma_document_parent_class)->mark_set)
+ GTK_TEXT_BUFFER_CLASS (pluma_document_parent_class)->mark_set (buffer,
+ iter,
+ mark);
+
+ if (mark == gtk_text_buffer_get_insert (buffer))
+ {
+ emit_cursor_moved (doc);
+ }
+}
+
+static void
+pluma_document_changed (GtkTextBuffer *buffer)
+{
+ emit_cursor_moved (PLUMA_DOCUMENT (buffer));
+
+ GTK_TEXT_BUFFER_CLASS (pluma_document_parent_class)->changed (buffer);
+}
+
+static void
+pluma_document_class_init (PlumaDocumentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkTextBufferClass *buf_class = GTK_TEXT_BUFFER_CLASS (klass);
+
+ object_class->dispose = pluma_document_dispose;
+ object_class->finalize = pluma_document_finalize;
+ object_class->get_property = pluma_document_get_property;
+ object_class->set_property = pluma_document_set_property;
+
+ buf_class->mark_set = pluma_document_mark_set;
+ buf_class->changed = pluma_document_changed;
+
+ klass->load = pluma_document_load_real;
+ klass->save = pluma_document_save_real;
+
+ g_object_class_install_property (object_class, PROP_URI,
+ g_param_spec_string ("uri",
+ "URI",
+ "The document's URI",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SHORTNAME,
+ g_param_spec_string ("shortname",
+ "Short Name",
+ "The document's short name",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_CONTENT_TYPE,
+ g_param_spec_string ("content-type",
+ "Content Type",
+ "The document's Content Type",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_MIME_TYPE,
+ g_param_spec_string ("mime-type",
+ "MIME Type",
+ "The document's MIME Type",
+ "text/plain",
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_READ_ONLY,
+ g_param_spec_boolean ("read-only",
+ "Read Only",
+ "Whether the document is read only or not",
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_ENCODING,
+ g_param_spec_boxed ("encoding",
+ "Encoding",
+ "The PlumaEncoding used for the document",
+ PLUMA_TYPE_ENCODING,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_CAN_SEARCH_AGAIN,
+ g_param_spec_boolean ("can-search-again",
+ "Can search again",
+ "Wheter it's possible to search again in the document",
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_ENABLE_SEARCH_HIGHLIGHTING,
+ g_param_spec_boolean ("enable-search-highlighting",
+ "Enable Search Highlighting",
+ "Whether all the occurences of the searched string must be highlighted",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * PlumaDocument:newline-type:
+ *
+ * The :newline-type property determines what is considered
+ * as a line ending when saving the document
+ */
+ g_object_class_install_property (object_class, PROP_NEWLINE_TYPE,
+ g_param_spec_enum ("newline-type",
+ "Newline type",
+ "The accepted types of line ending",
+ PLUMA_TYPE_DOCUMENT_NEWLINE_TYPE,
+ PLUMA_DOCUMENT_NEWLINE_TYPE_LF,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB));
+
+ /* This signal is used to update the cursor position is the statusbar,
+ * it's emitted either when the insert mark is moved explicitely or
+ * when the buffer changes (insert/delete).
+ * We prevent the emission of the signal during replace_all to
+ * improve performance.
+ */
+ document_signals[CURSOR_MOVED] =
+ g_signal_new ("cursor-moved",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, cursor_moved),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * PlumaDocument::load:
+ * @document: the #PlumaDocument.
+ * @uri: the uri where to load the document from.
+ * @encoding: the #PlumaEncoding to encode the document.
+ * @line_pos: the line to show.
+ * @create: whether the document should be created if it doesn't exist.
+ *
+ * The "load" signal is emitted when a document is loaded.
+ *
+ * Since: 2.22
+ */
+ document_signals[LOAD] =
+ g_signal_new ("load",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, load),
+ NULL, NULL,
+ pluma_marshal_VOID__STRING_BOXED_INT_BOOLEAN,
+ G_TYPE_NONE,
+ 4,
+ G_TYPE_STRING,
+ /* we rely on the fact that the PlumaEncoding pointer stays
+ * the same forever */
+ PLUMA_TYPE_ENCODING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_INT,
+ G_TYPE_BOOLEAN);
+
+
+ document_signals[LOADING] =
+ g_signal_new ("loading",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, loading),
+ NULL, NULL,
+ pluma_marshal_VOID__UINT64_UINT64,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_UINT64,
+ G_TYPE_UINT64);
+
+ document_signals[LOADED] =
+ g_signal_new ("loaded",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, loaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ /**
+ * PlumaDocument::save:
+ * @document: the #PlumaDocument.
+ * @uri: the uri where the document is about to be saved.
+ * @encoding: the #PlumaEncoding used to save the document.
+ * @flags: the #PlumaDocumentSaveFlags for the save operation.
+ *
+ * The "save" signal is emitted when the document is saved.
+ *
+ * Since: 2.20
+ */
+ document_signals[SAVE] =
+ g_signal_new ("save",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, save),
+ NULL, NULL,
+ pluma_marshal_VOID__STRING_BOXED_FLAGS,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_STRING,
+ /* we rely on the fact that the PlumaEncoding pointer stays
+ * the same forever */
+ PLUMA_TYPE_ENCODING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ PLUMA_TYPE_DOCUMENT_SAVE_FLAGS);
+
+ document_signals[SAVING] =
+ g_signal_new ("saving",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, saving),
+ NULL, NULL,
+ pluma_marshal_VOID__UINT64_UINT64,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_UINT64,
+ G_TYPE_UINT64);
+
+ document_signals[SAVED] =
+ g_signal_new ("saved",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, saved),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ document_signals[SEARCH_HIGHLIGHT_UPDATED] =
+ g_signal_new ("search_highlight_updated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaDocumentClass, search_highlight_updated),
+ NULL, NULL,
+ pluma_marshal_VOID__BOXED_BOXED,
+ G_TYPE_NONE,
+ 2,
+ GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE,
+ GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ g_type_class_add_private (object_class, sizeof(PlumaDocumentPrivate));
+}
+
+static void
+set_language (PlumaDocument *doc,
+ GtkSourceLanguage *lang,
+ gboolean set_by_user)
+{
+ GtkSourceLanguage *old_lang;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ old_lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (doc));
+
+ if (old_lang == lang)
+ return;
+
+ gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (doc), lang);
+
+ if (lang != NULL)
+ gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (doc),
+ pluma_prefs_manager_get_enable_syntax_highlighting ());
+ else
+ gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (doc),
+ FALSE);
+
+ if (set_by_user && (doc->priv->uri != NULL))
+ {
+ pluma_document_set_metadata (doc, PLUMA_METADATA_ATTRIBUTE_LANGUAGE,
+ (lang == NULL) ? "_NORMAL_" : gtk_source_language_get_id (lang),
+ NULL);
+ }
+
+ doc->priv->language_set_by_user = set_by_user;
+}
+
+static void
+set_encoding (PlumaDocument *doc,
+ const PlumaEncoding *encoding,
+ gboolean set_by_user)
+{
+ g_return_if_fail (encoding != NULL);
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ if (doc->priv->encoding == encoding)
+ return;
+
+ doc->priv->encoding = encoding;
+
+ if (set_by_user)
+ {
+ const gchar *charset;
+
+ charset = pluma_encoding_get_charset (encoding);
+
+ pluma_document_set_metadata (doc, PLUMA_METADATA_ATTRIBUTE_ENCODING,
+ charset, NULL);
+ }
+
+ g_object_notify (G_OBJECT (doc), "encoding");
+}
+
+static GtkSourceStyleScheme *
+get_default_style_scheme (void)
+{
+ gchar *scheme_id;
+ GtkSourceStyleScheme *def_style;
+ GtkSourceStyleSchemeManager *manager;
+
+ manager = pluma_get_style_scheme_manager ();
+ scheme_id = pluma_prefs_manager_get_source_style_scheme ();
+ def_style = gtk_source_style_scheme_manager_get_scheme (manager,
+ scheme_id);
+
+ if (def_style == NULL)
+ {
+ g_warning ("Default style scheme '%s' cannot be found, falling back to 'classic' style scheme ", scheme_id);
+
+ def_style = gtk_source_style_scheme_manager_get_scheme (manager, "classic");
+ if (def_style == NULL)
+ {
+ g_warning ("Style scheme 'classic' cannot be found, check your GtkSourceView installation.");
+ }
+ }
+
+ g_free (scheme_id);
+
+ return def_style;
+}
+
+static void
+on_uri_changed (PlumaDocument *doc,
+ GParamSpec *pspec,
+ gpointer useless)
+{
+#ifdef ENABLE_GVFS_METADATA
+ GFile *location;
+
+ location = pluma_document_get_location (doc);
+
+ /* load metadata for this uri: we load sync since metadata is
+ * always local so it should be fast and we need the information
+ * right after the uri was set.
+ */
+ if (location != NULL)
+ {
+ GError *error = NULL;
+
+ if (doc->priv->metadata_info != NULL)
+ g_object_unref (doc->priv->metadata_info);
+
+ doc->priv->metadata_info = g_file_query_info (location,
+ METADATA_QUERY,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (error != NULL)
+ {
+ if (error->code != G_FILE_ERROR_ISDIR &&
+ error->code != G_FILE_ERROR_NOTDIR &&
+ error->code != G_FILE_ERROR_NOENT)
+ {
+ g_warning ("%s", error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ g_object_unref (location);
+ }
+#endif
+}
+
+static GtkSourceLanguage *
+guess_language (PlumaDocument *doc,
+ const gchar *content_type)
+{
+ gchar *data;
+ GtkSourceLanguage *language = NULL;
+
+ data = pluma_document_get_metadata (doc, PLUMA_METADATA_ATTRIBUTE_LANGUAGE);
+
+ if (data != NULL)
+ {
+ pluma_debug_message (DEBUG_DOCUMENT, "Language from metadata: %s", data);
+
+ if (strcmp (data, "_NORMAL_") != 0)
+ {
+ language = gtk_source_language_manager_get_language (
+ pluma_get_language_manager (),
+ data);
+ }
+
+ g_free (data);
+ }
+ else
+ {
+ GFile *file;
+ gchar *basename = NULL;
+
+ file = pluma_document_get_location (doc);
+ pluma_debug_message (DEBUG_DOCUMENT, "Sniffing Language");
+
+ if (file)
+ {
+ basename = g_file_get_basename (file);
+ }
+ else if (doc->priv->short_name != NULL)
+ {
+ basename = g_strdup (doc->priv->short_name);
+ }
+
+ language = gtk_source_language_manager_guess_language (
+ pluma_get_language_manager (),
+ basename,
+ content_type);
+
+ g_free (basename);
+
+ if (file != NULL)
+ {
+ g_object_unref (file);
+ }
+ }
+
+ return language;
+}
+
+static void
+on_content_type_changed (PlumaDocument *doc,
+ GParamSpec *pspec,
+ gpointer useless)
+{
+ if (!doc->priv->language_set_by_user)
+ {
+ GtkSourceLanguage *language;
+
+ language = guess_language (doc, doc->priv->content_type);
+
+ pluma_debug_message (DEBUG_DOCUMENT, "Language: %s",
+ language != NULL ? gtk_source_language_get_name (language) : "None");
+
+ set_language (doc, language, FALSE);
+ }
+}
+
+static gchar *
+get_default_content_type (void)
+{
+ return g_content_type_from_mime_type ("text/plain");
+}
+
+static void
+pluma_document_init (PlumaDocument *doc)
+{
+ GtkSourceStyleScheme *style_scheme;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ doc->priv = PLUMA_DOCUMENT_GET_PRIVATE (doc);
+
+ doc->priv->uri = NULL;
+ doc->priv->untitled_number = get_untitled_number ();
+
+ doc->priv->metadata_info = NULL;
+
+ doc->priv->content_type = get_default_content_type ();
+
+ doc->priv->readonly = FALSE;
+
+ doc->priv->stop_cursor_moved_emission = FALSE;
+
+ doc->priv->last_save_was_manually = TRUE;
+ doc->priv->language_set_by_user = FALSE;
+
+ doc->priv->dispose_has_run = FALSE;
+
+ doc->priv->mtime.tv_sec = 0;
+ doc->priv->mtime.tv_usec = 0;
+
+ g_get_current_time (&doc->priv->time_of_last_save_or_load);
+
+ doc->priv->encoding = pluma_encoding_get_utf8 ();
+
+ doc->priv->newline_type = PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT;
+
+ gtk_source_buffer_set_max_undo_levels (GTK_SOURCE_BUFFER (doc),
+ pluma_prefs_manager_get_undo_actions_limit ());
+
+ gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (doc),
+ pluma_prefs_manager_get_bracket_matching ());
+
+ pluma_document_set_enable_search_highlighting (doc,
+ pluma_prefs_manager_get_enable_search_highlighting ());
+
+ style_scheme = get_default_style_scheme ();
+ if (style_scheme != NULL)
+ gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (doc),
+ style_scheme);
+
+ g_signal_connect_after (doc,
+ "insert-text",
+ G_CALLBACK (insert_text_cb),
+ NULL);
+
+ g_signal_connect_after (doc,
+ "delete-range",
+ G_CALLBACK (delete_range_cb),
+ NULL);
+
+ g_signal_connect (doc,
+ "notify::content-type",
+ G_CALLBACK (on_content_type_changed),
+ NULL);
+
+ g_signal_connect (doc,
+ "notify::uri",
+ G_CALLBACK (on_uri_changed),
+ NULL);
+}
+
+PlumaDocument *
+pluma_document_new (void)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ return PLUMA_DOCUMENT (g_object_new (PLUMA_TYPE_DOCUMENT, NULL));
+}
+
+static void
+set_content_type_no_guess (PlumaDocument *doc,
+ const gchar *content_type)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ if (doc->priv->content_type != NULL && content_type != NULL &&
+ (0 == strcmp (doc->priv->content_type, content_type)))
+ return;
+
+ g_free (doc->priv->content_type);
+
+ if (content_type == NULL || g_content_type_is_unknown (content_type))
+ doc->priv->content_type = get_default_content_type ();
+ else
+ doc->priv->content_type = g_strdup (content_type);
+
+ g_object_notify (G_OBJECT (doc), "content-type");
+}
+
+static void
+set_content_type (PlumaDocument *doc,
+ const gchar *content_type)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ if (content_type == NULL)
+ {
+ GFile *file;
+ gchar *guessed_type = NULL;
+
+ /* If content type is null, we guess from the filename */
+ file = pluma_document_get_location (doc);
+ if (file != NULL)
+ {
+ gchar *basename;
+
+ basename = g_file_get_basename (file);
+ guessed_type = g_content_type_guess (basename, NULL, 0, NULL);
+
+ g_free (basename);
+ g_object_unref (file);
+ }
+
+ set_content_type_no_guess (doc, guessed_type);
+
+ g_free (guessed_type);
+ }
+ else
+ {
+ set_content_type_no_guess (doc, content_type);
+ }
+}
+
+void
+pluma_document_set_content_type (PlumaDocument *doc,
+ const gchar *content_type)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ set_content_type (doc, content_type);
+}
+
+static void
+set_uri (PlumaDocument *doc,
+ const gchar *uri)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ g_return_if_fail ((uri == NULL) || pluma_utils_is_valid_uri (uri));
+
+ if (uri != NULL)
+ {
+ if (doc->priv->uri == uri)
+ return;
+
+ g_free (doc->priv->uri);
+ doc->priv->uri = g_strdup (uri);
+
+ if (doc->priv->untitled_number > 0)
+ {
+ release_untitled_number (doc->priv->untitled_number);
+ doc->priv->untitled_number = 0;
+ }
+ }
+
+ g_object_notify (G_OBJECT (doc), "uri");
+
+ if (doc->priv->short_name == NULL)
+ {
+ g_object_notify (G_OBJECT (doc), "shortname");
+ }
+}
+
+GFile *
+pluma_document_get_location (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ return doc->priv->uri == NULL ? NULL : g_file_new_for_uri (doc->priv->uri);
+}
+
+gchar *
+pluma_document_get_uri (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ return g_strdup (doc->priv->uri);
+}
+
+void
+pluma_document_set_uri (PlumaDocument *doc,
+ const gchar *uri)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (uri != NULL);
+
+ set_uri (doc, uri);
+ set_content_type (doc, NULL);
+}
+
+/* Never returns NULL */
+gchar *
+pluma_document_get_uri_for_display (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), g_strdup (""));
+
+ if (doc->priv->uri == NULL)
+ return g_strdup_printf (_("Unsaved Document %d"),
+ doc->priv->untitled_number);
+ else
+ return pluma_utils_uri_for_display (doc->priv->uri);
+}
+
+/* Never returns NULL */
+gchar *
+pluma_document_get_short_name_for_display (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), g_strdup (""));
+
+ if (doc->priv->short_name != NULL)
+ return g_strdup (doc->priv->short_name);
+ else if (doc->priv->uri == NULL)
+ return g_strdup_printf (_("Unsaved Document %d"),
+ doc->priv->untitled_number);
+ else
+ return pluma_utils_basename_for_display (doc->priv->uri);
+}
+
+void
+pluma_document_set_short_name_for_display (PlumaDocument *doc,
+ const gchar *short_name)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ g_free (doc->priv->short_name);
+ doc->priv->short_name = g_strdup (short_name);
+
+ g_object_notify (G_OBJECT (doc), "shortname");
+}
+
+gchar *
+pluma_document_get_content_type (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ return g_strdup (doc->priv->content_type);
+}
+
+/* Never returns NULL */
+gchar *
+pluma_document_get_mime_type (PlumaDocument *doc)
+{
+ gchar *mime_type = NULL;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), g_strdup ("text/plain"));
+
+ if ((doc->priv->content_type != NULL) &&
+ (!g_content_type_is_unknown (doc->priv->content_type)))
+ {
+ mime_type = g_content_type_get_mime_type (doc->priv->content_type);
+ }
+
+ return mime_type != NULL ? mime_type : g_strdup ("text/plain");
+}
+
+/* Note: do not emit the notify::read-only signal */
+static gboolean
+set_readonly (PlumaDocument *doc,
+ gboolean readonly)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ readonly = (readonly != FALSE);
+
+ if (doc->priv->readonly == readonly)
+ return FALSE;
+
+ doc->priv->readonly = readonly;
+
+ return TRUE;
+}
+
+/**
+ * pluma_document_set_readonly:
+ * @doc: a #PlumaDocument
+ * @readonly: %TRUE to se the document as read-only
+ *
+ * If @readonly is %TRUE sets @doc as read-only.
+ */
+void
+_pluma_document_set_readonly (PlumaDocument *doc,
+ gboolean readonly)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ if (set_readonly (doc, readonly))
+ {
+ g_object_notify (G_OBJECT (doc), "read-only");
+ }
+}
+
+gboolean
+pluma_document_get_readonly (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), TRUE);
+
+ return doc->priv->readonly;
+}
+
+gboolean
+_pluma_document_check_externally_modified (PlumaDocument *doc)
+{
+ GFile *gfile;
+ GFileInfo *info;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+
+ if (doc->priv->uri == NULL)
+ {
+ return FALSE;
+ }
+
+ gfile = g_file_new_for_uri (doc->priv->uri);
+ info = g_file_query_info (gfile,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ g_object_unref (gfile);
+
+ if (info != NULL)
+ {
+ /* While at it also check if permissions changed */
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ gboolean read_only;
+
+ read_only = !g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
+
+ _pluma_document_set_readonly (doc, read_only);
+ }
+
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ {
+ GTimeVal timeval;
+
+ g_file_info_get_modification_time (info, &timeval);
+ g_object_unref (info);
+
+ return (timeval.tv_sec > doc->priv->mtime.tv_sec) ||
+ (timeval.tv_sec == doc->priv->mtime.tv_sec &&
+ timeval.tv_usec > doc->priv->mtime.tv_usec);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+reset_temp_loading_data (PlumaDocument *doc)
+{
+ /* the loader has been used, throw it away */
+ g_object_unref (doc->priv->loader);
+ doc->priv->loader = NULL;
+
+ doc->priv->requested_encoding = NULL;
+ doc->priv->requested_line_pos = 0;
+}
+
+static void
+document_loader_loaded (PlumaDocumentLoader *loader,
+ const GError *error,
+ PlumaDocument *doc)
+{
+ /* load was successful */
+ if (error == NULL ||
+ (error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_CONVERSION_FALLBACK))
+ {
+ GtkTextIter iter;
+ GFileInfo *info;
+ const gchar *content_type = NULL;
+ gboolean read_only = FALSE;
+ GTimeVal mtime = {0, 0};
+
+ info = pluma_document_loader_get_info (loader);
+
+ if (info)
+ {
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE))
+ content_type = g_file_info_get_attribute_string (info,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ g_file_info_get_modification_time (info, &mtime);
+
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ read_only = !g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
+ }
+
+ doc->priv->mtime = mtime;
+
+ set_readonly (doc, read_only);
+
+ g_get_current_time (&doc->priv->time_of_last_save_or_load);
+
+ set_encoding (doc,
+ pluma_document_loader_get_encoding (loader),
+ (doc->priv->requested_encoding != NULL));
+
+ set_content_type (doc, content_type);
+
+ pluma_document_set_newline_type (doc,
+ pluma_document_loader_get_newline_type (loader));
+
+ /* move the cursor at the requested line if any */
+ if (doc->priv->requested_line_pos > 0)
+ {
+ /* line_pos - 1 because get_iter_at_line counts from 0 */
+ gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (doc),
+ &iter,
+ doc->priv->requested_line_pos - 1);
+ }
+ /* else, if enabled, to the position stored in the metadata */
+ else if (pluma_prefs_manager_get_restore_cursor_position ())
+ {
+ gchar *pos;
+ gint offset;
+
+ pos = pluma_document_get_metadata (doc, PLUMA_METADATA_ATTRIBUTE_POSITION);
+
+ offset = pos ? atoi (pos) : 0;
+ g_free (pos);
+
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc),
+ &iter,
+ MAX (offset, 0));
+
+ /* make sure it's a valid position, if the file
+ * changed we may have ended up in the middle of
+ * a utf8 character cluster */
+ if (!gtk_text_iter_is_cursor_position (&iter))
+ {
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (doc),
+ &iter);
+ }
+ }
+ /* otherwise to the top */
+ else
+ {
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (doc),
+ &iter);
+ }
+
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter);
+ }
+
+ /* special case creating a named new doc */
+ else if (doc->priv->create &&
+ (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND) &&
+ (pluma_utils_uri_has_file_scheme (doc->priv->uri)))
+ {
+ reset_temp_loading_data (doc);
+
+ g_signal_emit (doc,
+ document_signals[LOADED],
+ 0,
+ NULL);
+
+ return;
+ }
+
+ g_signal_emit (doc,
+ document_signals[LOADED],
+ 0,
+ error);
+
+ reset_temp_loading_data (doc);
+}
+
+static void
+document_loader_loading (PlumaDocumentLoader *loader,
+ gboolean completed,
+ const GError *error,
+ PlumaDocument *doc)
+{
+ if (completed)
+ {
+ document_loader_loaded (loader, error, doc);
+ }
+ else
+ {
+ goffset size = 0;
+ goffset read;
+ GFileInfo *info;
+
+ info = pluma_document_loader_get_info (loader);
+
+ if (info && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
+ size = g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE);
+
+ read = pluma_document_loader_get_bytes_read (loader);
+
+ g_signal_emit (doc,
+ document_signals[LOADING],
+ 0,
+ read,
+ size);
+ }
+}
+
+static void
+pluma_document_load_real (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create)
+{
+ g_return_if_fail (doc->priv->loader == NULL);
+
+ pluma_debug_message (DEBUG_DOCUMENT, "load_real: uri = %s", uri);
+
+ /* create a loader. It will be destroyed when loading is completed */
+ doc->priv->loader = pluma_document_loader_new (doc, uri, encoding);
+
+ g_signal_connect (doc->priv->loader,
+ "loading",
+ G_CALLBACK (document_loader_loading),
+ doc);
+
+ doc->priv->create = create;
+ doc->priv->requested_encoding = encoding;
+ doc->priv->requested_line_pos = line_pos;
+
+ set_uri (doc, uri);
+ set_content_type (doc, NULL);
+
+ pluma_document_loader_load (doc->priv->loader);
+}
+
+/**
+ * pluma_document_load:
+ * @doc: the #PlumaDocument.
+ * @uri: the uri where to load the document from.
+ * @encoding: the #PlumaEncoding to encode the document.
+ * @line_pos: the line to show.
+ * @create: whether the document should be created if it doesn't exist.
+ *
+ * Load a document. This results in the "load" signal to be emitted.
+ */
+void
+pluma_document_load (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (uri != NULL);
+ g_return_if_fail (pluma_utils_is_valid_uri (uri));
+
+ g_signal_emit (doc, document_signals[LOAD], 0, uri, encoding, line_pos, create);
+}
+
+/**
+ * pluma_document_load_cancel:
+ * @doc: the #PlumaDocument.
+ *
+ * Cancel load of a document.
+ */
+gboolean
+pluma_document_load_cancel (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+
+ if (doc->priv->loader == NULL)
+ return FALSE;
+
+ return pluma_document_loader_cancel (doc->priv->loader);
+}
+
+static void
+document_saver_saving (PlumaDocumentSaver *saver,
+ gboolean completed,
+ const GError *error,
+ PlumaDocument *doc)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ if (completed)
+ {
+ /* save was successful */
+ if (error == NULL)
+ {
+ const gchar *uri;
+ const gchar *content_type = NULL;
+ GTimeVal mtime = {0, 0};
+ GFileInfo *info;
+
+ uri = pluma_document_saver_get_uri (saver);
+ set_uri (doc, uri);
+
+ info = pluma_document_saver_get_info (saver);
+
+ if (info != NULL)
+ {
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE))
+ content_type = g_file_info_get_attribute_string (info,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ g_file_info_get_modification_time (info, &mtime);
+ }
+
+ set_content_type (doc, content_type);
+ doc->priv->mtime = mtime;
+
+ g_get_current_time (&doc->priv->time_of_last_save_or_load);
+
+ _pluma_document_set_readonly (doc, FALSE);
+
+ gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (doc),
+ FALSE);
+
+ set_encoding (doc,
+ doc->priv->requested_encoding,
+ TRUE);
+ }
+
+ g_signal_emit (doc,
+ document_signals[SAVED],
+ 0,
+ error);
+
+ /* the saver has been used, throw it away */
+ g_object_unref (doc->priv->saver);
+ doc->priv->saver = NULL;
+ }
+ else
+ {
+ goffset size = 0;
+ goffset written = 0;
+
+ size = pluma_document_saver_get_file_size (saver);
+ written = pluma_document_saver_get_bytes_written (saver);
+
+ pluma_debug_message (DEBUG_DOCUMENT, "save progress: %" G_GINT64_FORMAT " of %" G_GINT64_FORMAT, written, size);
+
+ g_signal_emit (doc,
+ document_signals[SAVING],
+ 0,
+ written,
+ size);
+ }
+}
+
+static void
+pluma_document_save_real (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentSaveFlags flags)
+{
+ g_return_if_fail (doc->priv->saver == NULL);
+
+ /* create a saver, it will be destroyed once saving is complete */
+ doc->priv->saver = pluma_document_saver_new (doc, uri, encoding,
+ doc->priv->newline_type,
+ flags);
+
+ g_signal_connect (doc->priv->saver,
+ "saving",
+ G_CALLBACK (document_saver_saving),
+ doc);
+
+ doc->priv->requested_encoding = encoding;
+
+ pluma_document_saver_save (doc->priv->saver,
+ &doc->priv->mtime);
+}
+
+/**
+ * pluma_document_save:
+ * @doc: the #PlumaDocument.
+ * @flags: optionnal #PlumaDocumentSaveFlags.
+ *
+ * Save the document to its previous location. This results in the "save"
+ * signal to be emitted.
+ */
+void
+pluma_document_save (PlumaDocument *doc,
+ PlumaDocumentSaveFlags flags)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (doc->priv->uri != NULL);
+
+ g_signal_emit (doc,
+ document_signals[SAVE],
+ 0,
+ doc->priv->uri,
+ doc->priv->encoding,
+ flags);
+}
+
+/**
+ * pluma_document_save_as:
+ * @doc: the #PlumaDocument.
+ * @uri: the uri where to save the document.
+ * @encoding: the #PlumaEncoding to encode the document.
+ * @flags: optionnal #PlumaDocumentSaveFlags.
+ *
+ * Save the document to a new location. This results in the "save" signal
+ * to be emitted.
+ */
+void
+pluma_document_save_as (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentSaveFlags flags)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (uri != NULL);
+ g_return_if_fail (encoding != NULL);
+
+ /* priv->mtime refers to the the old uri (if any). Thus, it should be
+ * ignored when saving as. */
+ g_signal_emit (doc,
+ document_signals[SAVE],
+ 0,
+ uri,
+ encoding,
+ flags | PLUMA_DOCUMENT_SAVE_IGNORE_MTIME);
+}
+
+gboolean
+pluma_document_insert_file (PlumaDocument *doc,
+ GtkTextIter *iter,
+ const gchar *uri,
+ const PlumaEncoding *encoding)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (gtk_text_iter_get_buffer (iter) ==
+ GTK_TEXT_BUFFER (doc), FALSE);
+
+ /* TODO */
+
+ return FALSE;
+}
+
+gboolean
+pluma_document_is_untouched (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), TRUE);
+
+ return (doc->priv->uri == NULL) &&
+ (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)));
+}
+
+gboolean
+pluma_document_is_untitled (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), TRUE);
+
+ return (doc->priv->uri == NULL);
+}
+
+gboolean
+pluma_document_is_local (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+
+ if (doc->priv->uri == NULL)
+ {
+ return FALSE;
+ }
+
+ return pluma_utils_uri_has_file_scheme (doc->priv->uri);
+}
+
+gboolean
+pluma_document_get_deleted (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+
+ return doc->priv->uri && !pluma_utils_uri_exists (doc->priv->uri);
+}
+
+/*
+ * If @line is bigger than the lines of the document, the cursor is moved
+ * to the last line and FALSE is returned.
+ */
+gboolean
+pluma_document_goto_line (PlumaDocument *doc,
+ gint line)
+{
+ gboolean ret = TRUE;
+ guint line_count;
+ GtkTextIter iter;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+ g_return_val_if_fail (line >= -1, FALSE);
+
+ line_count = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (doc));
+
+ if (line >= line_count)
+ {
+ ret = FALSE;
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc),
+ &iter);
+ }
+ else
+ {
+ gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (doc),
+ &iter,
+ line);
+ }
+
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter);
+
+ return ret;
+}
+
+gboolean
+pluma_document_goto_line_offset (PlumaDocument *doc,
+ gint line,
+ gint line_offset)
+{
+ gboolean ret = TRUE;
+ guint offset_count;
+ GtkTextIter iter;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+ g_return_val_if_fail (line >= -1, FALSE);
+ g_return_val_if_fail (line_offset >= -1, FALSE);
+
+ gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (doc),
+ &iter,
+ line);
+
+ offset_count = gtk_text_iter_get_chars_in_line (&iter);
+ if (line_offset > offset_count)
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_text_iter_set_line_offset (&iter, line_offset);
+ }
+
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), &iter);
+
+ return ret;
+}
+
+static gint
+compute_num_of_lines (const gchar *text)
+{
+ const gchar *p;
+ gint len;
+ gint n = 1;
+
+ g_return_val_if_fail (text != NULL, 0);
+
+ len = strlen (text);
+ p = text;
+
+ while (len > 0)
+ {
+ gint del, par;
+
+ pango_find_paragraph_boundary (p, len, &del, &par);
+
+ if (del == par) /* not found */
+ break;
+
+ p += par;
+ len -= par;
+ ++n;
+ }
+
+ return n;
+}
+
+void
+pluma_document_set_search_text (PlumaDocument *doc,
+ const gchar *text,
+ guint flags)
+{
+ gchar *converted_text;
+ gboolean notify = FALSE;
+ gboolean update_to_search_region = FALSE;
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail ((text == NULL) || (doc->priv->search_text != text));
+ g_return_if_fail ((text == NULL) || g_utf8_validate (text, -1, NULL));
+
+ pluma_debug_message (DEBUG_DOCUMENT, "text = %s", text);
+
+ if (text != NULL)
+ {
+ if (*text != '\0')
+ {
+ converted_text = pluma_utils_unescape_search_text (text);
+ notify = !pluma_document_get_can_search_again (doc);
+ }
+ else
+ {
+ converted_text = g_strdup("");
+ notify = pluma_document_get_can_search_again (doc);
+ }
+
+ g_free (doc->priv->search_text);
+
+ doc->priv->search_text = converted_text;
+ doc->priv->num_of_lines_search_text = compute_num_of_lines (doc->priv->search_text);
+ update_to_search_region = TRUE;
+ }
+
+ if (!PLUMA_SEARCH_IS_DONT_SET_FLAGS (flags))
+ {
+ if (doc->priv->search_flags != flags)
+ update_to_search_region = TRUE;
+
+ doc->priv->search_flags = flags;
+
+ }
+
+ if (update_to_search_region)
+ {
+ GtkTextIter begin;
+ GtkTextIter end;
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &begin,
+ &end);
+
+ to_search_region_range (doc,
+ &begin,
+ &end);
+ }
+
+ if (notify)
+ g_object_notify (G_OBJECT (doc), "can-search-again");
+}
+
+gchar *
+pluma_document_get_search_text (PlumaDocument *doc,
+ guint *flags)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ if (flags != NULL)
+ *flags = doc->priv->search_flags;
+
+ return pluma_utils_escape_search_text (doc->priv->search_text);
+}
+
+gboolean
+pluma_document_get_can_search_again (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+
+ return ((doc->priv->search_text != NULL) &&
+ (*doc->priv->search_text != '\0'));
+}
+
+gboolean
+pluma_document_search_forward (PlumaDocument *doc,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter iter;
+ GtkSourceSearchFlags search_flags;
+ gboolean found = FALSE;
+ GtkTextIter m_start;
+ GtkTextIter m_end;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+ g_return_val_if_fail ((start == NULL) ||
+ (gtk_text_iter_get_buffer (start) == GTK_TEXT_BUFFER (doc)), FALSE);
+ g_return_val_if_fail ((end == NULL) ||
+ (gtk_text_iter_get_buffer (end) == GTK_TEXT_BUFFER (doc)), FALSE);
+
+ if (doc->priv->search_text == NULL)
+ {
+ pluma_debug_message (DEBUG_DOCUMENT, "doc->priv->search_text == NULL\n");
+ return FALSE;
+ }
+ else
+ pluma_debug_message (DEBUG_DOCUMENT, "doc->priv->search_text == \"%s\"\n", doc->priv->search_text);
+
+ if (start == NULL)
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (doc), &iter);
+ else
+ iter = *start;
+
+ search_flags = GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_TEXT_ONLY;
+
+ if (!PLUMA_SEARCH_IS_CASE_SENSITIVE (doc->priv->search_flags))
+ {
+ search_flags = search_flags | GTK_SOURCE_SEARCH_CASE_INSENSITIVE;
+ }
+
+ while (!found)
+ {
+ found = gtk_source_iter_forward_search (&iter,
+ doc->priv->search_text,
+ search_flags,
+ &m_start,
+ &m_end,
+ end);
+
+ if (found && PLUMA_SEARCH_IS_ENTIRE_WORD (doc->priv->search_flags))
+ {
+ found = gtk_text_iter_starts_word (&m_start) &&
+ gtk_text_iter_ends_word (&m_end);
+
+ if (!found)
+ iter = m_end;
+ }
+ else
+ break;
+ }
+
+ if (found && (match_start != NULL))
+ *match_start = m_start;
+
+ if (found && (match_end != NULL))
+ *match_end = m_end;
+
+ return found;
+}
+
+gboolean
+pluma_document_search_backward (PlumaDocument *doc,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter iter;
+ GtkSourceSearchFlags search_flags;
+ gboolean found = FALSE;
+ GtkTextIter m_start;
+ GtkTextIter m_end;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+ g_return_val_if_fail ((start == NULL) ||
+ (gtk_text_iter_get_buffer (start) == GTK_TEXT_BUFFER (doc)), FALSE);
+ g_return_val_if_fail ((end == NULL) ||
+ (gtk_text_iter_get_buffer (end) == GTK_TEXT_BUFFER (doc)), FALSE);
+
+ if (doc->priv->search_text == NULL)
+ {
+ pluma_debug_message (DEBUG_DOCUMENT, "doc->priv->search_text == NULL\n");
+ return FALSE;
+ }
+ else
+ pluma_debug_message (DEBUG_DOCUMENT, "doc->priv->search_text == \"%s\"\n", doc->priv->search_text);
+
+ if (end == NULL)
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &iter);
+ else
+ iter = *end;
+
+ search_flags = GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_TEXT_ONLY;
+
+ if (!PLUMA_SEARCH_IS_CASE_SENSITIVE (doc->priv->search_flags))
+ {
+ search_flags = search_flags | GTK_SOURCE_SEARCH_CASE_INSENSITIVE;
+ }
+
+ while (!found)
+ {
+ found = gtk_source_iter_backward_search (&iter,
+ doc->priv->search_text,
+ search_flags,
+ &m_start,
+ &m_end,
+ start);
+
+ if (found && PLUMA_SEARCH_IS_ENTIRE_WORD (doc->priv->search_flags))
+ {
+ found = gtk_text_iter_starts_word (&m_start) &&
+ gtk_text_iter_ends_word (&m_end);
+
+ if (!found)
+ iter = m_start;
+ }
+ else
+ break;
+ }
+
+ if (found && (match_start != NULL))
+ *match_start = m_start;
+
+ if (found && (match_end != NULL))
+ *match_end = m_end;
+
+ return found;
+}
+
+gint
+pluma_document_replace_all (PlumaDocument *doc,
+ const gchar *find,
+ const gchar *replace,
+ guint flags)
+{
+ GtkTextIter iter;
+ GtkTextIter m_start;
+ GtkTextIter m_end;
+ GtkSourceSearchFlags search_flags = 0;
+ gboolean found = TRUE;
+ gint cont = 0;
+ gchar *search_text;
+ gchar *replace_text;
+ gint replace_text_len;
+ GtkTextBuffer *buffer;
+ gboolean brackets_highlighting;
+ gboolean search_highliting;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), 0);
+ g_return_val_if_fail (replace != NULL, 0);
+ g_return_val_if_fail ((find != NULL) || (doc->priv->search_text != NULL), 0);
+
+ buffer = GTK_TEXT_BUFFER (doc);
+
+ if (find == NULL)
+ search_text = g_strdup (doc->priv->search_text);
+ else
+ search_text = pluma_utils_unescape_search_text (find);
+
+ replace_text = pluma_utils_unescape_search_text (replace);
+
+ gtk_text_buffer_get_start_iter (buffer, &iter);
+
+ search_flags = GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_TEXT_ONLY;
+
+ if (!PLUMA_SEARCH_IS_CASE_SENSITIVE (flags))
+ {
+ search_flags = search_flags | GTK_SOURCE_SEARCH_CASE_INSENSITIVE;
+ }
+
+ replace_text_len = strlen (replace_text);
+
+ /* disable cursor_moved emission until the end of the
+ * replace_all so that we don't spend all the time
+ * updating the position in the statusbar
+ */
+ doc->priv->stop_cursor_moved_emission = TRUE;
+
+ /* also avoid spending time matching brackets */
+ brackets_highlighting = gtk_source_buffer_get_highlight_matching_brackets (GTK_SOURCE_BUFFER (buffer));
+ gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (buffer), FALSE);
+
+ /* and do search highliting later */
+ search_highliting = pluma_document_get_enable_search_highlighting (doc);
+ pluma_document_set_enable_search_highlighting (doc, FALSE);
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ do
+ {
+ found = gtk_source_iter_forward_search (&iter,
+ search_text,
+ search_flags,
+ &m_start,
+ &m_end,
+ NULL);
+
+ if (found && PLUMA_SEARCH_IS_ENTIRE_WORD (flags))
+ {
+ gboolean word;
+
+ word = gtk_text_iter_starts_word (&m_start) &&
+ gtk_text_iter_ends_word (&m_end);
+
+ if (!word)
+ {
+ iter = m_end;
+ continue;
+ }
+ }
+
+ if (found)
+ {
+ ++cont;
+
+ gtk_text_buffer_delete (buffer,
+ &m_start,
+ &m_end);
+ gtk_text_buffer_insert (buffer,
+ &m_start,
+ replace_text,
+ replace_text_len);
+
+ iter = m_start;
+ }
+
+ } while (found);
+
+ gtk_text_buffer_end_user_action (buffer);
+
+ /* re-enable cursor_moved emission and notify
+ * the current position
+ */
+ doc->priv->stop_cursor_moved_emission = FALSE;
+ emit_cursor_moved (doc);
+
+ gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (buffer),
+ brackets_highlighting);
+ pluma_document_set_enable_search_highlighting (doc, search_highliting);
+
+ g_free (search_text);
+ g_free (replace_text);
+
+ return cont;
+}
+
+void
+pluma_document_set_language (PlumaDocument *doc,
+ GtkSourceLanguage *lang)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ set_language (doc, lang, TRUE);
+}
+
+GtkSourceLanguage *
+pluma_document_get_language (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ return gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (doc));
+}
+
+const PlumaEncoding *
+pluma_document_get_encoding (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ return doc->priv->encoding;
+}
+
+glong
+_pluma_document_get_seconds_since_last_save_or_load (PlumaDocument *doc)
+{
+ GTimeVal current_time;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), -1);
+
+ g_get_current_time (&current_time);
+
+ return (current_time.tv_sec - doc->priv->time_of_last_save_or_load.tv_sec);
+}
+
+static void
+get_search_match_colors (PlumaDocument *doc,
+ gboolean *foreground_set,
+ GdkColor *foreground,
+ gboolean *background_set,
+ GdkColor *background)
+{
+ GtkSourceStyleScheme *style_scheme;
+ GtkSourceStyle *style;
+ gchar *bg;
+ gchar *fg;
+
+ style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (doc));
+ if (style_scheme == NULL)
+ goto fallback;
+
+ style = gtk_source_style_scheme_get_style (style_scheme,
+ "search-match");
+ if (style == NULL)
+ goto fallback;
+
+ g_object_get (style,
+ "foreground-set", foreground_set,
+ "foreground", &fg,
+ "background-set", background_set,
+ "background", &bg,
+ NULL);
+
+ if (*foreground_set)
+ {
+ if (fg == NULL ||
+ !gdk_color_parse (fg, foreground))
+ {
+ *foreground_set = FALSE;
+ }
+ }
+
+ if (*background_set)
+ {
+ if (bg == NULL ||
+ !gdk_color_parse (bg, background))
+ {
+ *background_set = FALSE;
+ }
+ }
+
+ g_free (fg);
+ g_free (bg);
+
+ return;
+
+ fallback:
+ pluma_debug_message (DEBUG_DOCUMENT,
+ "Falling back to hard-coded colors "
+ "for the \"found\" text tag.");
+
+ gdk_color_parse ("#FFFF78", background);
+ *background_set = TRUE;
+ *foreground_set = FALSE;
+
+ return;
+}
+
+static void
+sync_found_tag (PlumaDocument *doc,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ GdkColor fg;
+ GdkColor bg;
+ gboolean fg_set;
+ gboolean bg_set;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ g_return_if_fail (GTK_TEXT_TAG (doc->priv->found_tag));
+
+ get_search_match_colors (doc,
+ &fg_set, &fg,
+ &bg_set, &bg);
+
+ g_object_set (doc->priv->found_tag,
+ "foreground-gdk", fg_set ? &fg : NULL,
+ NULL);
+ g_object_set (doc->priv->found_tag,
+ "background-gdk", bg_set ? &bg : NULL,
+ NULL);
+}
+
+static void
+text_tag_set_highest_priority (GtkTextTag *tag,
+ GtkTextBuffer *buffer)
+{
+ GtkTextTagTable *table;
+ gint n;
+
+ table = gtk_text_buffer_get_tag_table (buffer);
+ n = gtk_text_tag_table_get_size (table);
+ gtk_text_tag_set_priority (tag, n - 1);
+}
+
+static void
+search_region (PlumaDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GtkTextIter iter;
+ GtkTextIter m_start;
+ GtkTextIter m_end;
+ GtkSourceSearchFlags search_flags = 0;
+ gboolean found = TRUE;
+
+ GtkTextBuffer *buffer;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ buffer = GTK_TEXT_BUFFER (doc);
+
+ if (doc->priv->found_tag == NULL)
+ {
+ doc->priv->found_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (doc),
+ "found",
+ NULL);
+
+ sync_found_tag (doc, NULL, NULL);
+
+ g_signal_connect (doc,
+ "notify::style-scheme",
+ G_CALLBACK (sync_found_tag),
+ NULL);
+ }
+
+ /* make sure the 'found' tag has the priority over
+ * syntax highlighting tags */
+ text_tag_set_highest_priority (doc->priv->found_tag,
+ GTK_TEXT_BUFFER (doc));
+
+
+ if (doc->priv->search_text == NULL)
+ return;
+
+ g_return_if_fail (doc->priv->num_of_lines_search_text > 0);
+
+ gtk_text_iter_backward_lines (start, doc->priv->num_of_lines_search_text);
+ gtk_text_iter_forward_lines (end, doc->priv->num_of_lines_search_text);
+
+ if (gtk_text_iter_has_tag (start, doc->priv->found_tag) &&
+ !gtk_text_iter_begins_tag (start, doc->priv->found_tag))
+ gtk_text_iter_backward_to_tag_toggle (start, doc->priv->found_tag);
+
+ if (gtk_text_iter_has_tag (end, doc->priv->found_tag) &&
+ !gtk_text_iter_ends_tag (end, doc->priv->found_tag))
+ gtk_text_iter_forward_to_tag_toggle (end, doc->priv->found_tag);
+
+ /*
+ g_print ("[%u (%u), %u (%u)]\n", gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
+ gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
+ */
+
+ gtk_text_buffer_remove_tag (buffer,
+ doc->priv->found_tag,
+ start,
+ end);
+
+ if (*doc->priv->search_text == '\0')
+ return;
+
+ iter = *start;
+
+ search_flags = GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_TEXT_ONLY;
+
+ if (!PLUMA_SEARCH_IS_CASE_SENSITIVE (doc->priv->search_flags))
+ {
+ search_flags = search_flags | GTK_SOURCE_SEARCH_CASE_INSENSITIVE;
+ }
+
+ do
+ {
+ if ((end != NULL) && gtk_text_iter_is_end (end))
+ end = NULL;
+
+ found = gtk_source_iter_forward_search (&iter,
+ doc->priv->search_text,
+ search_flags,
+ &m_start,
+ &m_end,
+ end);
+
+ iter = m_end;
+
+ if (found && PLUMA_SEARCH_IS_ENTIRE_WORD (doc->priv->search_flags))
+ {
+ gboolean word;
+
+ word = gtk_text_iter_starts_word (&m_start) &&
+ gtk_text_iter_ends_word (&m_end);
+
+ if (!word)
+ continue;
+ }
+
+ if (found)
+ {
+ gtk_text_buffer_apply_tag (buffer,
+ doc->priv->found_tag,
+ &m_start,
+ &m_end);
+ }
+
+ } while (found);
+}
+
+static void
+to_search_region_range (PlumaDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ pluma_debug (DEBUG_DOCUMENT);
+
+ if (doc->priv->to_search_region == NULL)
+ return;
+
+ gtk_text_iter_set_line_offset (start, 0);
+ gtk_text_iter_forward_to_line_end (end);
+
+ /*
+ g_print ("+ [%u (%u), %u (%u)]\n", gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
+ gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
+ */
+
+ /* Add the region to the refresh region */
+ pluma_text_region_add (doc->priv->to_search_region, start, end);
+
+ /* Notify views of the updated highlight region */
+ gtk_text_iter_backward_lines (start, doc->priv->num_of_lines_search_text);
+ gtk_text_iter_forward_lines (end, doc->priv->num_of_lines_search_text);
+
+ g_signal_emit (doc, document_signals [SEARCH_HIGHLIGHT_UPDATED], 0, start, end);
+}
+
+void
+_pluma_document_search_region (PlumaDocument *doc,
+ const GtkTextIter *start,
+ const GtkTextIter *end)
+{
+ PlumaTextRegion *region;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (start != NULL);
+ g_return_if_fail (end != NULL);
+
+ if (doc->priv->to_search_region == NULL)
+ return;
+
+ /*
+ g_print ("U [%u (%u), %u (%u)]\n", gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
+ gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
+ */
+
+ /* get the subregions not yet highlighted */
+ region = pluma_text_region_intersect (doc->priv->to_search_region,
+ start,
+ end);
+ if (region)
+ {
+ gint i;
+ GtkTextIter start_search;
+ GtkTextIter end_search;
+
+ i = pluma_text_region_subregions (region);
+ pluma_text_region_nth_subregion (region,
+ 0,
+ &start_search,
+ NULL);
+
+ pluma_text_region_nth_subregion (region,
+ i - 1,
+ NULL,
+ &end_search);
+
+ pluma_text_region_destroy (region, TRUE);
+
+ gtk_text_iter_order (&start_search, &end_search);
+
+ search_region (doc, &start_search, &end_search);
+
+ /* remove the just highlighted region */
+ pluma_text_region_subtract (doc->priv->to_search_region,
+ start,
+ end);
+ }
+}
+
+static void
+insert_text_cb (PlumaDocument *doc,
+ GtkTextIter *pos,
+ const gchar *text,
+ gint length)
+{
+ GtkTextIter start;
+ GtkTextIter end;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ start = end = *pos;
+
+ /*
+ * pos is invalidated when
+ * insertion occurs (because the buffer contents change), but the
+ * default signal handler revalidates it to point to the end of the
+ * inserted text
+ */
+ gtk_text_iter_backward_chars (&start,
+ g_utf8_strlen (text, length));
+
+ to_search_region_range (doc, &start, &end);
+}
+
+static void
+delete_range_cb (PlumaDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GtkTextIter d_start;
+ GtkTextIter d_end;
+
+ pluma_debug (DEBUG_DOCUMENT);
+
+ d_start = *start;
+ d_end = *end;
+
+ to_search_region_range (doc, &d_start, &d_end);
+}
+
+void
+pluma_document_set_enable_search_highlighting (PlumaDocument *doc,
+ gboolean enable)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ enable = enable != FALSE;
+
+ if ((doc->priv->to_search_region != NULL) == enable)
+ return;
+
+ if (doc->priv->to_search_region != NULL)
+ {
+ /* Disable search highlighting */
+ if (doc->priv->found_tag != NULL)
+ {
+ /* If needed remove the found_tag */
+ GtkTextIter begin;
+ GtkTextIter end;
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &begin,
+ &end);
+
+ gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (doc),
+ doc->priv->found_tag,
+ &begin,
+ &end);
+ }
+
+ pluma_text_region_destroy (doc->priv->to_search_region,
+ TRUE);
+ doc->priv->to_search_region = NULL;
+ }
+ else
+ {
+ doc->priv->to_search_region = pluma_text_region_new (GTK_TEXT_BUFFER (doc));
+ if (pluma_document_get_can_search_again (doc))
+ {
+ /* If search_text is not empty, highligth all its occurrences */
+ GtkTextIter begin;
+ GtkTextIter end;
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &begin,
+ &end);
+
+ to_search_region_range (doc,
+ &begin,
+ &end);
+ }
+ }
+}
+
+gboolean
+pluma_document_get_enable_search_highlighting (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), FALSE);
+
+ return (doc->priv->to_search_region != NULL);
+}
+
+void
+pluma_document_set_newline_type (PlumaDocument *doc,
+ PlumaDocumentNewlineType newline_type)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ if (doc->priv->newline_type != newline_type)
+ {
+ doc->priv->newline_type = newline_type;
+
+ g_object_notify (G_OBJECT (doc), "newline-type");
+ }
+}
+
+PlumaDocumentNewlineType
+pluma_document_get_newline_type (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), 0);
+
+ return doc->priv->newline_type;
+}
+
+void
+_pluma_document_set_mount_operation_factory (PlumaDocument *doc,
+ PlumaMountOperationFactory callback,
+ gpointer userdata)
+{
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ doc->priv->mount_operation_factory = callback;
+ doc->priv->mount_operation_userdata = userdata;
+}
+
+GMountOperation *
+_pluma_document_create_mount_operation (PlumaDocument *doc)
+{
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ if (doc->priv->mount_operation_factory == NULL)
+ return g_mount_operation_new ();
+ else
+ return doc->priv->mount_operation_factory (doc,
+ doc->priv->mount_operation_userdata);
+}
+
+#ifndef ENABLE_GVFS_METADATA
+gchar *
+pluma_document_get_metadata (PlumaDocument *doc,
+ const gchar *key)
+{
+ gchar *value = NULL;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ if (!pluma_document_is_untitled (doc))
+ {
+ value = pluma_metadata_manager_get (doc->priv->uri, key);
+ }
+
+ return value;
+}
+
+void
+pluma_document_set_metadata (PlumaDocument *doc,
+ const gchar *first_key,
+ ...)
+{
+ const gchar *key;
+ const gchar *value;
+ va_list var_args;
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (first_key != NULL);
+
+ if (pluma_document_is_untitled (doc))
+ {
+ /* Can't set metadata for untitled documents */
+ return;
+ }
+
+ va_start (var_args, first_key);
+
+ for (key = first_key; key; key = va_arg (var_args, const gchar *))
+ {
+ value = va_arg (var_args, const gchar *);
+
+ pluma_metadata_manager_set (doc->priv->uri,
+ key,
+ value);
+ }
+
+ va_end (var_args);
+}
+
+#else
+
+/**
+ * pluma_document_get_metadata:
+ * @doc: a #PlumaDocument
+ * @key: name of the key
+ *
+ * Gets the metadata assigned to @key.
+ *
+ * Returns: the value assigned to @key.
+ */
+gchar *
+pluma_document_get_metadata (PlumaDocument *doc,
+ const gchar *key)
+{
+ gchar *value = NULL;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ if (doc->priv->metadata_info && g_file_info_has_attribute (doc->priv->metadata_info,
+ key))
+ {
+ value = g_strdup (g_file_info_get_attribute_string (doc->priv->metadata_info,
+ key));
+ }
+
+ return value;
+}
+
+static void
+set_attributes_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer useless)
+{
+ g_file_set_attributes_finish (G_FILE (source),
+ res,
+ NULL,
+ NULL);
+}
+
+/**
+ * pluma_document_set_metadata:
+ * @doc: a #PlumaDocument
+ * @first_key: name of the first key to set
+ * @...: value for the first key, followed optionally by more key/value pairs,
+ * followed by %NULL.
+ *
+ * Sets metadata on a document.
+ */
+void
+pluma_document_set_metadata (PlumaDocument *doc,
+ const gchar *first_key,
+ ...)
+{
+ const gchar *key;
+ const gchar *value;
+ va_list var_args;
+ GFileInfo *info;
+ GFile *location;
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (first_key != NULL);
+
+ info = g_file_info_new ();
+
+ va_start (var_args, first_key);
+
+ for (key = first_key; key; key = va_arg (var_args, const gchar *))
+ {
+ value = va_arg (var_args, const gchar *);
+
+ if (value != NULL)
+ {
+ g_file_info_set_attribute_string (info,
+ key, value);
+ }
+ else
+ {
+ /* Unset the key */
+ g_file_info_set_attribute (info, key,
+ G_FILE_ATTRIBUTE_TYPE_INVALID,
+ NULL);
+ }
+ }
+
+ va_end (var_args);
+
+ if (doc->priv->metadata_info != NULL)
+ g_file_info_copy_into (info, doc->priv->metadata_info);
+
+ location = pluma_document_get_location (doc);
+
+ if (location != NULL)
+ {
+ g_file_set_attributes_async (location,
+ info,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ set_attributes_cb,
+ NULL);
+
+ g_object_unref (location);
+ }
+
+ g_object_unref (info);
+}
+#endif
diff --git a/pluma/pluma-document.h b/pluma/pluma-document.h
new file mode 100755
index 00000000..a9a87d8e
--- /dev/null
+++ b/pluma/pluma-document.h
@@ -0,0 +1,338 @@
+/*
+ * pluma-document.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_DOCUMENT_H__
+#define __PLUMA_DOCUMENT_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksourcebuffer.h>
+
+#include <pluma/pluma-encodings.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_DOCUMENT (pluma_document_get_type())
+#define PLUMA_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_DOCUMENT, PlumaDocument))
+#define PLUMA_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_DOCUMENT, PlumaDocumentClass))
+#define PLUMA_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_DOCUMENT))
+#define PLUMA_IS_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_DOCUMENT))
+#define PLUMA_DOCUMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_DOCUMENT, PlumaDocumentClass))
+
+#ifdef G_OS_WIN32
+#define PLUMA_METADATA_ATTRIBUTE_POSITION "position"
+#define PLUMA_METADATA_ATTRIBUTE_ENCODING "encoding"
+#define PLUMA_METADATA_ATTRIBUTE_LANGUAGE "language"
+#else
+#define PLUMA_METADATA_ATTRIBUTE_POSITION "metadata::pluma-position"
+#define PLUMA_METADATA_ATTRIBUTE_ENCODING "metadata::pluma-encoding"
+#define PLUMA_METADATA_ATTRIBUTE_LANGUAGE "metadata::pluma-language"
+#endif
+
+typedef enum
+{
+ PLUMA_DOCUMENT_NEWLINE_TYPE_LF,
+ PLUMA_DOCUMENT_NEWLINE_TYPE_CR,
+ PLUMA_DOCUMENT_NEWLINE_TYPE_CR_LF
+} PlumaDocumentNewlineType;
+
+#ifdef G_OS_WIN32
+#define PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT PLUMA_DOCUMENT_NEWLINE_TYPE_CR_LF
+#else
+#define PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT PLUMA_DOCUMENT_NEWLINE_TYPE_LF
+#endif
+
+typedef enum
+{
+ PLUMA_SEARCH_DONT_SET_FLAGS = 1 << 0,
+ PLUMA_SEARCH_ENTIRE_WORD = 1 << 1,
+ PLUMA_SEARCH_CASE_SENSITIVE = 1 << 2
+
+} PlumaSearchFlags;
+
+/**
+ * PlumaDocumentSaveFlags:
+ * @PLUMA_DOCUMENT_SAVE_IGNORE_MTIME: save file despite external modifications.
+ * @PLUMA_DOCUMENT_SAVE_IGNORE_BACKUP: write the file directly without attempting to backup.
+ * @PLUMA_DOCUMENT_SAVE_PRESERVE_BACKUP: preserve previous backup file, needed to support autosaving.
+ */
+typedef enum
+{
+ PLUMA_DOCUMENT_SAVE_IGNORE_MTIME = 1 << 0,
+ PLUMA_DOCUMENT_SAVE_IGNORE_BACKUP = 1 << 1,
+ PLUMA_DOCUMENT_SAVE_PRESERVE_BACKUP = 1 << 2
+} PlumaDocumentSaveFlags;
+
+/* Private structure type */
+typedef struct _PlumaDocumentPrivate PlumaDocumentPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaDocument PlumaDocument;
+
+struct _PlumaDocument
+{
+ GtkSourceBuffer buffer;
+
+ /*< private > */
+ PlumaDocumentPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaDocumentClass PlumaDocumentClass;
+
+struct _PlumaDocumentClass
+{
+ GtkSourceBufferClass parent_class;
+
+ /* Signals */ // CHECK: ancora da rivedere
+
+ void (* cursor_moved) (PlumaDocument *document);
+
+ /* Document load */
+ void (* load) (PlumaDocument *document,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create);
+
+ void (* loading) (PlumaDocument *document,
+ goffset size,
+ goffset total_size);
+
+ void (* loaded) (PlumaDocument *document,
+ const GError *error);
+
+ /* Document save */
+ void (* save) (PlumaDocument *document,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentSaveFlags flags);
+
+ void (* saving) (PlumaDocument *document,
+ goffset size,
+ goffset total_size);
+
+ void (* saved) (PlumaDocument *document,
+ const GError *error);
+
+ void (* search_highlight_updated)
+ (PlumaDocument *document,
+ GtkTextIter *start,
+ GtkTextIter *end);
+};
+
+
+#define PLUMA_DOCUMENT_ERROR pluma_document_error_quark ()
+
+enum
+{
+ PLUMA_DOCUMENT_ERROR_EXTERNALLY_MODIFIED,
+ PLUMA_DOCUMENT_ERROR_CANT_CREATE_BACKUP,
+ PLUMA_DOCUMENT_ERROR_TOO_BIG,
+ PLUMA_DOCUMENT_ERROR_ENCODING_AUTO_DETECTION_FAILED,
+ PLUMA_DOCUMENT_ERROR_CONVERSION_FALLBACK,
+ PLUMA_DOCUMENT_NUM_ERRORS
+};
+
+GQuark pluma_document_error_quark (void);
+
+GType pluma_document_get_type (void) G_GNUC_CONST;
+
+PlumaDocument *pluma_document_new (void);
+
+GFile *pluma_document_get_location (PlumaDocument *doc);
+
+gchar *pluma_document_get_uri (PlumaDocument *doc);
+void pluma_document_set_uri (PlumaDocument *doc,
+ const gchar *uri);
+
+gchar *pluma_document_get_uri_for_display
+ (PlumaDocument *doc);
+gchar *pluma_document_get_short_name_for_display
+ (PlumaDocument *doc);
+
+void pluma_document_set_short_name_for_display
+ (PlumaDocument *doc,
+ const gchar *name);
+
+gchar *pluma_document_get_content_type
+ (PlumaDocument *doc);
+
+void pluma_document_set_content_type
+ (PlumaDocument *doc,
+ const gchar *content_type);
+
+gchar *pluma_document_get_mime_type (PlumaDocument *doc);
+
+gboolean pluma_document_get_readonly (PlumaDocument *doc);
+
+void pluma_document_load (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create);
+
+gboolean pluma_document_insert_file (PlumaDocument *doc,
+ GtkTextIter *iter,
+ const gchar *uri,
+ const PlumaEncoding *encoding);
+
+gboolean pluma_document_load_cancel (PlumaDocument *doc);
+
+void pluma_document_save (PlumaDocument *doc,
+ PlumaDocumentSaveFlags flags);
+
+void pluma_document_save_as (PlumaDocument *doc,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentSaveFlags flags);
+
+gboolean pluma_document_is_untouched (PlumaDocument *doc);
+gboolean pluma_document_is_untitled (PlumaDocument *doc);
+
+gboolean pluma_document_is_local (PlumaDocument *doc);
+
+gboolean pluma_document_get_deleted (PlumaDocument *doc);
+
+gboolean pluma_document_goto_line (PlumaDocument *doc,
+ gint line);
+
+gboolean pluma_document_goto_line_offset(PlumaDocument *doc,
+ gint line,
+ gint line_offset);
+
+void pluma_document_set_search_text (PlumaDocument *doc,
+ const gchar *text,
+ guint flags);
+
+gchar *pluma_document_get_search_text (PlumaDocument *doc,
+ guint *flags);
+
+gboolean pluma_document_get_can_search_again
+ (PlumaDocument *doc);
+
+gboolean pluma_document_search_forward (PlumaDocument *doc,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end);
+
+gboolean pluma_document_search_backward (PlumaDocument *doc,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end);
+
+gint pluma_document_replace_all (PlumaDocument *doc,
+ const gchar *find,
+ const gchar *replace,
+ guint flags);
+
+void pluma_document_set_language (PlumaDocument *doc,
+ GtkSourceLanguage *lang);
+GtkSourceLanguage
+ *pluma_document_get_language (PlumaDocument *doc);
+
+const PlumaEncoding
+ *pluma_document_get_encoding (PlumaDocument *doc);
+
+void pluma_document_set_enable_search_highlighting
+ (PlumaDocument *doc,
+ gboolean enable);
+
+gboolean pluma_document_get_enable_search_highlighting
+ (PlumaDocument *doc);
+
+void pluma_document_set_newline_type (PlumaDocument *doc,
+ PlumaDocumentNewlineType newline_type);
+
+PlumaDocumentNewlineType
+ pluma_document_get_newline_type (PlumaDocument *doc);
+
+gchar *pluma_document_get_metadata (PlumaDocument *doc,
+ const gchar *key);
+
+void pluma_document_set_metadata (PlumaDocument *doc,
+ const gchar *first_key,
+ ...);
+
+/*
+ * Non exported functions
+ */
+void _pluma_document_set_readonly (PlumaDocument *doc,
+ gboolean readonly);
+
+glong _pluma_document_get_seconds_since_last_save_or_load
+ (PlumaDocument *doc);
+
+/* Note: this is a sync stat: use only on local files */
+gboolean _pluma_document_check_externally_modified
+ (PlumaDocument *doc);
+
+void _pluma_document_search_region (PlumaDocument *doc,
+ const GtkTextIter *start,
+ const GtkTextIter *end);
+
+/* Search macros */
+#define PLUMA_SEARCH_IS_DONT_SET_FLAGS(sflags) ((sflags & PLUMA_SEARCH_DONT_SET_FLAGS) != 0)
+#define PLUMA_SEARCH_SET_DONT_SET_FLAGS(sflags,state) ((state == TRUE) ? \
+(sflags |= PLUMA_SEARCH_DONT_SET_FLAGS) : (sflags &= ~PLUMA_SEARCH_DONT_SET_FLAGS))
+
+#define PLUMA_SEARCH_IS_ENTIRE_WORD(sflags) ((sflags & PLUMA_SEARCH_ENTIRE_WORD) != 0)
+#define PLUMA_SEARCH_SET_ENTIRE_WORD(sflags,state) ((state == TRUE) ? \
+(sflags |= PLUMA_SEARCH_ENTIRE_WORD) : (sflags &= ~PLUMA_SEARCH_ENTIRE_WORD))
+
+#define PLUMA_SEARCH_IS_CASE_SENSITIVE(sflags) ((sflags & PLUMA_SEARCH_CASE_SENSITIVE) != 0)
+#define PLUMA_SEARCH_SET_CASE_SENSITIVE(sflags,state) ((state == TRUE) ? \
+(sflags |= PLUMA_SEARCH_CASE_SENSITIVE) : (sflags &= ~PLUMA_SEARCH_CASE_SENSITIVE))
+
+typedef GMountOperation *(*PlumaMountOperationFactory)(PlumaDocument *doc,
+ gpointer userdata);
+
+void _pluma_document_set_mount_operation_factory
+ (PlumaDocument *doc,
+ PlumaMountOperationFactory callback,
+ gpointer userdata);
+GMountOperation
+ *_pluma_document_create_mount_operation
+ (PlumaDocument *doc);
+
+G_END_DECLS
+
+#endif /* __PLUMA_DOCUMENT_H__ */
diff --git a/pluma/pluma-documents-panel.c b/pluma/pluma-documents-panel.c
new file mode 100755
index 00000000..7a9e221b
--- /dev/null
+++ b/pluma/pluma-documents-panel.c
@@ -0,0 +1,828 @@
+/*
+ * pluma-documents-panel.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pluma-documents-panel.h"
+#include "pluma-utils.h"
+#include "pluma-notebook.h"
+
+#include <glib/gi18n.h>
+
+#define PLUMA_DOCUMENTS_PANEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_DOCUMENTS_PANEL, \
+ PlumaDocumentsPanelPrivate))
+
+struct _PlumaDocumentsPanelPrivate
+{
+ PlumaWindow *window;
+
+ GtkWidget *treeview;
+ GtkTreeModel *model;
+
+ guint adding_tab : 1;
+ guint is_reodering : 1;
+};
+
+G_DEFINE_TYPE(PlumaDocumentsPanel, pluma_documents_panel, GTK_TYPE_VBOX)
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW
+};
+
+enum
+{
+ PIXBUF_COLUMN,
+ NAME_COLUMN,
+ TAB_COLUMN,
+ N_COLUMNS
+};
+
+#define MAX_DOC_NAME_LENGTH 60
+
+static gchar *
+tab_get_name (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ gchar *name;
+ gchar *docname;
+ gchar *tab_name;
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), NULL);
+
+ doc = pluma_tab_get_document (tab);
+
+ name = pluma_document_get_short_name_for_display (doc);
+
+ /* Truncate the name so it doesn't get insanely wide. */
+ docname = pluma_utils_str_middle_truncate (name, MAX_DOC_NAME_LENGTH);
+
+ if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ {
+ if (pluma_document_get_readonly (doc))
+ {
+ tab_name = g_markup_printf_escaped ("<i>%s</i> [<i>%s</i>]",
+ docname,
+ _("Read-Only"));
+ }
+ else
+ {
+ tab_name = g_markup_printf_escaped ("<i>%s</i>",
+ docname);
+ }
+ }
+ else
+ {
+ if (pluma_document_get_readonly (doc))
+ {
+ tab_name = g_markup_printf_escaped ("%s [<i>%s</i>]",
+ docname,
+ _("Read-Only"));
+ }
+ else
+ {
+ tab_name = g_markup_escape_text (docname, -1);
+ }
+ }
+
+ g_free (docname);
+ g_free (name);
+
+ return tab_name;
+}
+
+static void
+get_iter_from_tab (PlumaDocumentsPanel *panel, PlumaTab *tab, GtkTreeIter *iter)
+{
+ gint num;
+ GtkWidget *nb;
+ GtkTreePath *path;
+
+ nb = _pluma_window_get_notebook (panel->priv->window);
+ num = gtk_notebook_page_num (GTK_NOTEBOOK (nb),
+ GTK_WIDGET (tab));
+
+ path = gtk_tree_path_new_from_indices (num, -1);
+ gtk_tree_model_get_iter (panel->priv->model,
+ iter,
+ path);
+ gtk_tree_path_free (path);
+}
+
+static void
+window_active_tab_changed (PlumaWindow *window,
+ PlumaTab *tab,
+ PlumaDocumentsPanel *panel)
+{
+ g_return_if_fail (tab != NULL);
+
+ if (!_pluma_window_is_removing_tabs (window))
+ {
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ get_iter_from_tab (panel, tab, &iter);
+
+ if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (panel->priv->model),
+ &iter))
+ {
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (panel->priv->treeview));
+
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+ }
+}
+
+static void
+refresh_list (PlumaDocumentsPanel *panel)
+{
+ /* TODO: refresh the list only if the panel is visible */
+
+ GList *tabs;
+ GList *l;
+ GtkWidget *nb;
+ GtkListStore *list_store;
+ PlumaTab *active_tab;
+
+ /* g_debug ("refresh_list"); */
+
+ list_store = GTK_LIST_STORE (panel->priv->model);
+
+ gtk_list_store_clear (list_store);
+
+ active_tab = pluma_window_get_active_tab (panel->priv->window);
+
+ nb = _pluma_window_get_notebook (panel->priv->window);
+
+ tabs = gtk_container_get_children (GTK_CONTAINER (nb));
+ l = tabs;
+
+ panel->priv->adding_tab = TRUE;
+
+ while (l != NULL)
+ {
+ GdkPixbuf *pixbuf;
+ gchar *name;
+ GtkTreeIter iter;
+
+ name = tab_get_name (PLUMA_TAB (l->data));
+ pixbuf = _pluma_tab_get_icon (PLUMA_TAB (l->data));
+
+ /* Add a new row to the model */
+ gtk_list_store_append (list_store, &iter);
+ gtk_list_store_set (list_store,
+ &iter,
+ PIXBUF_COLUMN, pixbuf,
+ NAME_COLUMN, name,
+ TAB_COLUMN, l->data,
+ -1);
+
+ g_free (name);
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+
+ if (l->data == active_tab)
+ {
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (panel->priv->treeview));
+
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+
+ l = g_list_next (l);
+ }
+
+ panel->priv->adding_tab = FALSE;
+
+ g_list_free (tabs);
+}
+
+static void
+sync_name_and_icon (PlumaTab *tab,
+ GParamSpec *pspec,
+ PlumaDocumentsPanel *panel)
+{
+ GdkPixbuf *pixbuf;
+ gchar *name;
+ GtkTreeIter iter;
+
+ get_iter_from_tab (panel, tab, &iter);
+
+ name = tab_get_name (tab);
+ pixbuf = _pluma_tab_get_icon (tab);
+
+ gtk_list_store_set (GTK_LIST_STORE (panel->priv->model),
+ &iter,
+ PIXBUF_COLUMN, pixbuf,
+ NAME_COLUMN, name,
+ TAB_COLUMN, tab,
+ -1);
+
+ g_free (name);
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+}
+
+static void
+window_tab_removed (PlumaWindow *window,
+ PlumaTab *tab,
+ PlumaDocumentsPanel *panel)
+{
+ g_signal_handlers_disconnect_by_func (tab,
+ G_CALLBACK (sync_name_and_icon),
+ panel);
+
+ if (_pluma_window_is_removing_tabs (window))
+ gtk_list_store_clear (GTK_LIST_STORE (panel->priv->model));
+ else
+ refresh_list (panel);
+}
+
+static void
+window_tab_added (PlumaWindow *window,
+ PlumaTab *tab,
+ PlumaDocumentsPanel *panel)
+{
+ GtkTreeIter iter;
+ GtkTreeIter sibling;
+ GdkPixbuf *pixbuf;
+ gchar *name;
+
+ g_signal_connect (tab,
+ "notify::name",
+ G_CALLBACK (sync_name_and_icon),
+ panel);
+
+ g_signal_connect (tab,
+ "notify::state",
+ G_CALLBACK (sync_name_and_icon),
+ panel);
+
+ get_iter_from_tab (panel, tab, &sibling);
+
+ panel->priv->adding_tab = TRUE;
+
+ if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (panel->priv->model),
+ &sibling))
+ {
+ gtk_list_store_insert_after (GTK_LIST_STORE (panel->priv->model),
+ &iter,
+ &sibling);
+ }
+ else
+ {
+ PlumaTab *active_tab;
+
+ gtk_list_store_append (GTK_LIST_STORE (panel->priv->model),
+ &iter);
+
+ active_tab = pluma_window_get_active_tab (panel->priv->window);
+
+ if (tab == active_tab)
+ {
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (panel->priv->treeview));
+
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+ }
+
+ name = tab_get_name (tab);
+ pixbuf = _pluma_tab_get_icon (tab);
+
+ gtk_list_store_set (GTK_LIST_STORE (panel->priv->model),
+ &iter,
+ PIXBUF_COLUMN, pixbuf,
+ NAME_COLUMN, name,
+ TAB_COLUMN, tab,
+ -1);
+
+ g_free (name);
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+
+ panel->priv->adding_tab = FALSE;
+}
+
+static void
+window_tabs_reordered (PlumaWindow *window,
+ PlumaDocumentsPanel *panel)
+{
+ if (panel->priv->is_reodering)
+ return;
+
+ refresh_list (panel);
+}
+
+static void
+set_window (PlumaDocumentsPanel *panel,
+ PlumaWindow *window)
+{
+ g_return_if_fail (panel->priv->window == NULL);
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ panel->priv->window = g_object_ref (window);
+
+ g_signal_connect (window,
+ "tab_added",
+ G_CALLBACK (window_tab_added),
+ panel);
+ g_signal_connect (window,
+ "tab_removed",
+ G_CALLBACK (window_tab_removed),
+ panel);
+ g_signal_connect (window,
+ "tabs_reordered",
+ G_CALLBACK (window_tabs_reordered),
+ panel);
+ g_signal_connect (window,
+ "active_tab_changed",
+ G_CALLBACK (window_active_tab_changed),
+ panel);
+}
+
+static void
+treeview_cursor_changed (GtkTreeView *view,
+ PlumaDocumentsPanel *panel)
+{
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+ gpointer tab;
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (panel->priv->treeview));
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ gtk_tree_model_get (panel->priv->model,
+ &iter,
+ TAB_COLUMN,
+ &tab,
+ -1);
+
+ if (pluma_window_get_active_tab (panel->priv->window) != tab)
+ {
+ pluma_window_set_active_tab (panel->priv->window,
+ PLUMA_TAB (tab));
+ }
+ }
+}
+
+static void
+pluma_documents_panel_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentsPanel *panel = PLUMA_DOCUMENTS_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ set_window (panel, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_documents_panel_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaDocumentsPanel *panel = PLUMA_DOCUMENTS_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value,
+ PLUMA_DOCUMENTS_PANEL_GET_PRIVATE (panel)->window);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_documents_panel_finalize (GObject *object)
+{
+ /* PlumaDocumentsPanel *tab = PLUMA_DOCUMENTS_PANEL (object); */
+
+ /* TODO: disconnect signal with window */
+
+ G_OBJECT_CLASS (pluma_documents_panel_parent_class)->finalize (object);
+}
+
+static void
+pluma_documents_panel_dispose (GObject *object)
+{
+ PlumaDocumentsPanel *panel = PLUMA_DOCUMENTS_PANEL (object);
+
+ if (panel->priv->window != NULL)
+ {
+ g_object_unref (panel->priv->window);
+ panel->priv->window = NULL;
+ }
+
+ G_OBJECT_CLASS (pluma_documents_panel_parent_class)->dispose (object);
+}
+
+static void
+pluma_documents_panel_class_init (PlumaDocumentsPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_documents_panel_finalize;
+ object_class->dispose = pluma_documents_panel_dispose;
+ object_class->get_property = pluma_documents_panel_get_property;
+ object_class->set_property = pluma_documents_panel_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_object ("window",
+ "Window",
+ "The PlumaWindow this PlumaDocumentsPanel is associated with",
+ PLUMA_TYPE_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof (PlumaDocumentsPanelPrivate));
+}
+
+static GtkTreePath *
+get_current_path (PlumaDocumentsPanel *panel)
+{
+ gint num;
+ GtkWidget *nb;
+ GtkTreePath *path;
+
+ nb = _pluma_window_get_notebook (panel->priv->window);
+ num = gtk_notebook_get_current_page (GTK_NOTEBOOK (nb));
+
+ path = gtk_tree_path_new_from_indices (num, -1);
+
+ return path;
+}
+
+static void
+menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ PlumaDocumentsPanel *panel)
+{
+ GtkTreePath *path;
+ GdkRectangle rect;
+ gint wx, wy;
+ GtkRequisition requisition;
+ GtkWidget *w;
+
+ w = panel->priv->treeview;
+
+ path = get_current_path (panel);
+
+ gtk_tree_view_get_cell_area (GTK_TREE_VIEW (w),
+ path,
+ NULL,
+ &rect);
+
+ wx = rect.x;
+ wy = rect.y;
+
+ gdk_window_get_origin (w->window, x, y);
+
+ gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
+
+ if (gtk_widget_get_direction (w) == GTK_TEXT_DIR_RTL)
+ {
+ *x += w->allocation.x + w->allocation.width - requisition.width - 10;
+ }
+ else
+ {
+ *x += w->allocation.x + 10;
+ }
+
+ wy = MAX (*y + 5, *y + wy + 5);
+ wy = MIN (wy, *y + w->allocation.height - requisition.height - 5);
+
+ *y = wy;
+
+ *push_in = TRUE;
+}
+
+static gboolean
+show_popup_menu (PlumaDocumentsPanel *panel,
+ GdkEventButton *event)
+{
+ GtkWidget *menu;
+
+ menu = gtk_ui_manager_get_widget (pluma_window_get_ui_manager (panel->priv->window),
+ "/NotebookPopup");
+ g_return_val_if_fail (menu != NULL, FALSE);
+
+ if (event != NULL)
+ {
+ gtk_menu_popup (GTK_MENU (menu),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ event->button,
+ event->time);
+ }
+ else
+ {
+ gtk_menu_popup (GTK_MENU (menu),
+ NULL,
+ NULL,
+ (GtkMenuPositionFunc) menu_position,
+ panel,
+ 0,
+ gtk_get_current_event_time ());
+
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+panel_button_press_event (GtkTreeView *treeview,
+ GdkEventButton *event,
+ PlumaDocumentsPanel *panel)
+{
+ if ((GDK_BUTTON_PRESS == event->type) && (3 == event->button))
+ {
+ GtkTreePath* path = NULL;
+
+ if (event->window == gtk_tree_view_get_bin_window (treeview))
+ {
+ /* Change the cursor position */
+ if (gtk_tree_view_get_path_at_pos (treeview,
+ event->x,
+ event->y,
+ &path,
+ NULL,
+ NULL,
+ NULL))
+ {
+
+ gtk_tree_view_set_cursor (treeview,
+ path,
+ NULL,
+ FALSE);
+
+ gtk_tree_path_free (path);
+
+ /* A row exists at mouse position */
+ return show_popup_menu (panel, event);
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+panel_popup_menu (GtkWidget *treeview,
+ PlumaDocumentsPanel *panel)
+{
+ /* Only respond if the treeview is the actual focus */
+ if (gtk_window_get_focus (GTK_WINDOW (panel->priv->window)) == treeview)
+ {
+ return show_popup_menu (panel, NULL);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+treeview_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip,
+ gpointer data)
+{
+ GtkTreeIter iter;
+ GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+ GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
+ GtkTreePath *path = NULL;
+ gpointer *tab;
+ gchar *tip;
+
+ if (keyboard_tip)
+ {
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+ if (path == NULL)
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ gint bin_x, bin_y;
+
+ gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
+ x, y,
+ &bin_x, &bin_y);
+
+ if (!gtk_tree_view_get_path_at_pos (tree_view,
+ bin_x, bin_y,
+ &path,
+ NULL, NULL, NULL))
+ {
+ return FALSE;
+ }
+ }
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model,
+ &iter,
+ TAB_COLUMN,
+ &tab,
+ -1);
+
+ tip = _pluma_tab_get_tooltips (PLUMA_TAB (tab));
+ gtk_tooltip_set_markup (tooltip, tip);
+
+ g_free (tip);
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
+
+static void
+treeview_row_inserted (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ PlumaDocumentsPanel *panel)
+{
+ PlumaTab *tab;
+ gint *indeces;
+ GtkWidget *nb;
+ gint old_position;
+ gint new_position;
+
+ if (panel->priv->adding_tab)
+ return;
+
+ tab = pluma_window_get_active_tab (panel->priv->window);
+ g_return_if_fail (tab != NULL);
+
+ panel->priv->is_reodering = TRUE;
+
+ indeces = gtk_tree_path_get_indices (path);
+
+ /* g_debug ("New Index: %d (path: %s)", indeces[0], gtk_tree_path_to_string (path));*/
+
+ nb = _pluma_window_get_notebook (panel->priv->window);
+
+ new_position = indeces[0];
+ old_position = gtk_notebook_page_num (GTK_NOTEBOOK (nb),
+ GTK_WIDGET (tab));
+ if (new_position > old_position)
+ new_position = MAX (0, new_position - 1);
+
+ pluma_notebook_reorder_tab (PLUMA_NOTEBOOK (nb),
+ tab,
+ new_position);
+
+ panel->priv->is_reodering = FALSE;
+}
+
+static void
+pluma_documents_panel_init (PlumaDocumentsPanel *panel)
+{
+ GtkWidget *sw;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkTreeSelection *selection;
+
+ panel->priv = PLUMA_DOCUMENTS_PANEL_GET_PRIVATE (panel);
+
+ panel->priv->adding_tab = FALSE;
+ panel->priv->is_reodering = FALSE;
+
+ /* Create the scrolled window */
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ g_return_if_fail (sw != NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_widget_show (sw);
+ gtk_box_pack_start (GTK_BOX (panel), sw, TRUE, TRUE, 0);
+
+ /* Create the empty model */
+ panel->priv->model = GTK_TREE_MODEL (gtk_list_store_new (N_COLUMNS,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_POINTER));
+
+ /* Create the treeview */
+ panel->priv->treeview = gtk_tree_view_new_with_model (panel->priv->model);
+ g_object_unref (G_OBJECT (panel->priv->model));
+ gtk_container_add (GTK_CONTAINER (sw), panel->priv->treeview);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (panel->priv->treeview), FALSE);
+ gtk_tree_view_set_reorderable (GTK_TREE_VIEW (panel->priv->treeview), TRUE);
+
+ g_object_set (panel->priv->treeview, "has-tooltip", TRUE, NULL);
+
+ gtk_widget_show (panel->priv->treeview);
+
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Documents"));
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_add_attribute (column, cell, "pixbuf", PIXBUF_COLUMN);
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_add_attribute (column, cell, "markup", NAME_COLUMN);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (panel->priv->treeview),
+ column);
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (panel->priv->treeview));
+
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ g_signal_connect (panel->priv->treeview,
+ "cursor_changed",
+ G_CALLBACK (treeview_cursor_changed),
+ panel);
+ g_signal_connect (panel->priv->treeview,
+ "button-press-event",
+ G_CALLBACK (panel_button_press_event),
+ panel);
+ g_signal_connect (panel->priv->treeview,
+ "popup-menu",
+ G_CALLBACK (panel_popup_menu),
+ panel);
+ g_signal_connect (panel->priv->treeview,
+ "query-tooltip",
+ G_CALLBACK (treeview_query_tooltip),
+ NULL);
+
+ g_signal_connect (panel->priv->model,
+ "row-inserted",
+ G_CALLBACK (treeview_row_inserted),
+ panel);
+}
+
+GtkWidget *
+pluma_documents_panel_new (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return GTK_WIDGET (g_object_new (PLUMA_TYPE_DOCUMENTS_PANEL,
+ "window", window,
+ NULL));
+}
diff --git a/pluma/pluma-documents-panel.h b/pluma/pluma-documents-panel.h
new file mode 100755
index 00000000..2fda1cf7
--- /dev/null
+++ b/pluma/pluma-documents-panel.h
@@ -0,0 +1,85 @@
+/*
+ * pluma-documents-panel.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_DOCUMENTS_PANEL_H__
+#define __PLUMA_DOCUMENTS_PANEL_H__
+
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-window.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_DOCUMENTS_PANEL (pluma_documents_panel_get_type())
+#define PLUMA_DOCUMENTS_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_DOCUMENTS_PANEL, PlumaDocumentsPanel))
+#define PLUMA_DOCUMENTS_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_DOCUMENTS_PANEL, PlumaDocumentsPanelClass))
+#define PLUMA_IS_DOCUMENTS_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_DOCUMENTS_PANEL))
+#define PLUMA_IS_DOCUMENTS_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_DOCUMENTS_PANEL))
+#define PLUMA_DOCUMENTS_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_DOCUMENTS_PANEL, PlumaDocumentsPanelClass))
+
+/* Private structure type */
+typedef struct _PlumaDocumentsPanelPrivate PlumaDocumentsPanelPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaDocumentsPanel PlumaDocumentsPanel;
+
+struct _PlumaDocumentsPanel
+{
+ GtkVBox vbox;
+
+ /*< private > */
+ PlumaDocumentsPanelPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaDocumentsPanelClass PlumaDocumentsPanelClass;
+
+struct _PlumaDocumentsPanelClass
+{
+ GtkVBoxClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType pluma_documents_panel_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_documents_panel_new (PlumaWindow *window);
+
+G_END_DECLS
+
+#endif /* __PLUMA_DOCUMENTS_PANEL_H__ */
diff --git a/pluma/pluma-encodings-combo-box.c b/pluma/pluma-encodings-combo-box.c
new file mode 100755
index 00000000..4eb8655b
--- /dev/null
+++ b/pluma/pluma-encodings-combo-box.c
@@ -0,0 +1,468 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-encodings-combo-box.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2003-2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: pluma-encodings-combo-box.c 6112 2008-01-23 08:26:24Z sfre $
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-encodings-combo-box.h>
+#include <pluma/pluma-prefs-manager.h>
+#include <pluma/dialogs/pluma-encodings-dialog.h>
+
+#define ENCODING_KEY "Enconding"
+
+#define PLUMA_ENCODINGS_COMBO_BOX_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_ENCODINGS_COMBO_BOX, \
+ PlumaEncodingsComboBoxPrivate))
+
+struct _PlumaEncodingsComboBoxPrivate
+{
+ GtkListStore *store;
+ glong changed_id;
+
+ guint activated_item;
+
+ guint save_mode : 1;
+};
+
+enum
+{
+ NAME_COLUMN,
+ ENCODING_COLUMN,
+ ADD_COLUMN,
+ N_COLUMNS
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_SAVE_MODE
+};
+
+
+G_DEFINE_TYPE(PlumaEncodingsComboBox, pluma_encodings_combo_box, GTK_TYPE_COMBO_BOX)
+
+static void update_menu (PlumaEncodingsComboBox *combo_box);
+
+static void
+pluma_encodings_combo_box_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaEncodingsComboBox *combo;
+
+ combo = PLUMA_ENCODINGS_COMBO_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_SAVE_MODE:
+ combo->priv->save_mode = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_encodings_combo_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaEncodingsComboBox *combo;
+
+ combo = PLUMA_ENCODINGS_COMBO_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_SAVE_MODE:
+ g_value_set_boolean (value, combo->priv->save_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_encodings_combo_box_dispose (GObject *object)
+{
+ PlumaEncodingsComboBox *combo = PLUMA_ENCODINGS_COMBO_BOX (object);
+
+ if (combo->priv->store != NULL)
+ {
+ g_object_unref (combo->priv->store);
+ combo->priv->store = NULL;
+ }
+
+ G_OBJECT_CLASS (pluma_encodings_combo_box_parent_class)->dispose (object);
+}
+
+static void
+pluma_encodings_combo_box_class_init (PlumaEncodingsComboBoxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = pluma_encodings_combo_box_set_property;
+ object_class->get_property = pluma_encodings_combo_box_get_property;
+ object_class->dispose = pluma_encodings_combo_box_dispose;
+
+ g_object_class_install_property (object_class,
+ PROP_SAVE_MODE,
+ g_param_spec_boolean ("save-mode",
+ "Save Mode",
+ "Save Mode",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof (PlumaEncodingsComboBoxPrivate));
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ PlumaEncodingsComboBox *menu)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ update_menu (menu);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+add_or_remove (PlumaEncodingsComboBox *menu,
+ GtkTreeModel *model)
+{
+ GtkTreeIter iter;
+ gboolean add_item = FALSE;
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (menu), &iter))
+ {
+ gtk_tree_model_get (model, &iter,
+ ADD_COLUMN, &add_item,
+ -1);
+ }
+
+ if (!add_item)
+ {
+ menu->priv->activated_item = gtk_combo_box_get_active (GTK_COMBO_BOX (menu));
+ }
+ else
+ {
+ GtkWidget *dialog;
+
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (menu));
+
+#if !GTK_CHECK_VERSION (2, 18, 0)
+ if (!GTK_WIDGET_TOPLEVEL (toplevel))
+#else
+ if (!gtk_widget_is_toplevel (toplevel))
+#endif
+ toplevel = NULL;
+
+ g_signal_handler_block (menu, menu->priv->changed_id);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (menu),
+ menu->priv->activated_item);
+ g_signal_handler_unblock (menu, menu->priv->changed_id);
+
+ dialog = pluma_encodings_dialog_new();
+
+ if (toplevel != NULL)
+ {
+ GtkWindowGroup *wg;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (toplevel));
+
+ wg = GTK_WINDOW (toplevel)->group;
+ if (wg == NULL)
+ {
+ wg = gtk_window_group_new ();
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (toplevel));
+ }
+
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (dialog));
+ }
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (dialog_response_cb),
+ menu);
+
+ gtk_widget_show (dialog);
+ }
+}
+
+static gboolean
+separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ gchar *str;
+ gboolean ret;
+
+ gtk_tree_model_get (model, iter, NAME_COLUMN, &str, -1);
+ ret = (str == NULL || *str == '\0');
+ g_free (str);
+
+ return ret;
+}
+
+static void
+update_menu (PlumaEncodingsComboBox *menu)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GSList *encodings, *l;
+ gchar *str;
+ const PlumaEncoding *utf8_encoding;
+ const PlumaEncoding *current_encoding;
+
+ store = menu->priv->store;
+
+ /* Unset the previous model */
+ g_signal_handler_block (menu, menu->priv->changed_id);
+ gtk_list_store_clear (store);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (menu),
+ NULL);
+
+ utf8_encoding = pluma_encoding_get_utf8 ();
+ current_encoding = pluma_encoding_get_current ();
+
+ if (!menu->priv->save_mode)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, _("Automatically Detected"),
+ ENCODING_COLUMN, NULL,
+ ADD_COLUMN, FALSE,
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, "",
+ ENCODING_COLUMN, NULL,
+ ADD_COLUMN, FALSE,
+ -1);
+ }
+
+ if (current_encoding != utf8_encoding)
+ str = pluma_encoding_to_string (utf8_encoding);
+ else
+ str = g_strdup_printf (_("Current Locale (%s)"),
+ pluma_encoding_get_charset (utf8_encoding));
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, str,
+ ENCODING_COLUMN, utf8_encoding,
+ ADD_COLUMN, FALSE,
+ -1);
+
+ g_free (str);
+
+ if ((utf8_encoding != current_encoding) &&
+ (current_encoding != NULL))
+ {
+ str = g_strdup_printf (_("Current Locale (%s)"),
+ pluma_encoding_get_charset (current_encoding));
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, str,
+ ENCODING_COLUMN, current_encoding,
+ ADD_COLUMN, FALSE,
+ -1);
+
+ g_free (str);
+ }
+
+ encodings = pluma_prefs_manager_get_shown_in_menu_encodings ();
+
+ for (l = encodings; l != NULL; l = g_slist_next (l))
+ {
+ const PlumaEncoding *enc = (const PlumaEncoding *)l->data;
+
+ if ((enc != current_encoding) &&
+ (enc != utf8_encoding) &&
+ (enc != NULL))
+ {
+ str = pluma_encoding_to_string (enc);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, str,
+ ENCODING_COLUMN, enc,
+ ADD_COLUMN, FALSE,
+ -1);
+
+ g_free (str);
+ }
+ }
+
+ g_slist_free (encodings);
+
+ if (pluma_prefs_manager_shown_in_menu_encodings_can_set ())
+ {
+ gtk_list_store_append (store, &iter);
+ /* separator */
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, "",
+ ENCODING_COLUMN, NULL,
+ ADD_COLUMN, FALSE,
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ NAME_COLUMN, _("Add or Remove..."),
+ ENCODING_COLUMN, NULL,
+ ADD_COLUMN, TRUE,
+ -1);
+ }
+
+ /* set the model back */
+ gtk_combo_box_set_model (GTK_COMBO_BOX (menu),
+ GTK_TREE_MODEL (menu->priv->store));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (menu), 0);
+
+ g_signal_handler_unblock (menu, menu->priv->changed_id);
+}
+
+static void
+pluma_encodings_combo_box_init (PlumaEncodingsComboBox *menu)
+{
+ GtkCellRenderer *text_renderer;
+
+ menu->priv = PLUMA_ENCODINGS_COMBO_BOX_GET_PRIVATE (menu);
+
+ menu->priv->store = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_POINTER,
+ G_TYPE_BOOLEAN);
+
+ /* Setup up the cells */
+ text_renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (menu),
+ text_renderer, TRUE);
+
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (menu),
+ text_renderer,
+ "text",
+ NAME_COLUMN,
+ NULL);
+
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (menu),
+ separator_func, NULL,
+ NULL);
+
+ menu->priv->changed_id = g_signal_connect (menu, "changed",
+ G_CALLBACK (add_or_remove),
+ menu->priv->store);
+
+ update_menu (menu);
+}
+
+GtkWidget *
+pluma_encodings_combo_box_new (gboolean save_mode)
+{
+ return g_object_new (PLUMA_TYPE_ENCODINGS_COMBO_BOX,
+ "save_mode", save_mode,
+ NULL);
+}
+
+const PlumaEncoding *
+pluma_encodings_combo_box_get_selected_encoding (PlumaEncodingsComboBox *menu)
+{
+ GtkTreeIter iter;
+
+ g_return_val_if_fail (PLUMA_IS_ENCODINGS_COMBO_BOX (menu), NULL);
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (menu), &iter))
+ {
+ const PlumaEncoding *ret;
+ GtkTreeModel *model;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (menu));
+
+ gtk_tree_model_get (model, &iter,
+ ENCODING_COLUMN, &ret,
+ -1);
+
+ return ret;
+ }
+
+ return NULL;
+}
+
+void
+pluma_encodings_combo_box_set_selected_encoding (PlumaEncodingsComboBox *menu,
+ const PlumaEncoding *encoding)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean b;
+ g_return_if_fail (PLUMA_IS_ENCODINGS_COMBO_BOX (menu));
+ g_return_if_fail (GTK_IS_COMBO_BOX (menu));
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (menu));
+ b = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (b)
+ {
+ const PlumaEncoding *enc;
+
+ gtk_tree_model_get (model, &iter,
+ ENCODING_COLUMN, &enc,
+ -1);
+
+ if (enc == encoding)
+ {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (menu),
+ &iter);
+
+ return;
+ }
+
+ b = gtk_tree_model_iter_next (model, &iter);
+ }
+}
diff --git a/pluma/pluma-encodings-combo-box.h b/pluma/pluma-encodings-combo-box.h
new file mode 100755
index 00000000..40518775
--- /dev/null
+++ b/pluma/pluma-encodings-combo-box.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-encodings-combo-box.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2003-2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: pluma-encodings-option-menu.h 4429 2005-12-12 17:28:04Z pborelli $
+ */
+
+#ifndef __PLUMA_ENCODINGS_COMBO_BOX_H__
+#define __PLUMA_ENCODINGS_COMBO_BOX_H__
+
+#include <gtk/gtkoptionmenu.h>
+#include <pluma/pluma-encodings.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_ENCODINGS_COMBO_BOX (pluma_encodings_combo_box_get_type ())
+#define PLUMA_ENCODINGS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_ENCODINGS_COMBO_BOX, PlumaEncodingsComboBox))
+#define PLUMA_ENCODINGS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_ENCODINGS_COMBO_BOX, PlumaEncodingsComboBoxClass))
+#define PLUMA_IS_ENCODINGS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_ENCODINGS_COMBO_BOX))
+#define PLUMA_IS_ENCODINGS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_ENCODINGS_COMBO_BOX))
+#define PLUMA_ENCODINGS_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_ENCODINGS_COMBO_BOX, PlumaEncodingsComboBoxClass))
+
+
+typedef struct _PlumaEncodingsComboBox PlumaEncodingsComboBox;
+typedef struct _PlumaEncodingsComboBoxClass PlumaEncodingsComboBoxClass;
+
+typedef struct _PlumaEncodingsComboBoxPrivate PlumaEncodingsComboBoxPrivate;
+
+struct _PlumaEncodingsComboBox
+{
+ GtkComboBox parent;
+
+ PlumaEncodingsComboBoxPrivate *priv;
+};
+
+struct _PlumaEncodingsComboBoxClass
+{
+ GtkComboBoxClass parent_class;
+};
+
+GType pluma_encodings_combo_box_get_type (void) G_GNUC_CONST;
+
+/* Constructor */
+GtkWidget *pluma_encodings_combo_box_new (gboolean save_mode);
+
+const PlumaEncoding *pluma_encodings_combo_box_get_selected_encoding (PlumaEncodingsComboBox *menu);
+void pluma_encodings_combo_box_set_selected_encoding (PlumaEncodingsComboBox *menu,
+ const PlumaEncoding *encoding);
+
+G_END_DECLS
+
+#endif /* __PLUMA_ENCODINGS_COMBO_BOX_H__ */
+
+
diff --git a/pluma/pluma-encodings.c b/pluma/pluma-encodings.c
new file mode 100755
index 00000000..9232a618
--- /dev/null
+++ b/pluma/pluma-encodings.c
@@ -0,0 +1,473 @@
+/*
+ * pluma-encodings.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "pluma-encodings.h"
+
+
+struct _PlumaEncoding
+{
+ gint index;
+ const gchar *charset;
+ const gchar *name;
+};
+
+/*
+ * The original versions of the following tables are taken from profterm
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ */
+
+typedef enum
+{
+
+ PLUMA_ENCODING_ISO_8859_1,
+ PLUMA_ENCODING_ISO_8859_2,
+ PLUMA_ENCODING_ISO_8859_3,
+ PLUMA_ENCODING_ISO_8859_4,
+ PLUMA_ENCODING_ISO_8859_5,
+ PLUMA_ENCODING_ISO_8859_6,
+ PLUMA_ENCODING_ISO_8859_7,
+ PLUMA_ENCODING_ISO_8859_8,
+ PLUMA_ENCODING_ISO_8859_9,
+ PLUMA_ENCODING_ISO_8859_10,
+ PLUMA_ENCODING_ISO_8859_13,
+ PLUMA_ENCODING_ISO_8859_14,
+ PLUMA_ENCODING_ISO_8859_15,
+ PLUMA_ENCODING_ISO_8859_16,
+
+ PLUMA_ENCODING_UTF_7,
+ PLUMA_ENCODING_UTF_16,
+ PLUMA_ENCODING_UTF_16_BE,
+ PLUMA_ENCODING_UTF_16_LE,
+ PLUMA_ENCODING_UTF_32,
+ PLUMA_ENCODING_UCS_2,
+ PLUMA_ENCODING_UCS_4,
+
+ PLUMA_ENCODING_ARMSCII_8,
+ PLUMA_ENCODING_BIG5,
+ PLUMA_ENCODING_BIG5_HKSCS,
+ PLUMA_ENCODING_CP_866,
+
+ PLUMA_ENCODING_EUC_JP,
+ PLUMA_ENCODING_EUC_JP_MS,
+ PLUMA_ENCODING_CP932,
+ PLUMA_ENCODING_EUC_KR,
+ PLUMA_ENCODING_EUC_TW,
+
+ PLUMA_ENCODING_GB18030,
+ PLUMA_ENCODING_GB2312,
+ PLUMA_ENCODING_GBK,
+ PLUMA_ENCODING_GEOSTD8,
+
+ PLUMA_ENCODING_IBM_850,
+ PLUMA_ENCODING_IBM_852,
+ PLUMA_ENCODING_IBM_855,
+ PLUMA_ENCODING_IBM_857,
+ PLUMA_ENCODING_IBM_862,
+ PLUMA_ENCODING_IBM_864,
+
+ PLUMA_ENCODING_ISO_2022_JP,
+ PLUMA_ENCODING_ISO_2022_KR,
+ PLUMA_ENCODING_ISO_IR_111,
+ PLUMA_ENCODING_JOHAB,
+ PLUMA_ENCODING_KOI8_R,
+ PLUMA_ENCODING_KOI8__R,
+ PLUMA_ENCODING_KOI8_U,
+
+ PLUMA_ENCODING_SHIFT_JIS,
+ PLUMA_ENCODING_TCVN,
+ PLUMA_ENCODING_TIS_620,
+ PLUMA_ENCODING_UHC,
+ PLUMA_ENCODING_VISCII,
+
+ PLUMA_ENCODING_WINDOWS_1250,
+ PLUMA_ENCODING_WINDOWS_1251,
+ PLUMA_ENCODING_WINDOWS_1252,
+ PLUMA_ENCODING_WINDOWS_1253,
+ PLUMA_ENCODING_WINDOWS_1254,
+ PLUMA_ENCODING_WINDOWS_1255,
+ PLUMA_ENCODING_WINDOWS_1256,
+ PLUMA_ENCODING_WINDOWS_1257,
+ PLUMA_ENCODING_WINDOWS_1258,
+
+ PLUMA_ENCODING_LAST,
+
+ PLUMA_ENCODING_UTF_8,
+ PLUMA_ENCODING_UNKNOWN
+
+} PlumaEncodingIndex;
+
+static const PlumaEncoding utf8_encoding = {
+ PLUMA_ENCODING_UTF_8,
+ "UTF-8",
+ N_("Unicode")
+};
+
+/* initialized in pluma_encoding_lazy_init() */
+static PlumaEncoding unknown_encoding = {
+ PLUMA_ENCODING_UNKNOWN,
+ NULL,
+ NULL
+};
+
+static const PlumaEncoding encodings [] = {
+
+ { PLUMA_ENCODING_ISO_8859_1,
+ "ISO-8859-1", N_("Western") },
+ { PLUMA_ENCODING_ISO_8859_2,
+ "ISO-8859-2", N_("Central European") },
+ { PLUMA_ENCODING_ISO_8859_3,
+ "ISO-8859-3", N_("South European") },
+ { PLUMA_ENCODING_ISO_8859_4,
+ "ISO-8859-4", N_("Baltic") },
+ { PLUMA_ENCODING_ISO_8859_5,
+ "ISO-8859-5", N_("Cyrillic") },
+ { PLUMA_ENCODING_ISO_8859_6,
+ "ISO-8859-6", N_("Arabic") },
+ { PLUMA_ENCODING_ISO_8859_7,
+ "ISO-8859-7", N_("Greek") },
+ { PLUMA_ENCODING_ISO_8859_8,
+ "ISO-8859-8", N_("Hebrew Visual") },
+ { PLUMA_ENCODING_ISO_8859_9,
+ "ISO-8859-9", N_("Turkish") },
+ { PLUMA_ENCODING_ISO_8859_10,
+ "ISO-8859-10", N_("Nordic") },
+ { PLUMA_ENCODING_ISO_8859_13,
+ "ISO-8859-13", N_("Baltic") },
+ { PLUMA_ENCODING_ISO_8859_14,
+ "ISO-8859-14", N_("Celtic") },
+ { PLUMA_ENCODING_ISO_8859_15,
+ "ISO-8859-15", N_("Western") },
+ { PLUMA_ENCODING_ISO_8859_16,
+ "ISO-8859-16", N_("Romanian") },
+
+ { PLUMA_ENCODING_UTF_7,
+ "UTF-7", N_("Unicode") },
+ { PLUMA_ENCODING_UTF_16,
+ "UTF-16", N_("Unicode") },
+ { PLUMA_ENCODING_UTF_16_BE,
+ "UTF-16BE", N_("Unicode") },
+ { PLUMA_ENCODING_UTF_16_LE,
+ "UTF-16LE", N_("Unicode") },
+ { PLUMA_ENCODING_UTF_32,
+ "UTF-32", N_("Unicode") },
+ { PLUMA_ENCODING_UCS_2,
+ "UCS-2", N_("Unicode") },
+ { PLUMA_ENCODING_UCS_4,
+ "UCS-4", N_("Unicode") },
+
+ { PLUMA_ENCODING_ARMSCII_8,
+ "ARMSCII-8", N_("Armenian") },
+ { PLUMA_ENCODING_BIG5,
+ "BIG5", N_("Chinese Traditional") },
+ { PLUMA_ENCODING_BIG5_HKSCS,
+ "BIG5-HKSCS", N_("Chinese Traditional") },
+ { PLUMA_ENCODING_CP_866,
+ "CP866", N_("Cyrillic/Russian") },
+
+ { PLUMA_ENCODING_EUC_JP,
+ "EUC-JP", N_("Japanese") },
+ { PLUMA_ENCODING_EUC_JP_MS,
+ "EUC-JP-MS", N_("Japanese") },
+ { PLUMA_ENCODING_CP932,
+ "CP932", N_("Japanese") },
+
+ { PLUMA_ENCODING_EUC_KR,
+ "EUC-KR", N_("Korean") },
+ { PLUMA_ENCODING_EUC_TW,
+ "EUC-TW", N_("Chinese Traditional") },
+
+ { PLUMA_ENCODING_GB18030,
+ "GB18030", N_("Chinese Simplified") },
+ { PLUMA_ENCODING_GB2312,
+ "GB2312", N_("Chinese Simplified") },
+ { PLUMA_ENCODING_GBK,
+ "GBK", N_("Chinese Simplified") },
+ { PLUMA_ENCODING_GEOSTD8,
+ "GEORGIAN-ACADEMY", N_("Georgian") }, /* FIXME GEOSTD8 ? */
+
+ { PLUMA_ENCODING_IBM_850,
+ "IBM850", N_("Western") },
+ { PLUMA_ENCODING_IBM_852,
+ "IBM852", N_("Central European") },
+ { PLUMA_ENCODING_IBM_855,
+ "IBM855", N_("Cyrillic") },
+ { PLUMA_ENCODING_IBM_857,
+ "IBM857", N_("Turkish") },
+ { PLUMA_ENCODING_IBM_862,
+ "IBM862", N_("Hebrew") },
+ { PLUMA_ENCODING_IBM_864,
+ "IBM864", N_("Arabic") },
+
+ { PLUMA_ENCODING_ISO_2022_JP,
+ "ISO-2022-JP", N_("Japanese") },
+ { PLUMA_ENCODING_ISO_2022_KR,
+ "ISO-2022-KR", N_("Korean") },
+ { PLUMA_ENCODING_ISO_IR_111,
+ "ISO-IR-111", N_("Cyrillic") },
+ { PLUMA_ENCODING_JOHAB,
+ "JOHAB", N_("Korean") },
+ { PLUMA_ENCODING_KOI8_R,
+ "KOI8R", N_("Cyrillic") },
+ { PLUMA_ENCODING_KOI8__R,
+ "KOI8-R", N_("Cyrillic") },
+ { PLUMA_ENCODING_KOI8_U,
+ "KOI8U", N_("Cyrillic/Ukrainian") },
+
+ { PLUMA_ENCODING_SHIFT_JIS,
+ "SHIFT_JIS", N_("Japanese") },
+ { PLUMA_ENCODING_TCVN,
+ "TCVN", N_("Vietnamese") },
+ { PLUMA_ENCODING_TIS_620,
+ "TIS-620", N_("Thai") },
+ { PLUMA_ENCODING_UHC,
+ "UHC", N_("Korean") },
+ { PLUMA_ENCODING_VISCII,
+ "VISCII", N_("Vietnamese") },
+
+ { PLUMA_ENCODING_WINDOWS_1250,
+ "WINDOWS-1250", N_("Central European") },
+ { PLUMA_ENCODING_WINDOWS_1251,
+ "WINDOWS-1251", N_("Cyrillic") },
+ { PLUMA_ENCODING_WINDOWS_1252,
+ "WINDOWS-1252", N_("Western") },
+ { PLUMA_ENCODING_WINDOWS_1253,
+ "WINDOWS-1253", N_("Greek") },
+ { PLUMA_ENCODING_WINDOWS_1254,
+ "WINDOWS-1254", N_("Turkish") },
+ { PLUMA_ENCODING_WINDOWS_1255,
+ "WINDOWS-1255", N_("Hebrew") },
+ { PLUMA_ENCODING_WINDOWS_1256,
+ "WINDOWS-1256", N_("Arabic") },
+ { PLUMA_ENCODING_WINDOWS_1257,
+ "WINDOWS-1257", N_("Baltic") },
+ { PLUMA_ENCODING_WINDOWS_1258,
+ "WINDOWS-1258", N_("Vietnamese") }
+};
+
+static void
+pluma_encoding_lazy_init (void)
+{
+ static gboolean initialized = FALSE;
+ const gchar *locale_charset;
+
+ if (initialized)
+ return;
+
+ if (g_get_charset (&locale_charset) == FALSE)
+ {
+ unknown_encoding.charset = g_strdup (locale_charset);
+ }
+
+ initialized = TRUE;
+}
+
+const PlumaEncoding *
+pluma_encoding_get_from_charset (const gchar *charset)
+{
+ gint i;
+
+ g_return_val_if_fail (charset != NULL, NULL);
+
+ pluma_encoding_lazy_init ();
+
+ if (charset == NULL)
+ return NULL;
+
+ if (g_ascii_strcasecmp (charset, "UTF-8") == 0)
+ return pluma_encoding_get_utf8 ();
+
+ i = 0;
+ while (i < PLUMA_ENCODING_LAST)
+ {
+ if (g_ascii_strcasecmp (charset, encodings[i].charset) == 0)
+ return &encodings[i];
+
+ ++i;
+ }
+
+ if (unknown_encoding.charset != NULL)
+ {
+ if (g_ascii_strcasecmp (charset, unknown_encoding.charset) == 0)
+ return &unknown_encoding;
+ }
+
+ return NULL;
+}
+
+const PlumaEncoding *
+pluma_encoding_get_from_index (gint idx)
+{
+ g_return_val_if_fail (idx >= 0, NULL);
+
+ if (idx >= PLUMA_ENCODING_LAST)
+ return NULL;
+
+ pluma_encoding_lazy_init ();
+
+ return &encodings[idx];
+}
+
+const PlumaEncoding *
+pluma_encoding_get_utf8 (void)
+{
+ pluma_encoding_lazy_init ();
+
+ return &utf8_encoding;
+}
+
+const PlumaEncoding *
+pluma_encoding_get_current (void)
+{
+ static gboolean initialized = FALSE;
+ static const PlumaEncoding *locale_encoding = NULL;
+
+ const gchar *locale_charset;
+
+ pluma_encoding_lazy_init ();
+
+ if (initialized != FALSE)
+ return locale_encoding;
+
+ if (g_get_charset (&locale_charset) == FALSE)
+ {
+ g_return_val_if_fail (locale_charset != NULL, &utf8_encoding);
+
+ locale_encoding = pluma_encoding_get_from_charset (locale_charset);
+ }
+ else
+ {
+ locale_encoding = &utf8_encoding;
+ }
+
+ if (locale_encoding == NULL)
+ {
+ locale_encoding = &unknown_encoding;
+ }
+
+ g_return_val_if_fail (locale_encoding != NULL, NULL);
+
+ initialized = TRUE;
+
+ return locale_encoding;
+}
+
+gchar *
+pluma_encoding_to_string (const PlumaEncoding* enc)
+{
+ g_return_val_if_fail (enc != NULL, NULL);
+
+ pluma_encoding_lazy_init ();
+
+ g_return_val_if_fail (enc->charset != NULL, NULL);
+
+ if (enc->name != NULL)
+ {
+ return g_strdup_printf ("%s (%s)", _(enc->name), enc->charset);
+ }
+ else
+ {
+ if (g_ascii_strcasecmp (enc->charset, "ANSI_X3.4-1968") == 0)
+ return g_strdup_printf ("US-ASCII (%s)", enc->charset);
+ else
+ return g_strdup (enc->charset);
+ }
+}
+
+const gchar *
+pluma_encoding_get_charset (const PlumaEncoding* enc)
+{
+ g_return_val_if_fail (enc != NULL, NULL);
+
+ pluma_encoding_lazy_init ();
+
+ g_return_val_if_fail (enc->charset != NULL, NULL);
+
+ return enc->charset;
+}
+
+const gchar *
+pluma_encoding_get_name (const PlumaEncoding* enc)
+{
+ g_return_val_if_fail (enc != NULL, NULL);
+
+ pluma_encoding_lazy_init ();
+
+ return (enc->name == NULL) ? _("Unknown") : _(enc->name);
+}
+
+/* These are to make language bindings happy. Since Encodings are
+ * const, copy() just returns the same pointer and fres() doesn't
+ * do nothing */
+
+PlumaEncoding *
+pluma_encoding_copy (const PlumaEncoding *enc)
+{
+ g_return_val_if_fail (enc != NULL, NULL);
+
+ return (PlumaEncoding *) enc;
+}
+
+void
+pluma_encoding_free (PlumaEncoding *enc)
+{
+ g_return_if_fail (enc != NULL);
+}
+
+/**
+ * pluma_encoding_get_type:
+ *
+ * Retrieves the GType object which is associated with the
+ * #PlumaEncoding class.
+ *
+ * Return value: the GType associated with #PlumaEncoding.
+ **/
+GType
+pluma_encoding_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (!our_type)
+ our_type = g_boxed_type_register_static (
+ "PlumaEncoding",
+ (GBoxedCopyFunc) pluma_encoding_copy,
+ (GBoxedFreeFunc) pluma_encoding_free);
+
+ return our_type;
+}
+
diff --git a/pluma/pluma-encodings.h b/pluma/pluma-encodings.h
new file mode 100755
index 00000000..32f3db16
--- /dev/null
+++ b/pluma/pluma-encodings.h
@@ -0,0 +1,62 @@
+/*
+ * pluma-encodings.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_ENCODINGS_H__
+#define __PLUMA_ENCODINGS_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _PlumaEncoding PlumaEncoding;
+
+#define PLUMA_TYPE_ENCODING (pluma_encoding_get_type ())
+
+GType pluma_encoding_get_type (void) G_GNUC_CONST;
+
+const PlumaEncoding *pluma_encoding_get_from_charset (const gchar *charset);
+const PlumaEncoding *pluma_encoding_get_from_index (gint index);
+
+gchar *pluma_encoding_to_string (const PlumaEncoding *enc);
+
+const gchar *pluma_encoding_get_name (const PlumaEncoding *enc);
+const gchar *pluma_encoding_get_charset (const PlumaEncoding *enc);
+
+const PlumaEncoding *pluma_encoding_get_utf8 (void);
+const PlumaEncoding *pluma_encoding_get_current (void);
+
+/* These should not be used, they are just to make python bindings happy */
+PlumaEncoding *pluma_encoding_copy (const PlumaEncoding *enc);
+void pluma_encoding_free (PlumaEncoding *enc);
+
+G_END_DECLS
+
+#endif /* __PLUMA_ENCODINGS_H__ */
diff --git a/pluma/pluma-enum-types.c.template b/pluma/pluma-enum-types.c.template
new file mode 100755
index 00000000..a737ec48
--- /dev/null
+++ b/pluma/pluma-enum-types.c.template
@@ -0,0 +1,39 @@
+/*** BEGIN file-header ***/
+#include "pluma-enum-types.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+#include "@filename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static GType the_type = 0;
+
+ if (the_type == 0)
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@,
+ "@VALUENAME@",
+ "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ the_type = g_@type@_register_static (
+ g_intern_static_string ("@EnumName@"),
+ values);
+ }
+ return the_type;
+}
+
+/*** END value-tail ***/
diff --git a/pluma/pluma-enum-types.h.template b/pluma/pluma-enum-types.h.template
new file mode 100755
index 00000000..4797ee81
--- /dev/null
+++ b/pluma/pluma-enum-types.h.template
@@ -0,0 +1,27 @@
+/*** BEGIN file-header ***/
+#ifndef __PLUMA_ENUM_TYPES_H__
+#define __PLUMA_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* Enumerations from "@filename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+#define PLUMA_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __PLUMA_ENUM_TYPES_H__ */
+/*** END file-tail ***/
+
diff --git a/pluma/pluma-file-chooser-dialog.c b/pluma/pluma-file-chooser-dialog.c
new file mode 100755
index 00000000..788cd1ba
--- /dev/null
+++ b/pluma/pluma-file-chooser-dialog.c
@@ -0,0 +1,560 @@
+/*
+ * pluma-file-chooser-dialog.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005-2007 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+/* TODO: Override set_extra_widget */
+/* TODO: add encoding property */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "pluma-file-chooser-dialog.h"
+#include "pluma-encodings-combo-box.h"
+#include "pluma-language-manager.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-debug.h"
+#include "pluma-enum-types.h"
+
+#define PLUMA_FILE_CHOOSER_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_FILE_CHOOSER_DIALOG, PlumaFileChooserDialogPrivate))
+
+#define ALL_FILES _("All Files")
+#define ALL_TEXT_FILES _("All Text Files")
+
+struct _PlumaFileChooserDialogPrivate
+{
+ GtkWidget *option_menu;
+ GtkWidget *extra_widget;
+
+ GtkWidget *newline_label;
+ GtkWidget *newline_combo;
+ GtkListStore *newline_store;
+};
+
+G_DEFINE_TYPE(PlumaFileChooserDialog, pluma_file_chooser_dialog, GTK_TYPE_FILE_CHOOSER_DIALOG)
+
+static void
+pluma_file_chooser_dialog_class_init (PlumaFileChooserDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof(PlumaFileChooserDialogPrivate));
+}
+
+static void
+create_option_menu (PlumaFileChooserDialog *dialog)
+{
+ GtkWidget *label;
+ GtkWidget *menu;
+
+ label = gtk_label_new_with_mnemonic (_("C_haracter Encoding:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+ menu = pluma_encodings_combo_box_new (
+ gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), menu);
+
+ gtk_box_pack_start (GTK_BOX (dialog->priv->extra_widget),
+ label,
+ FALSE,
+ TRUE,
+ 0);
+
+ gtk_box_pack_start (GTK_BOX (dialog->priv->extra_widget),
+ menu,
+ TRUE,
+ TRUE,
+ 0);
+
+ gtk_widget_show (label);
+ gtk_widget_show (menu);
+
+ dialog->priv->option_menu = menu;
+}
+
+static void
+update_newline_visibility (PlumaFileChooserDialog *dialog)
+{
+ if (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE)
+ {
+ gtk_widget_show (dialog->priv->newline_label);
+ gtk_widget_show (dialog->priv->newline_combo);
+ }
+ else
+ {
+ gtk_widget_hide (dialog->priv->newline_label);
+ gtk_widget_hide (dialog->priv->newline_combo);
+ }
+}
+
+static void
+newline_combo_append (GtkComboBox *combo,
+ GtkListStore *store,
+ GtkTreeIter *iter,
+ const gchar *label,
+ PlumaDocumentNewlineType newline_type)
+{
+ gtk_list_store_append (store, iter);
+ gtk_list_store_set (store, iter, 0, label, 1, newline_type, -1);
+
+ if (newline_type == PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT)
+ {
+ gtk_combo_box_set_active_iter (combo, iter);
+ }
+}
+
+static void
+create_newline_combo (PlumaFileChooserDialog *dialog)
+{
+ GtkWidget *label, *combo;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+
+ label = gtk_label_new_with_mnemonic (_("L_ine Ending:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, PLUMA_TYPE_DOCUMENT_NEWLINE_TYPE);
+ combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+ renderer = gtk_cell_renderer_text_new ();
+
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo),
+ renderer,
+ TRUE);
+
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo),
+ renderer,
+ "text",
+ 0);
+
+ newline_combo_append (GTK_COMBO_BOX (combo),
+ store,
+ &iter,
+ _("Unix/Linux"),
+ PLUMA_DOCUMENT_NEWLINE_TYPE_LF);
+
+ newline_combo_append (GTK_COMBO_BOX (combo),
+ store,
+ &iter,
+ _("Mac OS Classic"),
+ PLUMA_DOCUMENT_NEWLINE_TYPE_CR);
+
+ newline_combo_append (GTK_COMBO_BOX (combo),
+ store,
+ &iter,
+ _("Windows"),
+ PLUMA_DOCUMENT_NEWLINE_TYPE_CR_LF);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ gtk_box_pack_start (GTK_BOX (dialog->priv->extra_widget),
+ label,
+ FALSE,
+ TRUE,
+ 0);
+
+ gtk_box_pack_start (GTK_BOX (dialog->priv->extra_widget),
+ combo,
+ TRUE,
+ TRUE,
+ 0);
+
+ dialog->priv->newline_combo = combo;
+ dialog->priv->newline_label = label;
+ dialog->priv->newline_store = store;
+
+ update_newline_visibility (dialog);
+}
+
+static void
+create_extra_widget (PlumaFileChooserDialog *dialog)
+{
+ dialog->priv->extra_widget = gtk_hbox_new (FALSE, 6);
+
+ gtk_widget_show (dialog->priv->extra_widget);
+
+ create_option_menu (dialog);
+ create_newline_combo (dialog);
+
+ gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog),
+ dialog->priv->extra_widget);
+
+}
+
+static void
+action_changed (PlumaFileChooserDialog *dialog,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ GtkFileChooserAction action;
+
+ action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog));
+
+ switch (action)
+ {
+ case GTK_FILE_CHOOSER_ACTION_OPEN:
+ g_object_set (dialog->priv->option_menu,
+ "save_mode", FALSE,
+ NULL);
+ gtk_widget_show (dialog->priv->option_menu);
+ break;
+ case GTK_FILE_CHOOSER_ACTION_SAVE:
+ g_object_set (dialog->priv->option_menu,
+ "save_mode", TRUE,
+ NULL);
+ gtk_widget_show (dialog->priv->option_menu);
+ break;
+ default:
+ gtk_widget_hide (dialog->priv->option_menu);
+ }
+
+ update_newline_visibility (dialog);
+}
+
+static void
+filter_changed (PlumaFileChooserDialog *dialog,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ GtkFileFilter *filter;
+
+ if (!pluma_prefs_manager_active_file_filter_can_set ())
+ return;
+
+ filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (dialog));
+ if (filter != NULL)
+ {
+ const gchar *name;
+ gint id = 0;
+
+ name = gtk_file_filter_get_name (filter);
+ g_return_if_fail (name != NULL);
+
+ if (strcmp (name, ALL_TEXT_FILES) == 0)
+ id = 1;
+
+ pluma_debug_message (DEBUG_COMMANDS, "Active filter: %s (%d)", name, id);
+
+ pluma_prefs_manager_set_active_file_filter (id);
+ }
+}
+
+/* FIXME: use globs too - Paolo (Aug. 27, 2007) */
+static gboolean
+all_text_files_filter (const GtkFileFilterInfo *filter_info,
+ gpointer data)
+{
+ static GSList *known_mime_types = NULL;
+ GSList *mime_types;
+
+ if (known_mime_types == NULL)
+ {
+ GtkSourceLanguageManager *lm;
+ const gchar * const *languages;
+
+ lm = pluma_get_language_manager ();
+ languages = gtk_source_language_manager_get_language_ids (lm);
+
+ while ((languages != NULL) && (*languages != NULL))
+ {
+ gchar **mime_types;
+ gint i;
+ GtkSourceLanguage *lang;
+
+ lang = gtk_source_language_manager_get_language (lm, *languages);
+ g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (lang), FALSE);
+ ++languages;
+
+ mime_types = gtk_source_language_get_mime_types (lang);
+ if (mime_types == NULL)
+ continue;
+
+ for (i = 0; mime_types[i] != NULL; i++)
+ {
+ if (!g_content_type_is_a (mime_types[i], "text/plain"))
+ {
+ pluma_debug_message (DEBUG_COMMANDS,
+ "Mime-type %s is not related to text/plain",
+ mime_types[i]);
+
+ known_mime_types = g_slist_prepend (known_mime_types,
+ g_strdup (mime_types[i]));
+ }
+ }
+
+ g_strfreev (mime_types);
+ }
+
+ /* known_mime_types always has "text/plain" as first item" */
+ known_mime_types = g_slist_prepend (known_mime_types, g_strdup ("text/plain"));
+ }
+
+ /* known mime_types contains "text/plain" and then the list of mime-types unrelated to "text/plain"
+ * that pluma recognizes */
+
+ if (filter_info->mime_type == NULL)
+ return FALSE;
+
+ /*
+ * The filter is matching:
+ * - the mime-types beginning with "text/"
+ * - the mime-types inheriting from a known mime-type (note the text/plain is
+ * the first known mime-type)
+ */
+
+ if (strncmp (filter_info->mime_type, "text/", 5) == 0)
+ return TRUE;
+
+ mime_types = known_mime_types;
+ while (mime_types != NULL)
+ {
+ if (g_content_type_is_a (filter_info->mime_type, (const gchar*)mime_types->data))
+ return TRUE;
+
+ mime_types = g_slist_next (mime_types);
+ }
+
+ return FALSE;
+}
+
+static void
+pluma_file_chooser_dialog_init (PlumaFileChooserDialog *dialog)
+{
+ dialog->priv = PLUMA_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
+}
+
+static GtkWidget *
+pluma_file_chooser_dialog_new_valist (const gchar *title,
+ GtkWindow *parent,
+ GtkFileChooserAction action,
+ const PlumaEncoding *encoding,
+ const gchar *first_button_text,
+ va_list varargs)
+{
+ GtkWidget *result;
+ const char *button_text = first_button_text;
+ gint response_id;
+ GtkFileFilter *filter;
+ gint active_filter;
+
+ g_return_val_if_fail (parent != NULL, NULL);
+
+ result = g_object_new (PLUMA_TYPE_FILE_CHOOSER_DIALOG,
+ "title", title,
+ "file-system-backend", NULL,
+ "local-only", FALSE,
+ "action", action,
+ "select-multiple", action == GTK_FILE_CHOOSER_ACTION_OPEN,
+ NULL);
+
+ create_extra_widget (PLUMA_FILE_CHOOSER_DIALOG (result));
+
+ g_signal_connect (result,
+ "notify::action",
+ G_CALLBACK (action_changed),
+ NULL);
+
+ if (encoding != NULL)
+ pluma_encodings_combo_box_set_selected_encoding (
+ PLUMA_ENCODINGS_COMBO_BOX (PLUMA_FILE_CHOOSER_DIALOG (result)->priv->option_menu),
+ encoding);
+
+ active_filter = pluma_prefs_manager_get_active_file_filter ();
+ pluma_debug_message (DEBUG_COMMANDS, "Active filter: %d", active_filter);
+
+ /* Filters */
+ filter = gtk_file_filter_new ();
+
+ gtk_file_filter_set_name (filter, ALL_FILES);
+ gtk_file_filter_add_pattern (filter, "*");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (result), filter);
+
+ if (active_filter != 1)
+ {
+ /* Make this filter the default */
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (result), filter);
+ }
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, ALL_TEXT_FILES);
+ gtk_file_filter_add_custom (filter,
+ GTK_FILE_FILTER_MIME_TYPE,
+ all_text_files_filter,
+ NULL,
+ NULL);
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (result), filter);
+
+ if (active_filter == 1)
+ {
+ /* Make this filter the default */
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (result), filter);
+ }
+
+ g_signal_connect (result,
+ "notify::filter",
+ G_CALLBACK (filter_changed),
+ NULL);
+
+ gtk_window_set_transient_for (GTK_WINDOW (result), parent);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (result), TRUE);
+
+ while (button_text)
+ {
+ response_id = va_arg (varargs, gint);
+
+ gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id);
+ if ((response_id == GTK_RESPONSE_OK) ||
+ (response_id == GTK_RESPONSE_ACCEPT) ||
+ (response_id == GTK_RESPONSE_YES) ||
+ (response_id == GTK_RESPONSE_APPLY))
+ gtk_dialog_set_default_response (GTK_DIALOG (result), response_id);
+
+ button_text = va_arg (varargs, const gchar *);
+ }
+
+ return result;
+}
+
+/**
+ * pluma_file_chooser_dialog_new:
+ * @title: Title of the dialog, or %NULL
+ * @parent: Transient parent of the dialog, or %NULL
+ * @action: Open or save mode for the dialog
+ * @first_button_text: stock ID or text to go in the first button, or %NULL
+ * @Varargs: response ID for the first button, then additional (button, id) pairs, ending with %NULL
+ *
+ * Creates a new #PlumaFileChooserDialog. This function is analogous to
+ * gtk_dialog_new_with_buttons().
+ *
+ * Return value: a new #PlumaFileChooserDialog
+ *
+ **/
+GtkWidget *
+pluma_file_chooser_dialog_new (const gchar *title,
+ GtkWindow *parent,
+ GtkFileChooserAction action,
+ const PlumaEncoding *encoding,
+ const gchar *first_button_text,
+ ...)
+{
+ GtkWidget *result;
+ va_list varargs;
+
+ va_start (varargs, first_button_text);
+ result = pluma_file_chooser_dialog_new_valist (title, parent, action,
+ encoding, first_button_text,
+ varargs);
+ va_end (varargs);
+
+ return result;
+}
+
+void
+pluma_file_chooser_dialog_set_encoding (PlumaFileChooserDialog *dialog,
+ const PlumaEncoding *encoding)
+{
+ g_return_if_fail (PLUMA_IS_FILE_CHOOSER_DIALOG (dialog));
+ g_return_if_fail (PLUMA_IS_ENCODINGS_COMBO_BOX (dialog->priv->option_menu));
+
+ pluma_encodings_combo_box_set_selected_encoding (
+ PLUMA_ENCODINGS_COMBO_BOX (dialog->priv->option_menu),
+ encoding);
+}
+
+const PlumaEncoding *
+pluma_file_chooser_dialog_get_encoding (PlumaFileChooserDialog *dialog)
+{
+ g_return_val_if_fail (PLUMA_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
+ g_return_val_if_fail (PLUMA_IS_ENCODINGS_COMBO_BOX (dialog->priv->option_menu), NULL);
+ g_return_val_if_fail ((gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE), NULL);
+
+ return pluma_encodings_combo_box_get_selected_encoding (
+ PLUMA_ENCODINGS_COMBO_BOX (dialog->priv->option_menu));
+}
+
+void
+pluma_file_chooser_dialog_set_newline_type (PlumaFileChooserDialog *dialog,
+ PlumaDocumentNewlineType newline_type)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ g_return_if_fail (PLUMA_IS_FILE_CHOOSER_DIALOG (dialog));
+ g_return_if_fail (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE);
+
+ model = GTK_TREE_MODEL (dialog->priv->newline_store);
+
+ if (!gtk_tree_model_get_iter_first (model, &iter))
+ {
+ return;
+ }
+
+ do
+ {
+ PlumaDocumentNewlineType nt;
+
+ gtk_tree_model_get (model, &iter, 1, &nt, -1);
+
+ if (newline_type == nt)
+ {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (dialog->priv->newline_combo),
+ &iter);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
+
+PlumaDocumentNewlineType
+pluma_file_chooser_dialog_get_newline_type (PlumaFileChooserDialog *dialog)
+{
+ GtkTreeIter iter;
+ PlumaDocumentNewlineType newline_type;
+
+ g_return_val_if_fail (PLUMA_IS_FILE_CHOOSER_DIALOG (dialog), PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT);
+ g_return_val_if_fail (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE,
+ PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT);
+
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->priv->newline_combo),
+ &iter);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->newline_store),
+ &iter,
+ 1,
+ &newline_type,
+ -1);
+
+ return newline_type;
+}
diff --git a/pluma/pluma-file-chooser-dialog.h b/pluma/pluma-file-chooser-dialog.h
new file mode 100755
index 00000000..62285ec0
--- /dev/null
+++ b/pluma/pluma-file-chooser-dialog.h
@@ -0,0 +1,89 @@
+/*
+ * pluma-file-chooser-dialog.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_FILE_CHOOSER_DIALOG_H__
+#define __PLUMA_FILE_CHOOSER_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-encodings.h>
+#include <pluma/pluma-enum-types.h>
+#include <pluma/pluma-document.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_FILE_CHOOSER_DIALOG (pluma_file_chooser_dialog_get_type ())
+#define PLUMA_FILE_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_FILE_CHOOSER_DIALOG, PlumaFileChooserDialog))
+#define PLUMA_FILE_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_FILE_CHOOSER_DIALOG, PlumaFileChooserDialogClass))
+#define PLUMA_IS_FILE_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_FILE_CHOOSER_DIALOG))
+#define PLUMA_IS_FILE_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_FILE_CHOOSER_DIALOG))
+#define PLUMA_FILE_CHOOSER_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_FILE_CHOOSER_DIALOG, PlumaFileChooserDialogClass))
+
+typedef struct _PlumaFileChooserDialog PlumaFileChooserDialog;
+typedef struct _PlumaFileChooserDialogClass PlumaFileChooserDialogClass;
+
+typedef struct _PlumaFileChooserDialogPrivate PlumaFileChooserDialogPrivate;
+
+struct _PlumaFileChooserDialogClass
+{
+ GtkFileChooserDialogClass parent_class;
+};
+
+struct _PlumaFileChooserDialog
+{
+ GtkFileChooserDialog parent_instance;
+
+ PlumaFileChooserDialogPrivate *priv;
+};
+
+GType pluma_file_chooser_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_file_chooser_dialog_new (const gchar *title,
+ GtkWindow *parent,
+ GtkFileChooserAction action,
+ const PlumaEncoding *encoding,
+ const gchar *first_button_text,
+ ...);
+
+void pluma_file_chooser_dialog_set_encoding (PlumaFileChooserDialog *dialog,
+ const PlumaEncoding *encoding);
+
+const PlumaEncoding
+ *pluma_file_chooser_dialog_get_encoding (PlumaFileChooserDialog *dialog);
+
+void pluma_file_chooser_dialog_set_newline_type (PlumaFileChooserDialog *dialog,
+ PlumaDocumentNewlineType newline_type);
+
+PlumaDocumentNewlineType
+ pluma_file_chooser_dialog_get_newline_type (PlumaFileChooserDialog *dialog);
+
+G_END_DECLS
+
+#endif /* __PLUMA_FILE_CHOOSER_DIALOG_H__ */
diff --git a/pluma/pluma-gio-document-loader.c b/pluma/pluma-gio-document-loader.c
new file mode 100755
index 00000000..d23d752f
--- /dev/null
+++ b/pluma/pluma-gio-document-loader.c
@@ -0,0 +1,708 @@
+/*
+ * pluma-gio-document-loader.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2008. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include "pluma-gio-document-loader.h"
+#include "pluma-document-output-stream.h"
+#include "pluma-smart-charset-converter.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-debug.h"
+#include "pluma-utils.h"
+
+#ifndef ENABLE_GVFS_METADATA
+#include "pluma-metadata-manager.h"
+#endif
+
+typedef struct
+{
+ PlumaGioDocumentLoader *loader;
+ GCancellable *cancellable;
+
+ gssize read;
+ gboolean tried_mount;
+} AsyncData;
+
+#define READ_CHUNK_SIZE 8192
+#define REMOTE_QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
+ G_FILE_ATTRIBUTE_STANDARD_SIZE "," \
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE "," \
+ PLUMA_METADATA_ATTRIBUTE_ENCODING
+
+#define PLUMA_GIO_DOCUMENT_LOADER_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_GIO_DOCUMENT_LOADER, \
+ PlumaGioDocumentLoaderPrivate))
+
+static void pluma_gio_document_loader_load (PlumaDocumentLoader *loader);
+static gboolean pluma_gio_document_loader_cancel (PlumaDocumentLoader *loader);
+static goffset pluma_gio_document_loader_get_bytes_read (PlumaDocumentLoader *loader);
+
+static void open_async_read (AsyncData *async);
+
+struct _PlumaGioDocumentLoaderPrivate
+{
+ /* Info on the current file */
+ GFile *gfile;
+
+ goffset bytes_read;
+
+ /* Handle for remote files */
+ GCancellable *cancellable;
+ GInputStream *stream;
+ GOutputStream *output;
+ PlumaSmartCharsetConverter *converter;
+
+ gchar buffer[READ_CHUNK_SIZE];
+
+ GError *error;
+};
+
+G_DEFINE_TYPE(PlumaGioDocumentLoader, pluma_gio_document_loader, PLUMA_TYPE_DOCUMENT_LOADER)
+
+static void
+pluma_gio_document_loader_dispose (GObject *object)
+{
+ PlumaGioDocumentLoaderPrivate *priv;
+
+ priv = PLUMA_GIO_DOCUMENT_LOADER (object)->priv;
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ if (priv->stream != NULL)
+ {
+ g_object_unref (priv->stream);
+ priv->stream = NULL;
+ }
+
+ if (priv->output != NULL)
+ {
+ g_object_unref (priv->output);
+ priv->output = NULL;
+ }
+
+ if (priv->converter != NULL)
+ {
+ g_object_unref (priv->converter);
+ priv->converter = NULL;
+ }
+
+ if (priv->gfile != NULL)
+ {
+ g_object_unref (priv->gfile);
+ priv->gfile = NULL;
+ }
+
+ if (priv->error != NULL)
+ {
+ g_error_free (priv->error);
+ priv->error = NULL;
+ }
+
+ G_OBJECT_CLASS (pluma_gio_document_loader_parent_class)->dispose (object);
+}
+
+static void
+pluma_gio_document_loader_class_init (PlumaGioDocumentLoaderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ PlumaDocumentLoaderClass *loader_class = PLUMA_DOCUMENT_LOADER_CLASS (klass);
+
+ object_class->dispose = pluma_gio_document_loader_dispose;
+
+ loader_class->load = pluma_gio_document_loader_load;
+ loader_class->cancel = pluma_gio_document_loader_cancel;
+ loader_class->get_bytes_read = pluma_gio_document_loader_get_bytes_read;
+
+ g_type_class_add_private (object_class, sizeof(PlumaGioDocumentLoaderPrivate));
+}
+
+static void
+pluma_gio_document_loader_init (PlumaGioDocumentLoader *gvloader)
+{
+ gvloader->priv = PLUMA_GIO_DOCUMENT_LOADER_GET_PRIVATE (gvloader);
+
+ gvloader->priv->converter = NULL;
+ gvloader->priv->error = NULL;
+}
+
+static AsyncData *
+async_data_new (PlumaGioDocumentLoader *gvloader)
+{
+ AsyncData *async;
+
+ async = g_slice_new (AsyncData);
+ async->loader = gvloader;
+ async->cancellable = g_object_ref (gvloader->priv->cancellable);
+ async->tried_mount = FALSE;
+
+ return async;
+}
+
+static void
+async_data_free (AsyncData *async)
+{
+ g_object_unref (async->cancellable);
+ g_slice_free (AsyncData, async);
+}
+
+static const PlumaEncoding *
+get_metadata_encoding (PlumaDocumentLoader *loader)
+{
+ const PlumaEncoding *enc = NULL;
+
+#ifndef ENABLE_GVFS_METADATA
+ gchar *charset;
+ const gchar *uri;
+
+ uri = pluma_document_loader_get_uri (loader);
+
+ charset = pluma_metadata_manager_get (uri, "encoding");
+
+ if (charset == NULL)
+ return NULL;
+
+ enc = pluma_encoding_get_from_charset (charset);
+
+ g_free (charset);
+#else
+ GFileInfo *info;
+
+ info = pluma_document_loader_get_info (loader);
+
+ /* check if the encoding was set in the metadata */
+ if (g_file_info_has_attribute (info, PLUMA_METADATA_ATTRIBUTE_ENCODING))
+ {
+ const gchar *charset;
+
+ charset = g_file_info_get_attribute_string (info,
+ PLUMA_METADATA_ATTRIBUTE_ENCODING);
+
+ if (charset == NULL)
+ return NULL;
+
+ enc = pluma_encoding_get_from_charset (charset);
+ }
+#endif
+
+ return enc;
+}
+
+static void
+remote_load_completed_or_failed (PlumaGioDocumentLoader *loader, AsyncData *async)
+{
+ pluma_document_loader_loading (PLUMA_DOCUMENT_LOADER (loader),
+ TRUE,
+ loader->priv->error);
+
+ if (async)
+ async_data_free (async);
+}
+
+static void
+async_failed (AsyncData *async, GError *error)
+{
+ g_propagate_error (&async->loader->priv->error, error);
+ remote_load_completed_or_failed (async->loader, async);
+}
+
+static void
+close_input_stream_ready_cb (GInputStream *stream,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_LOADER);
+
+ /* check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ pluma_debug_message (DEBUG_SAVER, "Finished closing input stream");
+
+ if (!g_input_stream_close_finish (stream, res, &error))
+ {
+ pluma_debug_message (DEBUG_SAVER, "Closing input stream error: %s", error->message);
+
+ async_failed (async, error);
+ return;
+ }
+
+ pluma_debug_message (DEBUG_SAVER, "Close output stream");
+ if (!g_output_stream_close (async->loader->priv->output,
+ async->cancellable, &error))
+ {
+ async_failed (async, error);
+ return;
+ }
+
+ remote_load_completed_or_failed (async->loader, async);
+}
+
+static void
+write_complete (AsyncData *async)
+{
+ PlumaDocumentLoader *loader;
+
+ loader = PLUMA_DOCUMENT_LOADER (async->loader);
+
+ if (async->loader->priv->stream)
+ g_input_stream_close_async (G_INPUT_STREAM (async->loader->priv->stream),
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback)close_input_stream_ready_cb,
+ async);
+}
+
+/* prototype, because they call each other... isn't C lovely */
+static void read_file_chunk (AsyncData *async);
+
+static void
+write_file_chunk (AsyncData *async)
+{
+ PlumaGioDocumentLoader *gvloader;
+ gssize bytes_written;
+ GError *error = NULL;
+
+ gvloader = async->loader;
+
+ /* we use sync methods on doc stream since it is in memory. Using async
+ would be racy and we can endup with invalidated iters */
+ bytes_written = g_output_stream_write (G_OUTPUT_STREAM (gvloader->priv->output),
+ gvloader->priv->buffer,
+ async->read,
+ async->cancellable,
+ &error);
+
+ pluma_debug_message (DEBUG_SAVER, "Written: %" G_GSSIZE_FORMAT, bytes_written);
+ if (bytes_written == -1)
+ {
+ pluma_debug_message (DEBUG_SAVER, "Write error: %s", error->message);
+ async_failed (async, error);
+ return;
+ }
+
+ /* note that this signal blocks the read... check if it isn't
+ * a performance problem
+ */
+ pluma_document_loader_loading (PLUMA_DOCUMENT_LOADER (gvloader),
+ FALSE,
+ NULL);
+
+ read_file_chunk (async);
+}
+
+static void
+async_read_cb (GInputStream *stream,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ pluma_debug (DEBUG_LOADER);
+ PlumaGioDocumentLoader *gvloader;
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_LOADER);
+
+ /* manually check cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ gvloader = async->loader;
+
+ async->read = g_input_stream_read_finish (stream, res, &error);
+
+ /* error occurred */
+ if (async->read == -1)
+ {
+ async_failed (async, error);
+ return;
+ }
+
+ /* Check for the extremely unlikely case where the file size overflows. */
+ if (gvloader->priv->bytes_read + async->read < gvloader->priv->bytes_read)
+ {
+ g_set_error (&gvloader->priv->error,
+ PLUMA_DOCUMENT_ERROR,
+ PLUMA_DOCUMENT_ERROR_TOO_BIG,
+ "File too big");
+
+ async_failed (async, gvloader->priv->error);
+ return;
+ }
+
+ /* Bump the size. */
+ gvloader->priv->bytes_read += async->read;
+
+ /* end of the file, we are done! */
+ if (async->read == 0)
+ {
+ PlumaDocumentLoader *loader;
+
+ loader = PLUMA_DOCUMENT_LOADER (gvloader);
+
+ loader->auto_detected_encoding =
+ pluma_smart_charset_converter_get_guessed (gvloader->priv->converter);
+
+ loader->auto_detected_newline_type =
+ pluma_document_output_stream_detect_newline_type (PLUMA_DOCUMENT_OUTPUT_STREAM (gvloader->priv->output));
+
+ /* Check if we needed some fallback char, if so, check if there was
+ a previous error and if not set a fallback used error */
+ /* FIXME Uncomment this when we want to manage conversion fallback */
+ /*if ((pluma_smart_charset_converter_get_num_fallbacks (gvloader->priv->converter) != 0) &&
+ gvloader->priv->error == NULL)
+ {
+ g_set_error_literal (&gvloader->priv->error,
+ PLUMA_DOCUMENT_ERROR,
+ PLUMA_DOCUMENT_ERROR_CONVERSION_FALLBACK,
+ "There was a conversion error and it was "
+ "needed to use a fallback char");
+ }*/
+
+ write_complete (async);
+
+ return;
+ }
+
+ write_file_chunk (async);
+}
+
+static void
+read_file_chunk (AsyncData *async)
+{
+ PlumaGioDocumentLoader *gvloader;
+
+ gvloader = async->loader;
+
+ g_input_stream_read_async (G_INPUT_STREAM (gvloader->priv->stream),
+ gvloader->priv->buffer,
+ READ_CHUNK_SIZE,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) async_read_cb,
+ async);
+}
+
+static GSList *
+get_candidate_encodings (PlumaGioDocumentLoader *gvloader)
+{
+ const PlumaEncoding *metadata;
+ GSList *encodings = NULL;
+
+ encodings = pluma_prefs_manager_get_auto_detected_encodings ();
+
+ metadata = get_metadata_encoding (PLUMA_DOCUMENT_LOADER (gvloader));
+ if (metadata != NULL)
+ {
+ encodings = g_slist_prepend (encodings, (gpointer)metadata);
+ }
+
+ return encodings;
+}
+
+static void
+finish_query_info (AsyncData *async)
+{
+ PlumaGioDocumentLoader *gvloader;
+ PlumaDocumentLoader *loader;
+ GInputStream *conv_stream;
+ GFileInfo *info;
+ GSList *candidate_encodings;
+
+ gvloader = async->loader;
+ loader = PLUMA_DOCUMENT_LOADER (gvloader);
+ info = loader->info;
+
+ /* if it's not a regular file, error out... */
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) &&
+ g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
+ {
+ g_set_error (&gvloader->priv->error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_REGULAR_FILE,
+ "Not a regular file");
+
+ remote_load_completed_or_failed (gvloader, async);
+
+ return;
+ }
+
+ /* Get the candidate encodings */
+ if (loader->encoding == NULL)
+ {
+ candidate_encodings = get_candidate_encodings (gvloader);
+ }
+ else
+ {
+ candidate_encodings = g_slist_prepend (NULL, (gpointer) loader->encoding);
+ }
+
+ gvloader->priv->converter = pluma_smart_charset_converter_new (candidate_encodings);
+ g_slist_free (candidate_encodings);
+
+ conv_stream = g_converter_input_stream_new (gvloader->priv->stream,
+ G_CONVERTER (gvloader->priv->converter));
+ g_object_unref (gvloader->priv->stream);
+
+ gvloader->priv->stream = conv_stream;
+
+ /* Output stream */
+ gvloader->priv->output = pluma_document_output_stream_new (loader->document);
+
+ /* start reading */
+ read_file_chunk (async);
+}
+
+static void
+query_info_cb (GFile *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ PlumaGioDocumentLoader *gvloader;
+ GFileInfo *info;
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_LOADER);
+
+ /* manually check the cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ gvloader = async->loader;
+
+ /* finish the info query */
+ info = g_file_query_info_finish (gvloader->priv->gfile,
+ res,
+ &error);
+
+ if (info == NULL)
+ {
+ /* propagate the error and clean up */
+ async_failed (async, error);
+ return;
+ }
+
+ PLUMA_DOCUMENT_LOADER (gvloader)->info = info;
+
+ finish_query_info (async);
+}
+
+static void
+mount_ready_callback (GFile *file,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+ gboolean mounted;
+
+ pluma_debug (DEBUG_LOADER);
+
+ /* manual check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ mounted = g_file_mount_enclosing_volume_finish (file, res, &error);
+
+ if (!mounted)
+ {
+ async_failed (async, error);
+ }
+ else
+ {
+ /* try again to open the file for reading */
+ open_async_read (async);
+ }
+}
+
+static void
+recover_not_mounted (AsyncData *async)
+{
+ PlumaDocument *doc;
+ GMountOperation *mount_operation;
+
+ pluma_debug (DEBUG_LOADER);
+
+ doc = pluma_document_loader_get_document (PLUMA_DOCUMENT_LOADER (async->loader));
+ mount_operation = _pluma_document_create_mount_operation (doc);
+
+ async->tried_mount = TRUE;
+ g_file_mount_enclosing_volume (async->loader->priv->gfile,
+ G_MOUNT_MOUNT_NONE,
+ mount_operation,
+ async->cancellable,
+ (GAsyncReadyCallback) mount_ready_callback,
+ async);
+
+ g_object_unref (mount_operation);
+}
+
+static void
+async_read_ready_callback (GObject *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+ PlumaGioDocumentLoader *gvloader;
+
+ pluma_debug (DEBUG_LOADER);
+
+ /* manual check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ gvloader = async->loader;
+
+ gvloader->priv->stream = G_INPUT_STREAM (g_file_read_finish (gvloader->priv->gfile,
+ res, &error));
+
+ if (!gvloader->priv->stream)
+ {
+ if (error->code == G_IO_ERROR_NOT_MOUNTED && !async->tried_mount)
+ {
+ recover_not_mounted (async);
+ g_error_free (error);
+ return;
+ }
+
+ /* Propagate error */
+ g_propagate_error (&gvloader->priv->error, error);
+ pluma_document_loader_loading (PLUMA_DOCUMENT_LOADER (gvloader),
+ TRUE,
+ gvloader->priv->error);
+
+ async_data_free (async);
+ return;
+ }
+
+ /* get the file info: note we cannot use
+ * g_file_input_stream_query_info_async since it is not able to get the
+ * content type etc, beside it is not supported by gvfs.
+ * Using the file instead of the stream is slightly racy, but for
+ * loading this is not too bad...
+ */
+ g_file_query_info_async (gvloader->priv->gfile,
+ REMOTE_QUERY_ATTRIBUTES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) query_info_cb,
+ async);
+}
+
+static void
+open_async_read (AsyncData *async)
+{
+ g_file_read_async (async->loader->priv->gfile,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) async_read_ready_callback,
+ async);
+}
+
+static void
+pluma_gio_document_loader_load (PlumaDocumentLoader *loader)
+{
+ PlumaGioDocumentLoader *gvloader = PLUMA_GIO_DOCUMENT_LOADER (loader);
+ AsyncData *async;
+
+ pluma_debug (DEBUG_LOADER);
+
+ /* make sure no load operation is currently running */
+ g_return_if_fail (gvloader->priv->cancellable == NULL);
+
+ gvloader->priv->gfile = g_file_new_for_uri (loader->uri);
+
+ /* loading start */
+ pluma_document_loader_loading (PLUMA_DOCUMENT_LOADER (gvloader),
+ FALSE,
+ NULL);
+
+ gvloader->priv->cancellable = g_cancellable_new ();
+ async = async_data_new (gvloader);
+
+ open_async_read (async);
+}
+
+static goffset
+pluma_gio_document_loader_get_bytes_read (PlumaDocumentLoader *loader)
+{
+ return PLUMA_GIO_DOCUMENT_LOADER (loader)->priv->bytes_read;
+}
+
+static gboolean
+pluma_gio_document_loader_cancel (PlumaDocumentLoader *loader)
+{
+ PlumaGioDocumentLoader *gvloader = PLUMA_GIO_DOCUMENT_LOADER (loader);
+
+ if (gvloader->priv->cancellable == NULL)
+ return FALSE;
+
+ g_cancellable_cancel (gvloader->priv->cancellable);
+
+ g_set_error (&gvloader->priv->error,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ "Operation cancelled");
+
+ remote_load_completed_or_failed (gvloader, NULL);
+
+ return TRUE;
+}
diff --git a/pluma/pluma-gio-document-loader.h b/pluma/pluma-gio-document-loader.h
new file mode 100755
index 00000000..f060daa5
--- /dev/null
+++ b/pluma/pluma-gio-document-loader.h
@@ -0,0 +1,79 @@
+/*
+ * pluma-gio-document-loader.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2008. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_GIO_DOCUMENT_LOADER_H__
+#define __PLUMA_GIO_DOCUMENT_LOADER_H__
+
+#include <pluma/pluma-document.h>
+#include "pluma-document-loader.h"
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_GIO_DOCUMENT_LOADER (pluma_gio_document_loader_get_type())
+#define PLUMA_GIO_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_GIO_DOCUMENT_LOADER, PlumaGioDocumentLoader))
+#define PLUMA_GIO_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_GIO_DOCUMENT_LOADER, PlumaGioDocumentLoaderClass))
+#define PLUMA_IS_GIO_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_GIO_DOCUMENT_LOADER))
+#define PLUMA_IS_GIO_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_GIO_DOCUMENT_LOADER))
+#define PLUMA_GIO_DOCUMENT_LOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_GIO_DOCUMENT_LOADER, PlumaGioDocumentLoaderClass))
+
+/* Private structure type */
+typedef struct _PlumaGioDocumentLoaderPrivate PlumaGioDocumentLoaderPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaGioDocumentLoader PlumaGioDocumentLoader;
+
+struct _PlumaGioDocumentLoader
+{
+ PlumaDocumentLoader loader;
+
+ /*< private > */
+ PlumaGioDocumentLoaderPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef PlumaDocumentLoaderClass PlumaGioDocumentLoaderClass;
+
+/*
+ * Public methods
+ */
+GType pluma_gio_document_loader_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __PLUMA_GIO_DOCUMENT_LOADER_H__ */
diff --git a/pluma/pluma-gio-document-saver.c b/pluma/pluma-gio-document-saver.c
new file mode 100755
index 00000000..e64e5dec
--- /dev/null
+++ b/pluma/pluma-gio-document-saver.c
@@ -0,0 +1,775 @@
+/*
+ * pluma-gio-document-saver.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005-2006 - Paolo Borelli and Paolo Maggi
+ * Copyright (C) 2007 - Paolo Borelli, Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include "pluma-gio-document-saver.h"
+#include "pluma-document-input-stream.h"
+#include "pluma-debug.h"
+
+#define WRITE_CHUNK_SIZE 8192
+
+typedef struct
+{
+ PlumaGioDocumentSaver *saver;
+ gchar buffer[WRITE_CHUNK_SIZE];
+ GCancellable *cancellable;
+ gboolean tried_mount;
+ gssize written;
+ gssize read;
+ GError *error;
+} AsyncData;
+
+#define REMOTE_QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+#define PLUMA_GIO_DOCUMENT_SAVER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_GIO_DOCUMENT_SAVER, \
+ PlumaGioDocumentSaverPrivate))
+
+static void pluma_gio_document_saver_save (PlumaDocumentSaver *saver,
+ GTimeVal *old_mtime);
+static goffset pluma_gio_document_saver_get_file_size (PlumaDocumentSaver *saver);
+static goffset pluma_gio_document_saver_get_bytes_written (PlumaDocumentSaver *saver);
+
+
+static void check_modified_async (AsyncData *async);
+
+struct _PlumaGioDocumentSaverPrivate
+{
+ GTimeVal old_mtime;
+
+ goffset size;
+ goffset bytes_written;
+
+ GFile *gfile;
+ GCancellable *cancellable;
+ GOutputStream *stream;
+ GInputStream *input;
+
+ GError *error;
+};
+
+G_DEFINE_TYPE(PlumaGioDocumentSaver, pluma_gio_document_saver, PLUMA_TYPE_DOCUMENT_SAVER)
+
+static void
+pluma_gio_document_saver_dispose (GObject *object)
+{
+ PlumaGioDocumentSaverPrivate *priv = PLUMA_GIO_DOCUMENT_SAVER (object)->priv;
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ if (priv->gfile != NULL)
+ {
+ g_object_unref (priv->gfile);
+ priv->gfile = NULL;
+ }
+
+ if (priv->error != NULL)
+ {
+ g_error_free (priv->error);
+ priv->error = NULL;
+ }
+
+ if (priv->stream != NULL)
+ {
+ g_object_unref (priv->stream);
+ priv->stream = NULL;
+ }
+
+ if (priv->input != NULL)
+ {
+ g_object_unref (priv->input);
+ priv->input = NULL;
+ }
+
+ G_OBJECT_CLASS (pluma_gio_document_saver_parent_class)->dispose (object);
+}
+
+static AsyncData *
+async_data_new (PlumaGioDocumentSaver *gvsaver)
+{
+ AsyncData *async;
+
+ async = g_slice_new (AsyncData);
+ async->saver = gvsaver;
+ async->cancellable = g_object_ref (gvsaver->priv->cancellable);
+
+ async->tried_mount = FALSE;
+ async->written = 0;
+ async->read = 0;
+
+ async->error = NULL;
+
+ return async;
+}
+
+static void
+async_data_free (AsyncData *async)
+{
+ g_object_unref (async->cancellable);
+
+ if (async->error)
+ {
+ g_error_free (async->error);
+ }
+
+ g_slice_free (AsyncData, async);
+}
+
+static void
+pluma_gio_document_saver_class_init (PlumaGioDocumentSaverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ PlumaDocumentSaverClass *saver_class = PLUMA_DOCUMENT_SAVER_CLASS (klass);
+
+ object_class->dispose = pluma_gio_document_saver_dispose;
+
+ saver_class->save = pluma_gio_document_saver_save;
+ saver_class->get_file_size = pluma_gio_document_saver_get_file_size;
+ saver_class->get_bytes_written = pluma_gio_document_saver_get_bytes_written;
+
+ g_type_class_add_private (object_class, sizeof(PlumaGioDocumentSaverPrivate));
+}
+
+static void
+pluma_gio_document_saver_init (PlumaGioDocumentSaver *gvsaver)
+{
+ gvsaver->priv = PLUMA_GIO_DOCUMENT_SAVER_GET_PRIVATE (gvsaver);
+
+ gvsaver->priv->cancellable = g_cancellable_new ();
+ gvsaver->priv->error = NULL;
+}
+
+static void
+remote_save_completed_or_failed (PlumaGioDocumentSaver *gvsaver,
+ AsyncData *async)
+{
+ pluma_document_saver_saving (PLUMA_DOCUMENT_SAVER (gvsaver),
+ TRUE,
+ gvsaver->priv->error);
+
+ if (async)
+ async_data_free (async);
+}
+
+static void
+async_failed (AsyncData *async,
+ GError *error)
+{
+ g_propagate_error (&async->saver->priv->error, error);
+ remote_save_completed_or_failed (async->saver, async);
+}
+
+/* BEGIN NOTE:
+ *
+ * This fixes an issue in GOutputStream that applies the atomic replace
+ * save strategy. The stream moves the written file to the original file
+ * when the stream is closed. However, there is no way currently to tell
+ * the stream that the save should be aborted (there could be a
+ * conversion error). The patch explicitly closes the output stream
+ * in all these cases with a GCancellable in the cancelled state, causing
+ * the output stream to close, but not move the file. This makes use
+ * of an implementation detail in the local gio file stream and should be
+ * properly fixed by adding the appropriate API in gio. Until then, at least
+ * we prevent data corruption for now.
+ *
+ * Relevant bug reports:
+ *
+ * Bug 615110 - write file ignore encoding errors (pluma)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=615110
+ *
+ * Bug 602412 - g_file_replace does not restore original file when there is
+ * errors while writing (glib/gio)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=602412
+ */
+static void
+cancel_output_stream_ready_cb (GOutputStream *stream,
+ GAsyncResult *result,
+ AsyncData *async)
+{
+ GError *error;
+
+ g_output_stream_close_finish (stream, result, NULL);
+
+ /* check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable) || async->error == NULL)
+ {
+ async_data_free (async);
+ return;
+ }
+
+ error = async->error;
+ async->error = NULL;
+
+ async_failed (async, error);
+}
+
+static void
+cancel_output_stream (AsyncData *async)
+{
+ GCancellable *cancellable;
+
+ pluma_debug_message (DEBUG_SAVER, "Cancel output stream");
+
+ cancellable = g_cancellable_new ();
+ g_cancellable_cancel (cancellable);
+
+ g_output_stream_close_async (async->saver->priv->stream,
+ G_PRIORITY_HIGH,
+ cancellable,
+ (GAsyncReadyCallback)cancel_output_stream_ready_cb,
+ async);
+
+ g_object_unref (cancellable);
+}
+
+static void
+cancel_output_stream_and_fail (AsyncData *async,
+ GError *error)
+{
+
+ pluma_debug_message (DEBUG_SAVER, "Cancel output stream and fail");
+
+ g_propagate_error (&async->error, error);
+ cancel_output_stream (async);
+}
+
+/*
+ * END NOTE
+ */
+
+static void
+remote_get_info_cb (GFile *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ PlumaGioDocumentSaver *saver;
+ GFileInfo *info;
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_SAVER);
+
+ /* check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ saver = async->saver;
+
+ pluma_debug_message (DEBUG_SAVER, "Finished query info on file");
+ info = g_file_query_info_finish (source, res, &error);
+
+ if (info != NULL)
+ {
+ if (PLUMA_DOCUMENT_SAVER (saver)->info != NULL)
+ g_object_unref (PLUMA_DOCUMENT_SAVER (saver)->info);
+
+ PLUMA_DOCUMENT_SAVER (saver)->info = info;
+ }
+ else
+ {
+ pluma_debug_message (DEBUG_SAVER, "Query info failed: %s", error->message);
+ g_propagate_error (&saver->priv->error, error);
+ }
+
+ remote_save_completed_or_failed (saver, async);
+}
+
+static void
+close_async_ready_get_info_cb (GOutputStream *stream,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_SAVER);
+
+ /* check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ pluma_debug_message (DEBUG_SAVER, "Finished closing stream");
+
+ if (!g_output_stream_close_finish (stream, res, &error))
+ {
+ pluma_debug_message (DEBUG_SAVER, "Closing stream error: %s", error->message);
+
+ async_failed (async, error);
+ return;
+ }
+
+ /* get the file info: note we cannot use
+ * g_file_output_stream_query_info_async since it is not able to get the
+ * content type etc, beside it is not supported by gvfs.
+ * I'm not sure this is actually necessary, can't we just use
+ * g_content_type_guess (since we have the file name and the data)
+ */
+ pluma_debug_message (DEBUG_SAVER, "Query info on file");
+ g_file_query_info_async (async->saver->priv->gfile,
+ REMOTE_QUERY_ATTRIBUTES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) remote_get_info_cb,
+ async);
+}
+
+static void
+write_complete (AsyncData *async)
+{
+ GError *error = NULL;
+
+ /* first we close the input stream */
+ pluma_debug_message (DEBUG_SAVER, "Close input stream");
+ if (!g_input_stream_close (async->saver->priv->input,
+ async->cancellable, &error))
+ {
+ pluma_debug_message (DEBUG_SAVER, "Closing input stream error: %s", error->message);
+ cancel_output_stream_and_fail (async, error);
+ return;
+ }
+
+ /* now we close the output stream */
+ pluma_debug_message (DEBUG_SAVER, "Close output stream");
+ g_output_stream_close_async (async->saver->priv->stream,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback)close_async_ready_get_info_cb,
+ async);
+}
+
+/* prototype, because they call each other... isn't C lovely */
+static void read_file_chunk (AsyncData *async);
+static void write_file_chunk (AsyncData *async);
+
+static void
+async_write_cb (GOutputStream *stream,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ PlumaGioDocumentSaver *gvsaver;
+ gssize bytes_written;
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_SAVER);
+
+ /* Check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ cancel_output_stream (async);
+ return;
+ }
+
+ bytes_written = g_output_stream_write_finish (stream, res, &error);
+
+ pluma_debug_message (DEBUG_SAVER, "Written: %" G_GSSIZE_FORMAT, bytes_written);
+
+ if (bytes_written == -1)
+ {
+ pluma_debug_message (DEBUG_SAVER, "Write error: %s", error->message);
+ cancel_output_stream_and_fail (async, error);
+ return;
+ }
+
+ gvsaver = async->saver;
+ async->written += bytes_written;
+
+ /* write again */
+ if (async->written != async->read)
+ {
+ write_file_chunk (async);
+ return;
+ }
+
+ /* note that this signal blocks the write... check if it isn't
+ * a performance problem
+ */
+ pluma_document_saver_saving (PLUMA_DOCUMENT_SAVER (gvsaver),
+ FALSE,
+ NULL);
+
+ read_file_chunk (async);
+}
+
+static void
+write_file_chunk (AsyncData *async)
+{
+ PlumaGioDocumentSaver *gvsaver;
+
+ pluma_debug (DEBUG_SAVER);
+
+ gvsaver = async->saver;
+
+ g_output_stream_write_async (G_OUTPUT_STREAM (gvsaver->priv->stream),
+ async->buffer + async->written,
+ async->read - async->written,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) async_write_cb,
+ async);
+}
+
+static void
+read_file_chunk (AsyncData *async)
+{
+ PlumaGioDocumentSaver *gvsaver;
+ PlumaDocumentInputStream *dstream;
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_SAVER);
+
+ gvsaver = async->saver;
+ async->written = 0;
+
+ /* we use sync methods on doc stream since it is in memory. Using async
+ would be racy and we can endup with invalidated iters */
+ async->read = g_input_stream_read (gvsaver->priv->input,
+ async->buffer,
+ WRITE_CHUNK_SIZE,
+ async->cancellable,
+ &error);
+
+ if (error != NULL)
+ {
+ cancel_output_stream_and_fail (async, error);
+ return;
+ }
+
+ /* Check if we finished reading and writing */
+ if (async->read == 0)
+ {
+ write_complete (async);
+ return;
+ }
+
+ /* Get how many chars have been read */
+ dstream = PLUMA_DOCUMENT_INPUT_STREAM (gvsaver->priv->input);
+ gvsaver->priv->bytes_written = pluma_document_input_stream_tell (dstream);
+
+ write_file_chunk (async);
+}
+
+static void
+async_replace_ready_callback (GFile *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ PlumaGioDocumentSaver *gvsaver;
+ PlumaDocumentSaver *saver;
+ GCharsetConverter *converter;
+ GFileOutputStream *file_stream;
+ GError *error = NULL;
+
+ pluma_debug (DEBUG_SAVER);
+
+ /* Check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ gvsaver = async->saver;
+ saver = PLUMA_DOCUMENT_SAVER (gvsaver);
+ file_stream = g_file_replace_finish (source, res, &error);
+
+ /* handle any error that might occur */
+ if (!file_stream)
+ {
+ pluma_debug_message (DEBUG_SAVER, "Opening file failed: %s", error->message);
+ async_failed (async, error);
+ return;
+ }
+
+ /* FIXME: manage converter error? */
+ pluma_debug_message (DEBUG_SAVER, "Encoding charset: %s",
+ pluma_encoding_get_charset (saver->encoding));
+
+ if (saver->encoding != pluma_encoding_get_utf8 ())
+ {
+ converter = g_charset_converter_new (pluma_encoding_get_charset (saver->encoding),
+ "UTF-8",
+ NULL);
+ gvsaver->priv->stream = g_converter_output_stream_new (G_OUTPUT_STREAM (file_stream),
+ G_CONVERTER (converter));
+
+ g_object_unref (file_stream);
+ g_object_unref (converter);
+ }
+ else
+ {
+ gvsaver->priv->stream = G_OUTPUT_STREAM (file_stream);
+ }
+
+ gvsaver->priv->input = pluma_document_input_stream_new (GTK_TEXT_BUFFER (saver->document),
+ saver->newline_type);
+
+ gvsaver->priv->size = pluma_document_input_stream_get_total_size (PLUMA_DOCUMENT_INPUT_STREAM (gvsaver->priv->input));
+
+ read_file_chunk (async);
+}
+
+static void
+begin_write (AsyncData *async)
+{
+ PlumaGioDocumentSaver *gvsaver;
+ PlumaDocumentSaver *saver;
+ gboolean backup;
+
+ pluma_debug_message (DEBUG_SAVER, "Start replacing file contents");
+
+ /* For remote files we simply use g_file_replace_async. There is no
+ * backup as of yet
+ */
+ gvsaver = async->saver;
+ saver = PLUMA_DOCUMENT_SAVER (gvsaver);
+
+ /* Do not make backups for remote files so they do not clutter remote systems */
+ backup = (saver->keep_backup && pluma_document_is_local (saver->document));
+
+ pluma_debug_message (DEBUG_SAVER, "File contents size: %" G_GINT64_FORMAT, gvsaver->priv->size);
+ pluma_debug_message (DEBUG_SAVER, "Calling replace_async");
+ pluma_debug_message (DEBUG_SAVER, backup ? "Keep backup" : "Discard backup");
+
+ g_file_replace_async (gvsaver->priv->gfile,
+ NULL,
+ backup,
+ G_FILE_CREATE_NONE,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) async_replace_ready_callback,
+ async);
+}
+
+static void
+mount_ready_callback (GFile *file,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+ gboolean mounted;
+
+ pluma_debug (DEBUG_SAVER);
+
+ /* manual check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ mounted = g_file_mount_enclosing_volume_finish (file, res, &error);
+
+ if (!mounted)
+ {
+ async_failed (async, error);
+ }
+ else
+ {
+ /* try again to get the modified state */
+ check_modified_async (async);
+ }
+}
+
+static void
+recover_not_mounted (AsyncData *async)
+{
+ PlumaDocument *doc;
+ GMountOperation *mount_operation;
+
+ pluma_debug (DEBUG_LOADER);
+
+ doc = pluma_document_saver_get_document (PLUMA_DOCUMENT_SAVER (async->saver));
+ mount_operation = _pluma_document_create_mount_operation (doc);
+
+ async->tried_mount = TRUE;
+ g_file_mount_enclosing_volume (async->saver->priv->gfile,
+ G_MOUNT_MOUNT_NONE,
+ mount_operation,
+ async->cancellable,
+ (GAsyncReadyCallback) mount_ready_callback,
+ async);
+
+ g_object_unref (mount_operation);
+}
+
+static void
+check_modification_callback (GFile *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ PlumaGioDocumentSaver *gvsaver;
+ GError *error = NULL;
+ GFileInfo *info;
+
+ pluma_debug (DEBUG_SAVER);
+
+ /* manually check cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ gvsaver = async->saver;
+ info = g_file_query_info_finish (source, res, &error);
+ if (info == NULL)
+ {
+ if (error->code == G_IO_ERROR_NOT_MOUNTED && !async->tried_mount)
+ {
+ recover_not_mounted (async);
+ g_error_free (error);
+ return;
+ }
+
+ /* it's perfectly fine if the file doesn't exist yet */
+ if (error->code != G_IO_ERROR_NOT_FOUND)
+ {
+ pluma_debug_message (DEBUG_SAVER, "Error getting modification: %s", error->message);
+
+ async_failed (async, error);
+ return;
+ }
+ }
+
+ /* check if the mtime is > what we know about it (if we have it) */
+ if (info != NULL && g_file_info_has_attribute (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ {
+ GTimeVal mtime;
+ GTimeVal old_mtime;
+
+ g_file_info_get_modification_time (info, &mtime);
+ old_mtime = gvsaver->priv->old_mtime;
+
+ if ((old_mtime.tv_sec > 0 || old_mtime.tv_usec > 0) &&
+ (mtime.tv_sec != old_mtime.tv_sec || mtime.tv_usec != old_mtime.tv_usec) &&
+ (PLUMA_DOCUMENT_SAVER (gvsaver)->flags & PLUMA_DOCUMENT_SAVE_IGNORE_MTIME) == 0)
+ {
+ pluma_debug_message (DEBUG_SAVER, "File is externally modified");
+ g_set_error (&gvsaver->priv->error,
+ PLUMA_DOCUMENT_ERROR,
+ PLUMA_DOCUMENT_ERROR_EXTERNALLY_MODIFIED,
+ "Externally modified");
+
+ remote_save_completed_or_failed (gvsaver, async);
+ g_object_unref (info);
+
+ return;
+ }
+ }
+
+ if (info != NULL)
+ g_object_unref (info);
+
+ /* modification check passed, start write */
+ begin_write (async);
+}
+
+static void
+check_modified_async (AsyncData *async)
+{
+ pluma_debug_message (DEBUG_SAVER, "Check externally modified");
+
+ g_file_query_info_async (async->saver->priv->gfile,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) check_modification_callback,
+ async);
+}
+
+static gboolean
+save_remote_file_real (PlumaGioDocumentSaver *gvsaver)
+{
+ AsyncData *async;
+
+ pluma_debug_message (DEBUG_SAVER, "Starting gio save");
+
+ /* First find out if the file is modified externally. This requires
+ * a stat, but I don't think we can do this any other way
+ */
+ async = async_data_new (gvsaver);
+
+ check_modified_async (async);
+
+ /* return false to stop timeout */
+ return FALSE;
+}
+
+static void
+pluma_gio_document_saver_save (PlumaDocumentSaver *saver,
+ GTimeVal *old_mtime)
+{
+ PlumaGioDocumentSaver *gvsaver = PLUMA_GIO_DOCUMENT_SAVER (saver);
+
+ gvsaver->priv->old_mtime = *old_mtime;
+ gvsaver->priv->gfile = g_file_new_for_uri (saver->uri);
+
+ /* saving start */
+ pluma_document_saver_saving (saver, FALSE, NULL);
+
+ g_timeout_add_full (G_PRIORITY_HIGH,
+ 0,
+ (GSourceFunc) save_remote_file_real,
+ gvsaver,
+ NULL);
+}
+
+static goffset
+pluma_gio_document_saver_get_file_size (PlumaDocumentSaver *saver)
+{
+ return PLUMA_GIO_DOCUMENT_SAVER (saver)->priv->size;
+}
+
+static goffset
+pluma_gio_document_saver_get_bytes_written (PlumaDocumentSaver *saver)
+{
+ return PLUMA_GIO_DOCUMENT_SAVER (saver)->priv->bytes_written;
+}
diff --git a/pluma/pluma-gio-document-saver.h b/pluma/pluma-gio-document-saver.h
new file mode 100755
index 00000000..5ea16b27
--- /dev/null
+++ b/pluma/pluma-gio-document-saver.h
@@ -0,0 +1,76 @@
+/*
+ * pluma-gio-document-saver.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyrhing (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __PLUMA_GIO_DOCUMENT_SAVER_H__
+#define __PLUMA_GIO_DOCUMENT_SAVER_H__
+
+#include <pluma/pluma-document-saver.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_GIO_DOCUMENT_SAVER (pluma_gio_document_saver_get_type())
+#define PLUMA_GIO_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_GIO_DOCUMENT_SAVER, PlumaGioDocumentSaver))
+#define PLUMA_GIO_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_GIO_DOCUMENT_SAVER, PlumaGioDocumentSaverClass))
+#define PLUMA_IS_GIO_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_GIO_DOCUMENT_SAVER))
+#define PLUMA_IS_GIO_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_GIO_DOCUMENT_SAVER))
+#define PLUMA_GIO_DOCUMENT_SAVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_GIO_DOCUMENT_SAVER, PlumaGioDocumentSaverClass))
+
+/* Private structure type */
+typedef struct _PlumaGioDocumentSaverPrivate PlumaGioDocumentSaverPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaGioDocumentSaver PlumaGioDocumentSaver;
+
+struct _PlumaGioDocumentSaver
+{
+ PlumaDocumentSaver saver;
+
+ /*< private > */
+ PlumaGioDocumentSaverPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef PlumaDocumentSaverClass PlumaGioDocumentSaverClass;
+
+/*
+ * Public methods
+ */
+GType pluma_gio_document_saver_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __PLUMA_GIO_DOCUMENT_SAVER_H__ */
diff --git a/pluma/pluma-help.c b/pluma/pluma-help.c
new file mode 100755
index 00000000..239e3818
--- /dev/null
+++ b/pluma/pluma-help.c
@@ -0,0 +1,122 @@
+/*
+ * pluma-help.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pluma-help.h"
+
+#include <glib/gi18n.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#ifdef OS_OSX
+#include "osx/pluma-osx.h"
+#endif
+
+gboolean
+pluma_help_display (GtkWindow *parent,
+ const gchar *name, /* "pluma" if NULL */
+ const gchar *link_id)
+{
+ GError *error = NULL;
+ gboolean ret;
+ gchar *link;
+
+ g_return_val_if_fail ((parent == NULL) || GTK_IS_WINDOW (parent), FALSE);
+
+#ifdef OS_OSX
+ if (name == NULL || strcmp(name, "pluma.xml") == NULL || strcmp(name, "pluma") == 0)
+ {
+ return pluma_osx_show_help (link_id);
+ }
+ else
+ {
+ return FALSE;
+ }
+#endif
+
+ if (name == NULL)
+ name = "pluma";
+ else if (strcmp (name, "pluma.xml") == 0)
+ {
+ g_warning ("%s: Using \"pluma.xml\" for the help name is deprecated, use \"pluma\" or simply NULL instead", G_STRFUNC);
+
+ name = "pluma";
+ }
+
+#ifndef G_OS_WIN32
+ if (link_id)
+ link = g_strdup_printf ("ghelp:%s?%s", name, link_id);
+ else
+ link = g_strdup_printf ("ghelp:%s", name);
+#else
+ if (link_id)
+ link = g_strdup_printf ("http://library.gnome.org/users/pluma/stable/%s",
+ link_id);
+ else
+ link = g_strdup ("http://library.gnome.org/users/pluma/stable/");
+#endif
+
+ ret = gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (parent)),
+ link,
+ GDK_CURRENT_TIME,
+ &error);
+
+ g_free (link);
+
+ if (error != NULL)
+ {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("There was an error displaying the help."));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_widget_show (dialog);
+
+ g_error_free (error);
+ }
+
+ return ret;
+}
diff --git a/pluma/pluma-help.h b/pluma/pluma-help.h
new file mode 100755
index 00000000..33c3c533
--- /dev/null
+++ b/pluma/pluma-help.h
@@ -0,0 +1,44 @@
+/*
+ * pluma-help.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_HELP_H__
+#define __PLUMA_HELP_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gboolean pluma_help_display (GtkWindow *parent,
+ const gchar *name, /* "pluma" if NULL */
+ const gchar *link_id);
+
+G_END_DECLS
+
+#endif /* __PLUMA_HELP_H__ */
diff --git a/pluma/pluma-history-entry.c b/pluma/pluma-history-entry.c
new file mode 100755
index 00000000..f44d0ccf
--- /dev/null
+++ b/pluma/pluma-history-entry.c
@@ -0,0 +1,632 @@
+/*
+ * pluma-history-entry.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2006 - Paolo Borelli
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <mateconf/mateconf-client.h>
+
+#include "pluma-history-entry.h"
+
+enum {
+ PROP_0,
+ PROP_HISTORY_ID,
+ PROP_HISTORY_LENGTH
+};
+
+#define MIN_ITEM_LEN 3
+
+#define PLUMA_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT 10
+
+#define PLUMA_HISTORY_ENTRY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_HISTORY_ENTRY, \
+ PlumaHistoryEntryPrivate))
+
+struct _PlumaHistoryEntryPrivate
+{
+ gchar *history_id;
+ guint history_length;
+
+ GtkEntryCompletion *completion;
+
+ MateConfClient *mateconf_client;
+};
+
+G_DEFINE_TYPE(PlumaHistoryEntry, pluma_history_entry, GTK_TYPE_COMBO_BOX_ENTRY)
+
+static void
+pluma_history_entry_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ PlumaHistoryEntry *entry;
+
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (object));
+
+ entry = PLUMA_HISTORY_ENTRY (object);
+
+ switch (prop_id) {
+ case PROP_HISTORY_ID:
+ entry->priv->history_id = g_value_dup_string (value);
+ break;
+ case PROP_HISTORY_LENGTH:
+ pluma_history_entry_set_history_length (entry,
+ g_value_get_uint (value));
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+pluma_history_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ PlumaHistoryEntryPrivate *priv;
+
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (object));
+
+ priv = PLUMA_HISTORY_ENTRY (object)->priv;
+
+ switch (prop_id) {
+ case PROP_HISTORY_ID:
+ g_value_set_string (value, priv->history_id);
+ break;
+ case PROP_HISTORY_LENGTH:
+ g_value_set_uint (value, priv->history_length);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
+ }
+}
+
+static void
+pluma_history_entry_destroy (GtkObject *object)
+{
+ pluma_history_entry_set_enable_completion (PLUMA_HISTORY_ENTRY (object),
+ FALSE);
+
+ GTK_OBJECT_CLASS (pluma_history_entry_parent_class)->destroy (object);
+}
+
+static void
+pluma_history_entry_finalize (GObject *object)
+{
+ PlumaHistoryEntryPrivate *priv;
+
+ priv = PLUMA_HISTORY_ENTRY (object)->priv;
+
+ g_free (priv->history_id);
+
+ if (priv->mateconf_client != NULL)
+ {
+ g_object_unref (G_OBJECT (priv->mateconf_client));
+ priv->mateconf_client = NULL;
+ }
+
+ G_OBJECT_CLASS (pluma_history_entry_parent_class)->finalize (object);
+}
+
+static void
+pluma_history_entry_class_init (PlumaHistoryEntryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
+
+ object_class->set_property = pluma_history_entry_set_property;
+ object_class->get_property = pluma_history_entry_get_property;
+ object_class->finalize = pluma_history_entry_finalize;
+ gtkobject_class->destroy = pluma_history_entry_destroy;
+
+ g_object_class_install_property (object_class,
+ PROP_HISTORY_ID,
+ g_param_spec_string ("history-id",
+ "History ID",
+ "History ID",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_HISTORY_LENGTH,
+ g_param_spec_uint ("history-length",
+ "Max History Length",
+ "Max History Length",
+ 0,
+ G_MAXUINT,
+ PLUMA_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /* TODO: Add enable-completion property */
+
+ g_type_class_add_private (object_class, sizeof(PlumaHistoryEntryPrivate));
+}
+
+static GtkListStore *
+get_history_store (PlumaHistoryEntry *entry)
+{
+ GtkTreeModel *store;
+
+ store = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
+ g_return_val_if_fail (GTK_IS_LIST_STORE (store), NULL);
+
+ return (GtkListStore *) store;
+}
+
+static char *
+get_history_key (PlumaHistoryEntry *entry)
+{
+ gchar *tmp;
+ gchar *key;
+
+ /*
+ * We store the data under /apps/mate-settings/
+ * like the old MateEntry did. Maybe we should
+ * consider moving it to the /pluma MateConf prefix...
+ * Or maybe we should just switch away from MateConf.
+ */
+
+ tmp = mateconf_escape_key (entry->priv->history_id, -1);
+ key = g_strconcat ("/apps/mate-settings/",
+ "pluma",
+ "/history-",
+ tmp,
+ NULL);
+ g_free (tmp);
+
+ return key;
+}
+
+static GSList *
+get_history_list (PlumaHistoryEntry *entry)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gboolean valid;
+ GSList *list = NULL;
+
+ store = get_history_store (entry);
+
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store),
+ &iter);
+
+ while (valid)
+ {
+ gchar *str;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store),
+ &iter,
+ 0, &str,
+ -1);
+
+ list = g_slist_prepend (list, str);
+
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
+ &iter);
+ }
+
+ return g_slist_reverse (list);
+}
+
+static void
+pluma_history_entry_save_history (PlumaHistoryEntry *entry)
+{
+ GSList *mateconf_items;
+ gchar *key;
+
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+
+ mateconf_items = get_history_list (entry);
+ key = get_history_key (entry);
+
+ mateconf_client_set_list (entry->priv->mateconf_client,
+ key,
+ MATECONF_VALUE_STRING,
+ mateconf_items,
+ NULL);
+
+ g_slist_foreach (mateconf_items, (GFunc) g_free, NULL);
+ g_slist_free (mateconf_items);
+ g_free (key);
+}
+
+static gboolean
+remove_item (GtkListStore *store,
+ const gchar *text)
+{
+ GtkTreeIter iter;
+
+ g_return_val_if_fail (text != NULL, FALSE);
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+ return FALSE;
+
+ do
+ {
+ gchar *item_text;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store),
+ &iter,
+ 0,
+ &item_text,
+ -1);
+
+ if (item_text != NULL &&
+ strcmp (item_text, text) == 0)
+ {
+ gtk_list_store_remove (store, &iter);
+ g_free (item_text);
+ return TRUE;
+ }
+
+ g_free (item_text);
+
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+
+ return FALSE;
+}
+
+static void
+clamp_list_store (GtkListStore *store,
+ guint max)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ /* -1 because TreePath counts from 0 */
+ path = gtk_tree_path_new_from_indices (max - 1, -1);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+ {
+ while (1)
+ {
+ if (!gtk_list_store_remove (store, &iter))
+ break;
+ }
+ }
+
+ gtk_tree_path_free (path);
+}
+
+static void
+insert_history_item (PlumaHistoryEntry *entry,
+ const gchar *text,
+ gboolean prepend)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ if (g_utf8_strlen (text, -1) <= MIN_ITEM_LEN)
+ return;
+
+ store = get_history_store (entry);
+
+ /* remove the text from the store if it was already
+ * present. If it wasn't, clamp to max history - 1
+ * before inserting the new row, otherwise appending
+ * would not work */
+
+ if (!remove_item (store, text))
+ clamp_list_store (store,
+ entry->priv->history_length - 1);
+
+ if (prepend)
+ gtk_list_store_insert (store, &iter, 0);
+ else
+ gtk_list_store_append (store, &iter);
+
+ gtk_list_store_set (store,
+ &iter,
+ 0,
+ text,
+ -1);
+
+ pluma_history_entry_save_history (entry);
+}
+
+void
+pluma_history_entry_prepend_text (PlumaHistoryEntry *entry,
+ const gchar *text)
+{
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+ g_return_if_fail (text != NULL);
+
+ insert_history_item (entry, text, TRUE);
+}
+
+void
+pluma_history_entry_append_text (PlumaHistoryEntry *entry,
+ const gchar *text)
+{
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+ g_return_if_fail (text != NULL);
+
+ insert_history_item (entry, text, FALSE);
+}
+
+static void
+pluma_history_entry_load_history (PlumaHistoryEntry *entry)
+{
+ GSList *mateconf_items, *l;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gchar *key;
+ guint i;
+
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+
+ store = get_history_store (entry);
+ key = get_history_key (entry);
+
+ mateconf_items = mateconf_client_get_list (entry->priv->mateconf_client,
+ key,
+ MATECONF_VALUE_STRING,
+ NULL);
+
+ gtk_list_store_clear (store);
+
+ for (l = mateconf_items, i = 0;
+ l != NULL && i < entry->priv->history_length;
+ l = l->next, i++)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store,
+ &iter,
+ 0,
+ l->data,
+ -1);
+ }
+
+ g_slist_foreach (mateconf_items, (GFunc) g_free, NULL);
+ g_slist_free (mateconf_items);
+ g_free (key);
+}
+
+void
+pluma_history_entry_clear (PlumaHistoryEntry *entry)
+{
+ GtkListStore *store;
+
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+
+ store = get_history_store (entry);
+ gtk_list_store_clear (store);
+
+ pluma_history_entry_save_history (entry);
+}
+
+static void
+pluma_history_entry_init (PlumaHistoryEntry *entry)
+{
+ PlumaHistoryEntryPrivate *priv;
+
+ priv = PLUMA_HISTORY_ENTRY_GET_PRIVATE (entry);
+ entry->priv = priv;
+
+ priv->history_id = NULL;
+ priv->history_length = PLUMA_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT;
+
+ priv->completion = NULL;
+
+ priv->mateconf_client = mateconf_client_get_default ();
+}
+
+void
+pluma_history_entry_set_history_length (PlumaHistoryEntry *entry,
+ guint history_length)
+{
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+ g_return_if_fail (history_length > 0);
+
+ entry->priv->history_length = history_length;
+
+ /* TODO: update if we currently have more items than max */
+}
+
+guint
+pluma_history_entry_get_history_length (PlumaHistoryEntry *entry)
+{
+ g_return_val_if_fail (PLUMA_IS_HISTORY_ENTRY (entry), 0);
+
+ return entry->priv->history_length;
+}
+
+gchar *
+pluma_history_entry_get_history_id (PlumaHistoryEntry *entry)
+{
+ g_return_val_if_fail (PLUMA_IS_HISTORY_ENTRY (entry), NULL);
+
+ return g_strdup (entry->priv->history_id);
+}
+
+void
+pluma_history_entry_set_enable_completion (PlumaHistoryEntry *entry,
+ gboolean enable)
+{
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+
+ if (enable)
+ {
+ if (entry->priv->completion != NULL)
+ return;
+
+ entry->priv->completion = gtk_entry_completion_new ();
+ gtk_entry_completion_set_model (entry->priv->completion,
+ GTK_TREE_MODEL (get_history_store (entry)));
+
+ /* Use model column 0 as the text column */
+ gtk_entry_completion_set_text_column (entry->priv->completion, 0);
+
+ gtk_entry_completion_set_minimum_key_length (entry->priv->completion,
+ MIN_ITEM_LEN);
+
+ gtk_entry_completion_set_popup_completion (entry->priv->completion, FALSE);
+ gtk_entry_completion_set_inline_completion (entry->priv->completion, TRUE);
+
+ /* Assign the completion to the entry */
+ gtk_entry_set_completion (GTK_ENTRY (pluma_history_entry_get_entry(entry)),
+ entry->priv->completion);
+ }
+ else
+ {
+ if (entry->priv->completion == NULL)
+ return;
+
+ gtk_entry_set_completion (GTK_ENTRY (pluma_history_entry_get_entry (entry)),
+ NULL);
+
+ g_object_unref (entry->priv->completion);
+
+ entry->priv->completion = NULL;
+ }
+}
+
+gboolean
+pluma_history_entry_get_enable_completion (PlumaHistoryEntry *entry)
+{
+ g_return_val_if_fail (PLUMA_IS_HISTORY_ENTRY (entry), FALSE);
+
+ return entry->priv->completion != NULL;
+}
+
+GtkWidget *
+pluma_history_entry_new (const gchar *history_id,
+ gboolean enable_completion)
+{
+ GtkWidget *ret;
+ GtkListStore *store;
+
+ g_return_val_if_fail (history_id != NULL, NULL);
+
+ /* Note that we are setting the model, so
+ * user must be careful to always manipulate
+ * data in the history through pluma_history_entry_
+ * functions.
+ */
+
+ store = gtk_list_store_new (1, G_TYPE_STRING);
+
+ ret = g_object_new (PLUMA_TYPE_HISTORY_ENTRY,
+ "history-id", history_id,
+ "model", store,
+ "text-column", 0,
+ NULL);
+
+ g_object_unref (store);
+
+ /* loading has to happen after the model
+ * has been set. However the model is not a
+ * G_PARAM_CONSTRUCT property of GtkComboBox
+ * so we cannot do this in the constructor.
+ * For now we simply do here since this widget is
+ * not bound to other programming languages.
+ * A maybe better alternative is to override the
+ * model property of combobox and mark CONTRUCT_ONLY.
+ * This would also ensure that the model cannot be
+ * set explicitely at a later time.
+ */
+ pluma_history_entry_load_history (PLUMA_HISTORY_ENTRY (ret));
+
+ pluma_history_entry_set_enable_completion (PLUMA_HISTORY_ENTRY (ret),
+ enable_completion);
+
+ return ret;
+}
+
+/*
+ * Utility function to get the editable text entry internal widget.
+ * I would prefer to not expose this implementation detail and
+ * simply make the PlumaHistoryEntry widget implement the
+ * GtkEditable interface. Unfortunately both GtkEditable and
+ * GtkComboBox have a "changed" signal and I am not sure how to
+ * handle the conflict.
+ */
+GtkWidget *
+pluma_history_entry_get_entry (PlumaHistoryEntry *entry)
+{
+ g_return_val_if_fail (PLUMA_IS_HISTORY_ENTRY (entry), NULL);
+
+ return gtk_bin_get_child (GTK_BIN (entry));
+}
+
+static void
+escape_cell_data_func (GtkTreeViewColumn *col,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ PlumaHistoryEntryEscapeFunc escape_func)
+{
+ gchar *str;
+ gchar *escaped;
+
+ gtk_tree_model_get (model, iter, 0, &str, -1);
+ escaped = escape_func (str);
+ g_object_set (renderer, "text", escaped, NULL);
+
+ g_free (str);
+ g_free (escaped);
+}
+
+void
+pluma_history_entry_set_escape_func (PlumaHistoryEntry *entry,
+ PlumaHistoryEntryEscapeFunc escape_func)
+{
+ GList *cells;
+
+ g_return_if_fail (PLUMA_IS_HISTORY_ENTRY (entry));
+
+ cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (entry));
+
+ /* We only have one cell renderer */
+ g_return_if_fail (cells->data != NULL && cells->next == NULL);
+
+ if (escape_func != NULL)
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (entry),
+ GTK_CELL_RENDERER (cells->data),
+ (GtkCellLayoutDataFunc) escape_cell_data_func,
+ escape_func,
+ NULL);
+ else
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (entry),
+ GTK_CELL_RENDERER (cells->data),
+ NULL,
+ NULL,
+ NULL);
+
+ g_list_free (cells);
+}
diff --git a/pluma/pluma-history-entry.h b/pluma/pluma-history-entry.h
new file mode 100755
index 00000000..59d16767
--- /dev/null
+++ b/pluma/pluma-history-entry.h
@@ -0,0 +1,96 @@
+/*
+ * pluma-history-entry.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2006 - Paolo Borelli
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_HISTORY_ENTRY_H__
+#define __PLUMA_HISTORY_ENTRY_H__
+
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_HISTORY_ENTRY (pluma_history_entry_get_type ())
+#define PLUMA_HISTORY_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_HISTORY_ENTRY, PlumaHistoryEntry))
+#define PLUMA_HISTORY_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_HISTORY_ENTRY, PlumaHistoryEntryClass))
+#define PLUMA_IS_HISTORY_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_HISTORY_ENTRY))
+#define PLUMA_IS_HISTORY_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_HISTORY_ENTRY))
+#define PLUMA_HISTORY_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_HISTORY_ENTRY, PlumaHistoryEntryClass))
+
+
+typedef struct _PlumaHistoryEntry PlumaHistoryEntry;
+typedef struct _PlumaHistoryEntryClass PlumaHistoryEntryClass;
+typedef struct _PlumaHistoryEntryPrivate PlumaHistoryEntryPrivate;
+
+struct _PlumaHistoryEntryClass
+{
+ GtkComboBoxEntryClass parent_class;
+};
+
+struct _PlumaHistoryEntry
+{
+ GtkComboBoxEntry parent_instance;
+
+ PlumaHistoryEntryPrivate *priv;
+};
+
+GType pluma_history_entry_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_history_entry_new (const gchar *history_id,
+ gboolean enable_completion);
+
+void pluma_history_entry_prepend_text (PlumaHistoryEntry *entry,
+ const gchar *text);
+
+void pluma_history_entry_append_text (PlumaHistoryEntry *entry,
+ const gchar *text);
+
+void pluma_history_entry_clear (PlumaHistoryEntry *entry);
+
+void pluma_history_entry_set_history_length (PlumaHistoryEntry *entry,
+ guint max_saved);
+
+guint pluma_history_entry_get_history_length (PlumaHistoryEntry *gentry);
+
+gchar *pluma_history_entry_get_history_id (PlumaHistoryEntry *entry);
+
+void pluma_history_entry_set_enable_completion
+ (PlumaHistoryEntry *entry,
+ gboolean enable);
+
+gboolean pluma_history_entry_get_enable_completion
+ (PlumaHistoryEntry *entry);
+
+GtkWidget *pluma_history_entry_get_entry (PlumaHistoryEntry *entry);
+
+typedef gchar * (* PlumaHistoryEntryEscapeFunc) (const gchar *str);
+void pluma_history_entry_set_escape_func (PlumaHistoryEntry *entry,
+ PlumaHistoryEntryEscapeFunc escape_func);
+
+G_END_DECLS
+
+#endif /* __PLUMA_HISTORY_ENTRY_H__ */
diff --git a/pluma/pluma-io-error-message-area.c b/pluma/pluma-io-error-message-area.c
new file mode 100755
index 00000000..4de896f9
--- /dev/null
+++ b/pluma/pluma-io-error-message-area.c
@@ -0,0 +1,1288 @@
+/*
+ * pluma-io-error-message-area.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+/*
+ * Verbose error reporting for file I/O operations (load, save, revert, create)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "pluma-utils.h"
+#include "pluma-document.h"
+#include "pluma-io-error-message-area.h"
+#include "pluma-prefs-manager.h"
+#include <pluma/pluma-encodings-combo-box.h>
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+#include "pluma-message-area.h"
+#endif
+
+#define MAX_URI_IN_DIALOG_LENGTH 50
+
+static gboolean
+is_recoverable_error (const GError *error)
+{
+ gboolean is_recoverable = FALSE;
+
+ if (error->domain == G_IO_ERROR)
+ {
+ switch (error->code) {
+ case G_IO_ERROR_PERMISSION_DENIED:
+ case G_IO_ERROR_NOT_FOUND:
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ case G_IO_ERROR_TIMED_OUT:
+ case G_IO_ERROR_NOT_MOUNTABLE_FILE:
+ case G_IO_ERROR_NOT_MOUNTED:
+ case G_IO_ERROR_BUSY:
+ is_recoverable = TRUE;
+ }
+ }
+
+ return is_recoverable;
+}
+
+static gboolean
+is_gio_error (const GError *error,
+ gint code)
+{
+ return error->domain == G_IO_ERROR && error->code == code;
+}
+
+static void
+set_contents (GtkWidget *area,
+ GtkWidget *contents)
+{
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_set_contents (PLUMA_MESSAGE_AREA (area),
+ contents);
+#else
+ GtkWidget *content_area;
+
+ content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (area));
+ gtk_container_add (GTK_CONTAINER (content_area), contents);
+#endif
+}
+
+#if GTK_CHECK_VERSION (2, 17, 1)
+static void
+info_bar_add_stock_button_with_text (GtkInfoBar *infobar,
+ const gchar *text,
+ const gchar *stock_id,
+ gint response_id)
+{
+ GtkWidget *button;
+ GtkWidget *image;
+
+ button = gtk_info_bar_add_button (infobar, text, response_id);
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image (GTK_BUTTON (button), image);
+}
+#endif
+
+static void
+set_message_area_text_and_icon (GtkWidget *message_area,
+ const gchar *icon_stock_id,
+ const gchar *primary_text,
+ const gchar *secondary_text)
+{
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ gchar *primary_markup;
+ gchar *secondary_markup;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+
+ image = gtk_image_new_from_stock (icon_stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+ primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
+ primary_label = gtk_label_new (primary_markup);
+ g_free (primary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ if (secondary_text != NULL)
+ {
+ secondary_markup = g_strdup_printf ("<small>%s</small>",
+ secondary_text);
+ secondary_label = gtk_label_new (secondary_markup);
+ g_free (secondary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
+ gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+ }
+
+ gtk_widget_show_all (hbox_content);
+ set_contents (message_area, hbox_content);
+}
+
+static GtkWidget *
+create_io_loading_error_message_area (const gchar *primary_text,
+ const gchar *secondary_text,
+ gboolean recoverable_error)
+{
+ GtkWidget *message_area;
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ message_area = pluma_message_area_new_with_buttons (
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ NULL);
+#else
+ message_area = gtk_info_bar_new_with_buttons (
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ NULL);
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area),
+ GTK_MESSAGE_ERROR);
+#endif
+
+ set_message_area_text_and_icon (message_area,
+ "gtk-dialog-error",
+ primary_text,
+ secondary_text);
+
+ if (recoverable_error)
+ {
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_add_stock_button_with_text (PLUMA_MESSAGE_AREA (message_area),
+ _("_Retry"),
+ GTK_STOCK_REFRESH,
+ GTK_RESPONSE_OK);
+#else
+ info_bar_add_stock_button_with_text (GTK_INFO_BAR (message_area),
+ _("_Retry"),
+ GTK_STOCK_REFRESH,
+ GTK_RESPONSE_OK);
+#endif
+ }
+
+ return message_area;
+}
+
+static gboolean
+parse_gio_error (gint code,
+ gchar **error_message,
+ gchar **message_details,
+ const gchar *uri,
+ const gchar *uri_for_display)
+{
+ gboolean ret = TRUE;
+
+ switch (code)
+ {
+ case G_IO_ERROR_NOT_FOUND:
+ case G_IO_ERROR_NOT_DIRECTORY:
+ *error_message = g_strdup_printf (_("Could not find the file %s."),
+ uri_for_display);
+ *message_details = g_strdup (_("Please check that you typed the "
+ "location correctly and try again."));
+ break;
+ case G_IO_ERROR_NOT_SUPPORTED:
+ {
+ gchar *scheme_string;
+ gchar *scheme_markup;
+
+ scheme_string = g_uri_parse_scheme (uri);
+
+ if ((scheme_string != NULL) && g_utf8_validate (scheme_string, -1, NULL))
+ {
+ scheme_markup = g_markup_printf_escaped ("<i>%s:</i>", scheme_string);
+
+ /* Translators: %s is a URI scheme (like for example http:, ftp:, etc.) */
+ *message_details = g_strdup_printf (_("pluma cannot handle %s locations."),
+ scheme_markup);
+ g_free (scheme_markup);
+ }
+ else
+ {
+ *message_details = g_strdup (_("pluma cannot handle this location."));
+ }
+
+ g_free (scheme_string);
+ }
+ break;
+
+ case G_IO_ERROR_NOT_MOUNTABLE_FILE:
+ *message_details = g_strdup (_("The location of the file cannot be mounted."));
+ break;
+
+ case G_IO_ERROR_NOT_MOUNTED:
+ *message_details = g_strdup( _("The location of the file cannot be accessed because it is not mounted."));
+
+ break;
+ case G_IO_ERROR_IS_DIRECTORY:
+ *error_message = g_strdup_printf (_("%s is a directory."),
+ uri_for_display);
+ *message_details = g_strdup (_("Please check that you typed the "
+ "location correctly and try again."));
+ break;
+
+ case G_IO_ERROR_INVALID_FILENAME:
+ *error_message = g_strdup_printf (_("%s is not a valid location."),
+ uri_for_display);
+ *message_details = g_strdup (_("Please check that you typed the "
+ "location correctly and try again."));
+ break;
+
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ /* This case can be hit for user-typed strings like "foo" due to
+ * the code that guesses web addresses when there's no initial "/".
+ * But this case is also hit for legitimate web addresses when
+ * the proxy is set up wrong.
+ */
+ {
+ gchar *hn = NULL;
+
+ if (pluma_utils_decode_uri (uri, NULL, NULL, &hn, NULL, NULL))
+ {
+ if (hn != NULL)
+ {
+ gchar *host_markup;
+ gchar *host_name;
+
+ host_name = pluma_utils_make_valid_utf8 (hn);
+ g_free (hn);
+
+ host_markup = g_markup_printf_escaped ("<i>%s</i>", host_name);
+ g_free (host_name);
+
+ /* Translators: %s is a host name */
+ *message_details = g_strdup_printf (
+ _("Host %s could not be found. "
+ "Please check that your proxy settings "
+ "are correct and try again."),
+ host_markup);
+
+ g_free (host_markup);
+ }
+ }
+
+ if (!*message_details)
+ {
+ /* use the same string as INVALID_HOST */
+ *message_details = g_strdup_printf (
+ _("Hostname was invalid. "
+ "Please check that you typed the location "
+ "correctly and try again."));
+ }
+ }
+ break;
+
+ case G_IO_ERROR_NOT_REGULAR_FILE:
+ *message_details = g_strdup_printf (_("%s is not a regular file."),
+ uri_for_display);
+ break;
+
+ case G_IO_ERROR_TIMED_OUT:
+ *message_details = g_strdup (_("Connection timed out. Please try again."));
+ break;
+
+ default:
+ ret = FALSE;
+ break;
+ }
+
+ return ret;
+}
+
+static gboolean
+parse_pluma_error (gint code,
+ gchar **error_message,
+ gchar **message_details,
+ const gchar *uri,
+ const gchar *uri_for_display)
+{
+ gboolean ret = TRUE;
+
+ switch (code)
+ {
+ case PLUMA_DOCUMENT_ERROR_TOO_BIG:
+ *message_details = g_strdup (_("The file is too big."));
+ break;
+
+ default:
+ ret = FALSE;
+ break;
+ }
+
+ return ret;
+}
+
+static void
+parse_error (const GError *error,
+ gchar **error_message,
+ gchar **message_details,
+ const gchar *uri,
+ const gchar *uri_for_display)
+{
+ gboolean ret = FALSE;
+
+ if (error->domain == G_IO_ERROR)
+ {
+ ret = parse_gio_error (error->code,
+ error_message,
+ message_details,
+ uri,
+ uri_for_display);
+ }
+ else if (error->domain == PLUMA_DOCUMENT_ERROR)
+ {
+ ret = parse_pluma_error (error->code,
+ error_message,
+ message_details,
+ uri,
+ uri_for_display);
+ }
+
+ if (!ret)
+ {
+ g_warning ("Hit unhandled case %d (%s) in %s.",
+ error->code, error->message, G_STRFUNC);
+ *message_details = g_strdup_printf (_("Unexpected error: %s"),
+ error->message);
+ }
+}
+
+GtkWidget *
+pluma_unrecoverable_reverting_error_message_area_new (const gchar *uri,
+ const GError *error)
+{
+ gchar *error_message = NULL;
+ gchar *message_details = NULL;
+ gchar *full_formatted_uri;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+ GtkWidget *message_area;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+ g_return_val_if_fail ((error->domain == PLUMA_DOCUMENT_ERROR) ||
+ (error->domain == G_IO_ERROR), NULL);
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+ if (is_gio_error (error, G_IO_ERROR_NOT_FOUND))
+ {
+ message_details = g_strdup (_("pluma cannot find the file. "
+ "Perhaps it has recently been deleted."));
+ }
+ else
+ {
+ parse_error (error, &error_message, &message_details, uri, uri_for_display);
+ }
+
+ if (error_message == NULL)
+ {
+ error_message = g_strdup_printf (_("Could not revert the file %s."),
+ uri_for_display);
+ }
+
+ message_area = create_io_loading_error_message_area (error_message,
+ message_details,
+ FALSE);
+
+ g_free (uri_for_display);
+ g_free (error_message);
+ g_free (message_details);
+
+ return message_area;
+}
+
+static void
+create_combo_box (GtkWidget *message_area, GtkWidget *vbox)
+{
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkWidget *menu;
+ gchar *label_markup;
+
+ hbox = gtk_hbox_new (FALSE, 6);
+
+ label_markup = g_strdup_printf ("<small>%s</small>",
+ _("Ch_aracter Encoding:"));
+ label = gtk_label_new_with_mnemonic (label_markup);
+ g_free (label_markup);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+ menu = pluma_encodings_combo_box_new (TRUE);
+ g_object_set_data (G_OBJECT (message_area),
+ "pluma-message-area-encoding-menu",
+ menu);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), menu);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ label,
+ FALSE,
+ FALSE,
+ 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox),
+ menu,
+ FALSE,
+ FALSE,
+ 0);
+
+ gtk_widget_show_all (hbox);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
+}
+
+static GtkWidget *
+create_conversion_error_message_area (const gchar *primary_text,
+ const gchar *secondary_text,
+ gboolean edit_anyway)
+{
+ GtkWidget *message_area;
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ gchar *primary_markup;
+ gchar *secondary_markup;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ message_area = pluma_message_area_new ();
+
+ pluma_message_area_add_stock_button_with_text (PLUMA_MESSAGE_AREA (message_area),
+ _("_Retry"),
+ GTK_STOCK_REDO,
+ GTK_RESPONSE_OK);
+
+ if (edit_anyway)
+ {
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ _("Edit Any_way"),
+ GTK_RESPONSE_YES);
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ _("D_on't Edit"),
+ GTK_RESPONSE_NO);
+ }
+ else
+ {
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ }
+#else
+ message_area = gtk_info_bar_new ();
+
+ info_bar_add_stock_button_with_text (GTK_INFO_BAR (message_area),
+ _("_Retry"),
+ GTK_STOCK_REDO,
+ GTK_RESPONSE_OK);
+
+ if (edit_anyway)
+ {
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ /* Translators: the access key chosen for this string should be
+ different from other main menu access keys (Open, Edit, View...) */
+ _("Edit Any_way"),
+ GTK_RESPONSE_YES);
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ /* Translators: the access key chosen for this string should be
+ different from other main menu access keys (Open, Edit, View...) */
+ _("D_on't Edit"),
+ GTK_RESPONSE_NO);
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area),
+ GTK_MESSAGE_WARNING);
+ }
+ else
+ {
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area),
+ GTK_MESSAGE_ERROR);
+ }
+#endif
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+
+ image = gtk_image_new_from_stock ("gtk-dialog-error", GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+ primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
+ primary_label = gtk_label_new (primary_markup);
+ g_free (primary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ if (secondary_text != NULL)
+ {
+ secondary_markup = g_strdup_printf ("<small>%s</small>",
+ secondary_text);
+ secondary_label = gtk_label_new (secondary_markup);
+ g_free (secondary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
+ gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+ }
+
+ create_combo_box (message_area, vbox);
+ gtk_widget_show_all (hbox_content);
+ set_contents (message_area, hbox_content);
+
+ return message_area;
+}
+
+GtkWidget *
+pluma_io_loading_error_message_area_new (const gchar *uri,
+ const PlumaEncoding *encoding,
+ const GError *error)
+{
+ gchar *error_message = NULL;
+ gchar *message_details = NULL;
+ gchar *full_formatted_uri;
+ gchar *encoding_name;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+ GtkWidget *message_area;
+ gboolean edit_anyway = FALSE;
+ gboolean convert_error = FALSE;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+ g_return_val_if_fail ((error->domain == G_CONVERT_ERROR) ||
+ (error->domain == PLUMA_DOCUMENT_ERROR) ||
+ (error->domain == G_IO_ERROR), NULL);
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+ if (encoding != NULL)
+ encoding_name = pluma_encoding_to_string (encoding);
+ else
+ encoding_name = g_strdup ("UTF-8");
+
+ if (is_gio_error (error, G_IO_ERROR_TOO_MANY_LINKS))
+ {
+ message_details = g_strdup (_("The number of followed links is limited and the actual file could not be found within this limit."));
+ }
+ else if (is_gio_error (error, G_IO_ERROR_PERMISSION_DENIED))
+ {
+ message_details = g_strdup (_("You do not have the permissions necessary to open the file."));
+ }
+ else if ((is_gio_error (error, G_IO_ERROR_INVALID_DATA) && encoding == NULL) ||
+ (error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_ENCODING_AUTO_DETECTION_FAILED))
+ {
+ message_details = g_strconcat (_("pluma has not been able to detect "
+ "the character encoding."), "\n",
+ _("Please check that you are not trying to open a binary file."), "\n",
+ _("Select a character encoding from the menu and try again."), NULL);
+ convert_error = TRUE;
+ }
+ else if (error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_CONVERSION_FALLBACK)
+ {
+ error_message = g_strdup_printf (_("There was a problem opening the file %s."),
+ uri_for_display);
+ message_details = g_strconcat (_("The file you opened has some invalid characters. "
+ "If you continue editing this file you could make this "
+ "document useless."), "\n",
+ _("You can also choose another character encoding and try again."),
+ NULL);
+ edit_anyway = TRUE;
+ convert_error = TRUE;
+ }
+ else if (is_gio_error (error, G_IO_ERROR_INVALID_DATA) && encoding != NULL)
+ {
+ error_message = g_strdup_printf (_("Could not open the file %s using the %s character encoding."),
+ uri_for_display,
+ encoding_name);
+ message_details = g_strconcat (_("Please check that you are not trying to open a binary file."), "\n",
+ _("Select a different character encoding from the menu and try again."), NULL);
+ convert_error = TRUE;
+ }
+ else
+ {
+ parse_error (error, &error_message, &message_details, uri, uri_for_display);
+ }
+
+ if (error_message == NULL)
+ {
+ error_message = g_strdup_printf (_("Could not open the file %s."),
+ uri_for_display);
+ }
+
+ if (convert_error)
+ {
+ message_area = create_conversion_error_message_area (error_message,
+ message_details,
+ edit_anyway);
+ }
+ else
+ {
+ message_area = create_io_loading_error_message_area (error_message,
+ message_details,
+ is_recoverable_error (error));
+ }
+
+ g_free (uri_for_display);
+ g_free (encoding_name);
+ g_free (error_message);
+ g_free (message_details);
+
+ return message_area;
+}
+
+GtkWidget *
+pluma_conversion_error_while_saving_message_area_new (
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ const GError *error)
+{
+ gchar *error_message = NULL;
+ gchar *message_details = NULL;
+ gchar *full_formatted_uri;
+ gchar *encoding_name;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+ GtkWidget *message_area;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+ g_return_val_if_fail (error->domain == G_CONVERT_ERROR ||
+ error->domain == G_IO_ERROR, NULL);
+ g_return_val_if_fail (encoding != NULL, NULL);
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+ encoding_name = pluma_encoding_to_string (encoding);
+
+ error_message = g_strdup_printf (_("Could not save the file %s using the %s character encoding."),
+ uri_for_display,
+ encoding_name);
+ message_details = g_strconcat (_("The document contains one or more characters that cannot be encoded "
+ "using the specified character encoding."), "\n",
+ _("Select a different character encoding from the menu and try again."), NULL);
+
+ message_area = create_conversion_error_message_area (
+ error_message,
+ message_details,
+ FALSE);
+
+ g_free (uri_for_display);
+ g_free (encoding_name);
+ g_free (error_message);
+ g_free (message_details);
+
+ return message_area;
+}
+
+const PlumaEncoding *
+pluma_conversion_error_message_area_get_encoding (GtkWidget *message_area)
+{
+ gpointer menu;
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ g_return_val_if_fail (PLUMA_IS_MESSAGE_AREA (message_area), NULL);
+#else
+ g_return_val_if_fail (GTK_IS_INFO_BAR (message_area), NULL);
+#endif
+
+ menu = g_object_get_data (G_OBJECT (message_area),
+ "pluma-message-area-encoding-menu");
+ g_return_val_if_fail (menu, NULL);
+
+ return pluma_encodings_combo_box_get_selected_encoding
+ (PLUMA_ENCODINGS_COMBO_BOX (menu));
+}
+
+GtkWidget *
+pluma_file_already_open_warning_message_area_new (const gchar *uri)
+{
+ GtkWidget *message_area;
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ gchar *primary_markup;
+ gchar *secondary_markup;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+ gchar *primary_text;
+ const gchar *secondary_text;
+ gchar *full_formatted_uri;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ message_area = pluma_message_area_new ();
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ _("Edit Any_way"),
+ GTK_RESPONSE_YES);
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ _("D_on't Edit"),
+ GTK_RESPONSE_CANCEL);
+#else
+ message_area = gtk_info_bar_new ();
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ /* Translators: the access key chosen for this string should be
+ different from other main menu access keys (Open, Edit, View...) */
+ _("Edit Any_way"),
+ GTK_RESPONSE_YES);
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ /* Translators: the access key chosen for this string should be
+ different from other main menu access keys (Open, Edit, View...) */
+ _("D_on't Edit"),
+ GTK_RESPONSE_CANCEL);
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area),
+ GTK_MESSAGE_WARNING);
+#endif
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+
+ image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+ primary_text = g_strdup_printf (_("This file (%s) is already open in another pluma window."), uri_for_display);
+ g_free (uri_for_display);
+
+ primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
+ g_free (primary_text);
+ primary_label = gtk_label_new (primary_markup);
+ g_free (primary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ secondary_text = _("pluma opened this instance of the file in a non-editable way. "
+ "Do you want to edit it anyway?");
+ secondary_markup = g_strdup_printf ("<small>%s</small>",
+ secondary_text);
+ secondary_label = gtk_label_new (secondary_markup);
+ g_free (secondary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
+ gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+
+ gtk_widget_show_all (hbox_content);
+ set_contents (message_area, hbox_content);
+
+ return message_area;
+}
+
+GtkWidget *
+pluma_externally_modified_saving_error_message_area_new (
+ const gchar *uri,
+ const GError *error)
+{
+ GtkWidget *message_area;
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ gchar *primary_markup;
+ gchar *secondary_markup;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+ gchar *primary_text;
+ const gchar *secondary_text;
+ gchar *full_formatted_uri;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+ g_return_val_if_fail (error->domain == PLUMA_DOCUMENT_ERROR, NULL);
+ g_return_val_if_fail (error->code == PLUMA_DOCUMENT_ERROR_EXTERNALLY_MODIFIED, NULL);
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ message_area = pluma_message_area_new ();
+ pluma_message_area_add_stock_button_with_text (PLUMA_MESSAGE_AREA (message_area),
+ _("S_ave Anyway"),
+ GTK_STOCK_SAVE,
+ GTK_RESPONSE_YES);
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ _("D_on't Save"),
+ GTK_RESPONSE_CANCEL);
+#else
+ message_area = gtk_info_bar_new ();
+
+ info_bar_add_stock_button_with_text (GTK_INFO_BAR (message_area),
+ _("S_ave Anyway"),
+ GTK_STOCK_SAVE,
+ GTK_RESPONSE_YES);
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ _("D_on't Save"),
+ GTK_RESPONSE_CANCEL);
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area),
+ GTK_MESSAGE_WARNING);
+#endif
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+
+ image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+ // FIXME: review this message, it's not clear since for the user the "modification"
+ // could be interpreted as the changes he made in the document. beside "reading" is
+ // not accurate (since last load/save)
+ primary_text = g_strdup_printf (_("The file %s has been modified since reading it."),
+ uri_for_display);
+ g_free (uri_for_display);
+
+ primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
+ g_free (primary_text);
+ primary_label = gtk_label_new (primary_markup);
+ g_free (primary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ secondary_text = _("If you save it, all the external changes could be lost. Save it anyway?");
+ secondary_markup = g_strdup_printf ("<small>%s</small>",
+ secondary_text);
+ secondary_label = gtk_label_new (secondary_markup);
+ g_free (secondary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
+ gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+
+ gtk_widget_show_all (hbox_content);
+ set_contents (message_area, hbox_content);
+
+ return message_area;
+}
+
+GtkWidget *
+pluma_no_backup_saving_error_message_area_new (const gchar *uri,
+ const GError *error)
+{
+ GtkWidget *message_area;
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ gchar *primary_markup;
+ gchar *secondary_markup;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+ gchar *primary_text;
+ const gchar *secondary_text;
+ gchar *full_formatted_uri;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+ g_return_val_if_fail (((error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_CANT_CREATE_BACKUP) ||
+ (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_CANT_CREATE_BACKUP)), NULL);
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ message_area = pluma_message_area_new ();
+ pluma_message_area_add_stock_button_with_text (PLUMA_MESSAGE_AREA (message_area),
+ _("S_ave Anyway"),
+ GTK_STOCK_SAVE,
+ GTK_RESPONSE_YES);
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ _("D_on't Save"),
+ GTK_RESPONSE_CANCEL);
+#else
+ message_area = gtk_info_bar_new ();
+
+ info_bar_add_stock_button_with_text (GTK_INFO_BAR (message_area),
+ _("S_ave Anyway"),
+ GTK_STOCK_SAVE,
+ GTK_RESPONSE_YES);
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ _("D_on't Save"),
+ GTK_RESPONSE_CANCEL);
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area),
+ GTK_MESSAGE_WARNING);
+#endif
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+
+ image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+ // FIXME: review this messages
+
+ if (pluma_prefs_manager_get_create_backup_copy ())
+ primary_text = g_strdup_printf (_("Could not create a backup file while saving %s"),
+ uri_for_display);
+ else
+ primary_text = g_strdup_printf (_("Could not create a temporary backup file while saving %s"),
+ uri_for_display);
+
+ g_free (uri_for_display);
+
+ primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
+ g_free (primary_text);
+ primary_label = gtk_label_new (primary_markup);
+ g_free (primary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ secondary_text = _("pluma could not back up the old copy of the file before saving the new one. "
+ "You can ignore this warning and save the file anyway, but if an error "
+ "occurs while saving, you could lose the old copy of the file. Save anyway?");
+ secondary_markup = g_strdup_printf ("<small>%s</small>",
+ secondary_text);
+ secondary_label = gtk_label_new (secondary_markup);
+ g_free (secondary_markup);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
+ gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+
+ gtk_widget_show_all (hbox_content);
+ set_contents (message_area, hbox_content);
+
+ return message_area;
+}
+
+GtkWidget *
+pluma_unrecoverable_saving_error_message_area_new (const gchar *uri,
+ const GError *error)
+{
+ gchar *error_message = NULL;
+ gchar *message_details = NULL;
+ gchar *full_formatted_uri;
+ gchar *scheme_string;
+ gchar *scheme_markup;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+ GtkWidget *message_area;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (error != NULL, NULL);
+ g_return_val_if_fail ((error->domain == PLUMA_DOCUMENT_ERROR) ||
+ (error->domain == G_IO_ERROR), NULL);
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+ if (is_gio_error (error, G_IO_ERROR_NOT_SUPPORTED))
+ {
+ scheme_string = g_uri_parse_scheme (uri);
+
+ if ((scheme_string != NULL) && g_utf8_validate (scheme_string, -1, NULL))
+ {
+ scheme_markup = g_markup_printf_escaped ("<i>%s:</i>", scheme_string);
+
+ /* Translators: %s is a URI scheme (like for example http:, ftp:, etc.) */
+ message_details = g_strdup_printf (_("pluma cannot handle %s locations in write mode. "
+ "Please check that you typed the "
+ "location correctly and try again."),
+ scheme_markup);
+ g_free (scheme_markup);
+ }
+ else
+ {
+ message_details = g_strdup (_("pluma cannot handle this location in write mode. "
+ "Please check that you typed the "
+ "location correctly and try again."));
+ }
+
+ g_free (scheme_string);
+ }
+ else if (is_gio_error (error, G_IO_ERROR_INVALID_FILENAME))
+ {
+ message_details = g_strdup (_("%s is not a valid location. "
+ "Please check that you typed the "
+ "location correctly and try again."));
+ }
+ else if (is_gio_error (error, G_IO_ERROR_PERMISSION_DENIED))
+ {
+ message_details = g_strdup (_("You do not have the permissions necessary to save the file. "
+ "Please check that you typed the "
+ "location correctly and try again."));
+ }
+ else if (is_gio_error (error, G_IO_ERROR_NO_SPACE))
+ {
+ message_details = g_strdup (_("There is not enough disk space to save the file. "
+ "Please free some disk space and try again."));
+ }
+ else if (is_gio_error (error, G_IO_ERROR_READ_ONLY))
+ {
+ message_details = g_strdup (_("You are trying to save the file on a read-only disk. "
+ "Please check that you typed the location "
+ "correctly and try again."));
+ }
+ else if (is_gio_error (error, G_IO_ERROR_EXISTS))
+ {
+ message_details = g_strdup (_("A file with the same name already exists. "
+ "Please use a different name."));
+ }
+ else if (is_gio_error (error, G_IO_ERROR_FILENAME_TOO_LONG))
+ {
+ message_details = g_strdup (_("The disk where you are trying to save the file has "
+ "a limitation on length of the file names. "
+ "Please use a shorter name."));
+ }
+ else if (error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_TOO_BIG)
+ {
+ message_details = g_strdup (_("The disk where you are trying to save the file has "
+ "a limitation on file sizes. Please try saving "
+ "a smaller file or saving it to a disk that does not "
+ "have this limitation."));
+ }
+ else
+ {
+ parse_error (error,
+ &error_message,
+ &message_details,
+ uri,
+ uri_for_display);
+ }
+
+ if (error_message == NULL)
+ {
+ error_message = g_strdup_printf (_("Could not save the file %s."),
+ uri_for_display);
+ }
+
+ message_area = create_io_loading_error_message_area (error_message,
+ message_details,
+ FALSE);
+
+ g_free (uri_for_display);
+ g_free (error_message);
+ g_free (message_details);
+
+ return message_area;
+}
+
+GtkWidget *
+pluma_externally_modified_message_area_new (const gchar *uri,
+ gboolean document_modified)
+{
+ gchar *full_formatted_uri;
+ gchar *uri_for_display;
+ gchar *temp_uri_for_display;
+ const gchar *primary_text;
+ const gchar *secondary_text;
+ GtkWidget *message_area;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ full_formatted_uri = pluma_utils_uri_for_display (uri);
+
+ /* Truncate the URI so it doesn't get insanely wide. Note that even
+ * though the dialog uses wrapped text, if the URI doesn't contain
+ * white space then the text-wrapping code is too stupid to wrap it.
+ */
+ temp_uri_for_display = pluma_utils_str_middle_truncate (full_formatted_uri,
+ MAX_URI_IN_DIALOG_LENGTH);
+ g_free (full_formatted_uri);
+
+ uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
+ g_free (temp_uri_for_display);
+
+ // FIXME: review this message, it's not clear since for the user the "modification"
+ // could be interpreted as the changes he made in the document. beside "reading" is
+ // not accurate (since last load/save)
+ primary_text = g_strdup_printf (_("The file %s changed on disk."),
+ uri_for_display);
+ g_free (uri_for_display);
+
+ if (document_modified)
+ secondary_text = _("Do you want to drop your changes and reload the file?");
+ else
+ secondary_text = _("Do you want to reload the file?");
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ message_area = pluma_message_area_new ();
+
+ pluma_message_area_add_stock_button_with_text (PLUMA_MESSAGE_AREA (message_area),
+ _("_Reload"),
+ GTK_STOCK_REFRESH,
+ GTK_RESPONSE_OK);
+
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (message_area),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+#else
+ message_area = gtk_info_bar_new ();
+
+ info_bar_add_stock_button_with_text (GTK_INFO_BAR (message_area),
+ _("_Reload"),
+ GTK_STOCK_REFRESH,
+ GTK_RESPONSE_OK);
+ gtk_info_bar_add_button (GTK_INFO_BAR (message_area),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (message_area),
+ GTK_MESSAGE_WARNING);
+#endif
+
+ set_message_area_text_and_icon (message_area,
+ "gtk-dialog-warning",
+ primary_text,
+ secondary_text);
+
+ return message_area;
+}
+
diff --git a/pluma/pluma-io-error-message-area.h b/pluma/pluma-io-error-message-area.h
new file mode 100755
index 00000000..fbe9142f
--- /dev/null
+++ b/pluma/pluma-io-error-message-area.h
@@ -0,0 +1,68 @@
+/*
+ * pluma-io-error-message-area.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_IO_ERROR_MESSAGE_AREA_H__
+#define __PLUMA_IO_ERROR_MESSAGE_AREA_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *pluma_io_loading_error_message_area_new (const gchar *uri,
+ const PlumaEncoding *encoding,
+ const GError *error);
+
+GtkWidget *pluma_unrecoverable_reverting_error_message_area_new (const gchar *uri,
+ const GError *error);
+
+GtkWidget *pluma_conversion_error_while_saving_message_area_new (const gchar *uri,
+ const PlumaEncoding *encoding,
+ const GError *error);
+
+const PlumaEncoding
+ *pluma_conversion_error_message_area_get_encoding (GtkWidget *message_area);
+
+GtkWidget *pluma_file_already_open_warning_message_area_new (const gchar *uri);
+
+GtkWidget *pluma_externally_modified_saving_error_message_area_new (const gchar *uri,
+ const GError *error);
+
+GtkWidget *pluma_no_backup_saving_error_message_area_new (const gchar *uri,
+ const GError *error);
+
+GtkWidget *pluma_unrecoverable_saving_error_message_area_new (const gchar *uri,
+ const GError *error);
+
+GtkWidget *pluma_externally_modified_message_area_new (const gchar *uri,
+ gboolean document_modified);
+
+G_END_DECLS
+
+#endif /* __PLUMA_IO_ERROR_MESSAGE_AREA_H__ */
diff --git a/pluma/pluma-language-manager.c b/pluma/pluma-language-manager.c
new file mode 100755
index 00000000..4398f516
--- /dev/null
+++ b/pluma/pluma-language-manager.c
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-languages-manager.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2003-2006 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003-2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+
+#include "pluma-language-manager.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-utils.h"
+#include "pluma-debug.h"
+
+static GtkSourceLanguageManager *language_manager = NULL;
+
+GtkSourceLanguageManager *
+pluma_get_language_manager (void)
+{
+ if (language_manager == NULL)
+ {
+ language_manager = gtk_source_language_manager_new ();
+ }
+
+ return language_manager;
+}
+
+static gint
+language_compare (gconstpointer a, gconstpointer b)
+{
+ GtkSourceLanguage *lang_a = (GtkSourceLanguage *)a;
+ GtkSourceLanguage *lang_b = (GtkSourceLanguage *)b;
+ const gchar *name_a = gtk_source_language_get_name (lang_a);
+ const gchar *name_b = gtk_source_language_get_name (lang_b);
+
+ return g_utf8_collate (name_a, name_b);
+}
+
+GSList *
+pluma_language_manager_list_languages_sorted (GtkSourceLanguageManager *lm,
+ gboolean include_hidden)
+{
+ GSList *languages = NULL;
+ const gchar * const *ids;
+
+ ids = gtk_source_language_manager_get_language_ids (lm);
+ if (ids == NULL)
+ return NULL;
+
+ while (*ids != NULL)
+ {
+ GtkSourceLanguage *lang;
+
+ lang = gtk_source_language_manager_get_language (lm, *ids);
+ g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (lang), NULL);
+ ++ids;
+
+ if (include_hidden || !gtk_source_language_get_hidden (lang))
+ {
+ languages = g_slist_prepend (languages, lang);
+ }
+ }
+
+ return g_slist_sort (languages, (GCompareFunc)language_compare);
+}
+
diff --git a/pluma/pluma-language-manager.h b/pluma/pluma-language-manager.h
new file mode 100755
index 00000000..29eddee1
--- /dev/null
+++ b/pluma/pluma-language-manager.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-languages-manager.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2003-2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_LANGUAGES_MANAGER_H__
+#define __PLUMA_LANGUAGES_MANAGER_H__
+
+#include <gtksourceview/gtksourcelanguagemanager.h>
+
+G_BEGIN_DECLS
+
+GtkSourceLanguageManager *pluma_get_language_manager (void);
+
+GSList *pluma_language_manager_list_languages_sorted
+ (GtkSourceLanguageManager *lm,
+ gboolean include_hidden);
+
+G_END_DECLS
+
+#endif /* __PLUMA_LANGUAGES_MANAGER_H__ */
diff --git a/pluma/pluma-marshal.list b/pluma/pluma-marshal.list
new file mode 100755
index 00000000..d2882947
--- /dev/null
+++ b/pluma/pluma-marshal.list
@@ -0,0 +1,13 @@
+BOOLEAN:NONE
+BOOLEAN:OBJECT
+VOID:BOOLEAN
+VOID:BOOLEAN,POINTER
+VOID:BOXED,BOXED
+VOID:OBJECT
+VOID:POINTER
+VOID:STRING,BOXED,FLAGS
+VOID:STRING,BOXED,INT,BOOLEAN
+VOID:UINT,POINTER
+VOID:UINT64,UINT64
+VOID:VOID
+VOID:INT,INT
diff --git a/pluma/pluma-message-area.c b/pluma/pluma-message-area.c
new file mode 100755
index 00000000..3a191670
--- /dev/null
+++ b/pluma/pluma-message-area.c
@@ -0,0 +1,626 @@
+/*
+ * pluma-message-area.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+/* TODO: Style properties */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "pluma-message-area.h"
+
+#define PLUMA_MESSAGE_AREA_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_MESSAGE_AREA, \
+ PlumaMessageAreaPrivate))
+
+struct _PlumaMessageAreaPrivate
+{
+ GtkWidget *main_hbox;
+
+ GtkWidget *contents;
+ GtkWidget *action_area;
+
+ gboolean changing_style;
+};
+
+typedef struct _ResponseData ResponseData;
+
+struct _ResponseData
+{
+ gint response_id;
+};
+
+enum {
+ RESPONSE,
+ CLOSE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE(PlumaMessageArea, pluma_message_area, GTK_TYPE_HBOX)
+
+
+static void
+pluma_message_area_finalize (GObject *object)
+{
+ /*
+ PlumaMessageArea *message_area = PLUMA_MESSAGE_AREA (object);
+ */
+
+ G_OBJECT_CLASS (pluma_message_area_parent_class)->finalize (object);
+}
+
+static ResponseData *
+get_response_data (GtkWidget *widget,
+ gboolean create)
+{
+ ResponseData *ad = g_object_get_data (G_OBJECT (widget),
+ "pluma-message-area-response-data");
+
+ if (ad == NULL && create)
+ {
+ ad = g_new (ResponseData, 1);
+
+ g_object_set_data_full (G_OBJECT (widget),
+ "pluma-message-area-response-data",
+ ad,
+ g_free);
+ }
+
+ return ad;
+}
+
+static GtkWidget *
+find_button (PlumaMessageArea *message_area,
+ gint response_id)
+{
+ GList *children, *tmp_list;
+ GtkWidget *child = NULL;
+
+ children = gtk_container_get_children (
+ GTK_CONTAINER (message_area->priv->action_area));
+
+ for (tmp_list = children; tmp_list; tmp_list = tmp_list->next)
+ {
+ ResponseData *rd = get_response_data (tmp_list->data, FALSE);
+
+ if (rd && rd->response_id == response_id)
+ {
+ child = tmp_list->data;
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ return child;
+}
+
+static void
+pluma_message_area_close (PlumaMessageArea *message_area)
+{
+ if (!find_button (message_area, GTK_RESPONSE_CANCEL))
+ return;
+
+ /* emit response signal */
+ pluma_message_area_response (PLUMA_MESSAGE_AREA (message_area),
+ GTK_RESPONSE_CANCEL);
+}
+
+static gboolean
+paint_message_area (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer user_data)
+{
+ gtk_paint_flat_box (widget->style,
+ widget->window,
+ GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ NULL,
+ widget,
+ "tooltip",
+ widget->allocation.x + 1,
+ widget->allocation.y + 1,
+ widget->allocation.width - 2,
+ widget->allocation.height - 2);
+
+ return FALSE;
+}
+
+static void
+pluma_message_area_class_init (PlumaMessageAreaClass *klass)
+{
+ GObjectClass *object_class;
+ GtkBindingSet *binding_set;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = pluma_message_area_finalize;
+
+ klass->close = pluma_message_area_close;
+
+ g_type_class_add_private (object_class, sizeof(PlumaMessageAreaPrivate));
+
+ signals[RESPONSE] = g_signal_new ("response",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaMessageAreaClass, response),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ signals[CLOSE] = g_signal_new ("close",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaMessageAreaClass, close),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "close", 0);
+}
+
+static void
+style_set (GtkWidget *widget,
+ GtkStyle *prev_style,
+ PlumaMessageArea *message_area)
+{
+ GtkWidget *window;
+ GtkStyle *style;
+
+ if (message_area->priv->changing_style)
+ return;
+
+ /* This is a hack needed to use the tooltip background color */
+ window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_widget_set_name (window, "gtk-tooltip");
+ gtk_widget_ensure_style (window);
+ style = gtk_widget_get_style (window);
+
+ message_area->priv->changing_style = TRUE;
+ gtk_widget_set_style (GTK_WIDGET (message_area), style);
+ message_area->priv->changing_style = FALSE;
+
+ gtk_widget_destroy (window);
+
+ gtk_widget_queue_draw (GTK_WIDGET (message_area));
+}
+
+static void
+pluma_message_area_init (PlumaMessageArea *message_area)
+{
+ message_area->priv = PLUMA_MESSAGE_AREA_GET_PRIVATE (message_area);
+
+ message_area->priv->main_hbox = gtk_hbox_new (FALSE, 16); /* FIXME: use style properties */
+ gtk_widget_show (message_area->priv->main_hbox);
+ gtk_container_set_border_width (GTK_CONTAINER (message_area->priv->main_hbox),
+ 8); /* FIXME: use style properties */
+
+ message_area->priv->action_area = gtk_vbox_new (TRUE, 10); /* FIXME: use style properties */
+ gtk_widget_show (message_area->priv->action_area);
+ gtk_box_pack_end (GTK_BOX (message_area->priv->main_hbox),
+ message_area->priv->action_area,
+ FALSE,
+ TRUE,
+ 0);
+
+ gtk_box_pack_start (GTK_BOX (message_area),
+ message_area->priv->main_hbox,
+ TRUE,
+ TRUE,
+ 0);
+
+ gtk_widget_set_app_paintable (GTK_WIDGET (message_area), TRUE);
+
+ g_signal_connect (message_area,
+ "expose-event",
+ G_CALLBACK (paint_message_area),
+ NULL);
+
+ /* Note that we connect to style-set on one of the internal
+ * widgets, not on the message area itself, since gtk does
+ * not deliver any further style-set signals for a widget on
+ * which the style has been forced with gtk_widget_set_style() */
+ g_signal_connect (message_area->priv->main_hbox,
+ "style-set",
+ G_CALLBACK (style_set),
+ message_area);
+}
+
+static gint
+get_response_for_widget (PlumaMessageArea *message_area,
+ GtkWidget *widget)
+{
+ ResponseData *rd;
+
+ rd = get_response_data (widget, FALSE);
+ if (!rd)
+ return GTK_RESPONSE_NONE;
+ else
+ return rd->response_id;
+}
+
+static void
+action_widget_activated (GtkWidget *widget, PlumaMessageArea *message_area)
+{
+ gint response_id;
+
+ response_id = get_response_for_widget (message_area, widget);
+
+ pluma_message_area_response (message_area, response_id);
+}
+
+void
+pluma_message_area_add_action_widget (PlumaMessageArea *message_area,
+ GtkWidget *child,
+ gint response_id)
+{
+ ResponseData *ad;
+ guint signal_id;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE_AREA (message_area));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ ad = get_response_data (child, TRUE);
+
+ ad->response_id = response_id;
+
+ if (GTK_IS_BUTTON (child))
+ signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON);
+ else
+ signal_id = GTK_WIDGET_GET_CLASS (child)->activate_signal;
+
+ if (signal_id)
+ {
+ GClosure *closure;
+
+ closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated),
+ G_OBJECT (message_area));
+
+ g_signal_connect_closure_by_id (child,
+ signal_id,
+ 0,
+ closure,
+ FALSE);
+ }
+ else
+ g_warning ("Only 'activatable' widgets can be packed into the action area of a PlumaMessageArea");
+
+ if (response_id != GTK_RESPONSE_HELP)
+ gtk_box_pack_start (GTK_BOX (message_area->priv->action_area),
+ child,
+ FALSE,
+ FALSE,
+ 0);
+ else
+ gtk_box_pack_end (GTK_BOX (message_area->priv->action_area),
+ child,
+ FALSE,
+ FALSE,
+ 0);
+}
+
+/**
+ * pluma_message_area_set_contents:
+ * @message_area: a #PlumaMessageArea
+ * @contents: widget you want to add to the contents area
+ *
+ * Adds the @contents widget to the contents area of #PlumaMessageArea.
+ */
+void
+pluma_message_area_set_contents (PlumaMessageArea *message_area,
+ GtkWidget *contents)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_AREA (message_area));
+ g_return_if_fail (GTK_IS_WIDGET (contents));
+
+ message_area->priv->contents = contents;
+ gtk_box_pack_start (GTK_BOX (message_area->priv->main_hbox),
+ message_area->priv->contents,
+ TRUE,
+ TRUE,
+ 0);
+}
+
+/**
+ * pluma_message_area_add_button:
+ * @message_area: a #PlumaMessageArea
+ * @button_text: text of button, or stock ID
+ * @response_id: response ID for the button
+ *
+ * Adds a button with the given text (or a stock button, if button_text is a stock ID)
+ * and sets things up so that clicking the button will emit the "response" signal
+ * with the given response_id. The button is appended to the end of the message area's
+ * action area. The button widget is returned, but usually you don't need it.
+ *
+ * Returns: the button widget that was added
+ */
+GtkWidget*
+pluma_message_area_add_button (PlumaMessageArea *message_area,
+ const gchar *button_text,
+ gint response_id)
+{
+ GtkWidget *button;
+
+ g_return_val_if_fail (PLUMA_IS_MESSAGE_AREA (message_area), NULL);
+ g_return_val_if_fail (button_text != NULL, NULL);
+
+ button = gtk_button_new_from_stock (button_text);
+
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+
+ gtk_widget_show (button);
+
+ pluma_message_area_add_action_widget (message_area,
+ button,
+ response_id);
+
+ return button;
+}
+
+static void
+add_buttons_valist (PlumaMessageArea *message_area,
+ const gchar *first_button_text,
+ va_list args)
+{
+ const gchar* text;
+ gint response_id;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE_AREA (message_area));
+
+ if (first_button_text == NULL)
+ return;
+
+ text = first_button_text;
+ response_id = va_arg (args, gint);
+
+ while (text != NULL)
+ {
+ pluma_message_area_add_button (message_area,
+ text,
+ response_id);
+
+ text = va_arg (args, gchar*);
+ if (text == NULL)
+ break;
+
+ response_id = va_arg (args, int);
+ }
+}
+
+/**
+ * pluma_message_area_add_buttons:
+ * @message_area: a #PlumaMessageArea
+ * @first_button_text: button text or stock ID
+ * @...: response ID for first button, then more text-response_id pairs
+ *
+ * Adds more buttons, same as calling pluma_message_area_add_button() repeatedly.
+ * The variable argument list should be NULL-terminated as with
+ * pluma_message_area_new_with_buttons(). Each button must have both text and response ID.
+ */
+void
+pluma_message_area_add_buttons (PlumaMessageArea *message_area,
+ const gchar *first_button_text,
+ ...)
+{
+ va_list args;
+
+ va_start (args, first_button_text);
+
+ add_buttons_valist (message_area,
+ first_button_text,
+ args);
+
+ va_end (args);
+}
+
+/**
+ * pluma_message_area_new:
+ *
+ * Creates a new #PlumaMessageArea object.
+ *
+ * Returns: a new #PlumaMessageArea object
+ */
+GtkWidget *
+pluma_message_area_new (void)
+{
+ return g_object_new (PLUMA_TYPE_MESSAGE_AREA, NULL);
+}
+
+/**
+ * pluma_message_area_new_with_buttons:
+ * @first_button_text: stock ID or text to go in first button, or NULL
+ * @...: response ID for first button, then additional buttons, ending with NULL
+ *
+ * Creates a new #PlumaMessageArea with buttons. Button text/response ID pairs
+ * should be listed, with a NULL pointer ending the list. Button text can be either
+ * a stock ID such as GTK_STOCK_OK, or some arbitrary text. A response ID can be any
+ * positive number, or one of the values in the GtkResponseType enumeration. If
+ * the user clicks one of these dialog buttons, PlumaMessageArea will emit the "response"
+ * signal with the corresponding response ID.
+ *
+ * Returns: a new #PlumaMessageArea
+ */
+GtkWidget *
+pluma_message_area_new_with_buttons (const gchar *first_button_text,
+ ...)
+{
+ PlumaMessageArea *message_area;
+ va_list args;
+
+ message_area = PLUMA_MESSAGE_AREA (pluma_message_area_new ());
+
+ va_start (args, first_button_text);
+
+ add_buttons_valist (message_area,
+ first_button_text,
+ args);
+
+ va_end (args);
+
+ return GTK_WIDGET (message_area);
+}
+
+/**
+ * pluma_message_area_set_response_sensitive:
+ * @message_area: a #PlumaMessageArea
+ * @response_id: a response ID
+ * @setting: TRUE for sensitive
+ *
+ * Calls gtk_widget_set_sensitive (widget, setting) for each widget in the dialog's
+ * action area with the given response_id. A convenient way to sensitize/desensitize
+ * dialog buttons.
+ */
+void
+pluma_message_area_set_response_sensitive (PlumaMessageArea *message_area,
+ gint response_id,
+ gboolean setting)
+{
+ GList *children;
+ GList *tmp_list;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE_AREA (message_area));
+
+ children = gtk_container_get_children (GTK_CONTAINER (message_area->priv->action_area));
+
+ tmp_list = children;
+ while (tmp_list != NULL)
+ {
+ GtkWidget *widget = tmp_list->data;
+ ResponseData *rd = get_response_data (widget, FALSE);
+
+ if (rd && rd->response_id == response_id)
+ gtk_widget_set_sensitive (widget, setting);
+
+ tmp_list = g_list_next (tmp_list);
+ }
+
+ g_list_free (children);
+}
+
+/**
+ * pluma_message_area_set_default_response:
+ * @message_area: a #PlumaMessageArea
+ * @response_id: a response ID
+ *
+ * Sets the last widget in the message area's action area with the given response_id
+ * as the default widget for the dialog. Pressing "Enter" normally activates the
+ * default widget.
+ */
+void
+pluma_message_area_set_default_response (PlumaMessageArea *message_area,
+ gint response_id)
+{
+ GList *children;
+ GList *tmp_list;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE_AREA (message_area));
+
+ children = gtk_container_get_children (GTK_CONTAINER (message_area->priv->action_area));
+
+ tmp_list = children;
+ while (tmp_list != NULL)
+ {
+ GtkWidget *widget = tmp_list->data;
+ ResponseData *rd = get_response_data (widget, FALSE);
+
+ if (rd && rd->response_id == response_id)
+ gtk_widget_grab_default (widget);
+
+ tmp_list = g_list_next (tmp_list);
+ }
+
+ g_list_free (children);
+}
+
+/**
+ * pluma_message_area_set_default_response:
+ * @message_area: a #PlumaMessageArea
+ * @response_id: a response ID
+ *
+ * Emits the 'response' signal with the given @response_id.
+ */
+void
+pluma_message_area_response (PlumaMessageArea *message_area,
+ gint response_id)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_AREA (message_area));
+
+ g_signal_emit (message_area,
+ signals[RESPONSE],
+ 0,
+ response_id);
+}
+
+/**
+ * pluma_message_area_add_stock_button_with_text:
+ * @message_area: a #PlumaMessageArea
+ * @text: the text to visualize in the button
+ * @stock_id: the stock ID of the button
+ * @response_id: a response ID
+ *
+ * Same as pluma_message_area_add_button() but with a specific text.
+ */
+GtkWidget *
+pluma_message_area_add_stock_button_with_text (PlumaMessageArea *message_area,
+ const gchar *text,
+ const gchar *stock_id,
+ gint response_id)
+{
+ GtkWidget *button;
+
+ g_return_val_if_fail (PLUMA_IS_MESSAGE_AREA (message_area), NULL);
+ g_return_val_if_fail (text != NULL, NULL);
+ g_return_val_if_fail (stock_id != NULL, NULL);
+
+ button = gtk_button_new_with_mnemonic (text);
+ gtk_button_set_image (GTK_BUTTON (button),
+ gtk_image_new_from_stock (stock_id,
+ GTK_ICON_SIZE_BUTTON));
+
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+
+ gtk_widget_show (button);
+
+ pluma_message_area_add_action_widget (message_area,
+ button,
+ response_id);
+
+ return button;
+}
+
diff --git a/pluma/pluma-message-area.h b/pluma/pluma-message-area.h
new file mode 100755
index 00000000..e0111609
--- /dev/null
+++ b/pluma/pluma-message-area.h
@@ -0,0 +1,129 @@
+/*
+ * pluma-message-area.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_MESSAGE_AREA_H__
+#define __PLUMA_MESSAGE_AREA_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_MESSAGE_AREA (pluma_message_area_get_type())
+#define PLUMA_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_MESSAGE_AREA, PlumaMessageArea))
+#define PLUMA_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_MESSAGE_AREA, PlumaMessageAreaClass))
+#define PLUMA_IS_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_MESSAGE_AREA))
+#define PLUMA_IS_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_MESSAGE_AREA))
+#define PLUMA_MESSAGE_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_MESSAGE_AREA, PlumaMessageAreaClass))
+
+/* Private structure type */
+typedef struct _PlumaMessageAreaPrivate PlumaMessageAreaPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaMessageArea PlumaMessageArea;
+
+struct _PlumaMessageArea
+{
+ GtkHBox parent;
+
+ /*< private > */
+ PlumaMessageAreaPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaMessageAreaClass PlumaMessageAreaClass;
+
+struct _PlumaMessageAreaClass
+{
+ GtkHBoxClass parent_class;
+
+ /* Signals */
+ void (* response) (PlumaMessageArea *message_area, gint response_id);
+
+ /* Keybinding signals */
+ void (* close) (PlumaMessageArea *message_area);
+
+ /* Padding for future expansion */
+ void (*_pluma_reserved1) (void);
+ void (*_pluma_reserved2) (void);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_message_area_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_message_area_new (void);
+
+GtkWidget *pluma_message_area_new_with_buttons (const gchar *first_button_text,
+ ...);
+
+void pluma_message_area_set_contents (PlumaMessageArea *message_area,
+ GtkWidget *contents);
+
+void pluma_message_area_add_action_widget (PlumaMessageArea *message_area,
+ GtkWidget *child,
+ gint response_id);
+
+GtkWidget *pluma_message_area_add_button (PlumaMessageArea *message_area,
+ const gchar *button_text,
+ gint response_id);
+
+GtkWidget *pluma_message_area_add_stock_button_with_text
+ (PlumaMessageArea *message_area,
+ const gchar *text,
+ const gchar *stock_id,
+ gint response_id);
+
+void pluma_message_area_add_buttons (PlumaMessageArea *message_area,
+ const gchar *first_button_text,
+ ...);
+
+void pluma_message_area_set_response_sensitive
+ (PlumaMessageArea *message_area,
+ gint response_id,
+ gboolean setting);
+void pluma_message_area_set_default_response
+ (PlumaMessageArea *message_area,
+ gint response_id);
+
+/* Emit response signal */
+void pluma_message_area_response (PlumaMessageArea *message_area,
+ gint response_id);
+
+G_END_DECLS
+
+#endif /* __PLUMA_MESSAGE_AREA_H__ */
diff --git a/pluma/pluma-message-bus.c b/pluma/pluma-message-bus.c
new file mode 100755
index 00000000..043c4dae
--- /dev/null
+++ b/pluma/pluma-message-bus.c
@@ -0,0 +1,1158 @@
+#include "pluma-message-bus.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <gobject/gvaluecollector.h>
+
+/**
+ * PlumaMessageCallback:
+ * @bus: the #PlumaMessageBus on which the message was sent
+ * @message: the #PlumaMessage which was sent
+ * @userdata: the supplied user data when connecting the callback
+ *
+ * Callback signature used for connecting callback functions to be called
+ * when a message is received (see pluma_message_bus_connect()).
+ *
+ */
+
+/**
+ * SECTION:pluma-message-bus
+ * @short_description: internal message communication bus
+ * @include: pluma/pluma-message-bus.h
+ *
+ * pluma has a communication bus very similar to DBus. Its primary use is to
+ * allow easy communication between plugins, but it can also be used to expose
+ * pluma functionality to external applications by providing DBus bindings for
+ * the internal pluma message bus.
+ *
+ * There are two different communication busses available. The default bus
+ * (see pluma_message_bus_get_default()) is an application wide communication
+ * bus. In addition, each #PlumaWindow has a separate, private bus
+ * (see pluma_window_get_message_bus()). This makes it easier for plugins to
+ * communicate to other plugins in the same window.
+ *
+ * The concept of the message bus is very simple. You can register a message
+ * type on the bus, specified as a Method at a specific Object Path with a
+ * certain set of Method Arguments. You can then connect callback functions
+ * for this message type on the bus. Whenever a message with the Object Path
+ * and Method for which callbacks are connected is sent over the bus, the
+ * callbacks are called. There is no distinction between Methods and Signals
+ * (signals are simply messages where sender and receiver have switched places).
+ *
+ * <example>
+ * <title>Registering a message type</title>
+ * <programlisting>
+ * PlumaMessageBus *bus = pluma_message_bus_get_default ();
+ *
+ * // Register 'method' at '/plugins/example' with one required
+ * // string argument 'arg1'
+ * PlumaMessageType *message_type = pluma_message_bus_register ("/plugins/example", "method",
+ * 0,
+ * "arg1", G_TYPE_STRING,
+ * NULL);
+ * </programlisting>
+ * </example>
+ * <example>
+ * <title>Connecting a callback</title>
+ * <programlisting>
+ * static void
+ * example_method_cb (PlumaMessageBus *bus,
+ * PlumaMessage *message,
+ * gpointer userdata)
+ * {
+ * gchar *arg1 = NULL;
+ *
+ * pluma_message_get (message, "arg1", &arg1, NULL);
+ * g_message ("Evoked /plugins/example.method with: %s", arg1);
+ * g_free (arg1);
+ * }
+ *
+ * PlumaMessageBus *bus = pluma_message_bus_get_default ();
+ *
+ * guint id = pluma_message_bus_connect (bus,
+ * "/plugins/example", "method",
+ * example_method_cb,
+ * NULL,
+ * NULL);
+ *
+ * </programlisting>
+ * </example>
+ * <example>
+ * <title>Sending a message</title>
+ * <programlisting>
+ * PlumaMessageBus *bus = pluma_message_bus_get_default ();
+ *
+ * pluma_message_bus_send (bus,
+ * "/plugins/example", "method",
+ * "arg1", "Hello World",
+ * NULL);
+ * </programlisting>
+ * </example>
+ *
+ * Since: 2.25.3
+ *
+ */
+
+#define PLUMA_MESSAGE_BUS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), PLUMA_TYPE_MESSAGE_BUS, PlumaMessageBusPrivate))
+
+typedef struct
+{
+ gchar *object_path;
+ gchar *method;
+
+ GList *listeners;
+} Message;
+
+typedef struct
+{
+ guint id;
+ gboolean blocked;
+
+ GDestroyNotify destroy_data;
+ PlumaMessageCallback callback;
+ gpointer userdata;
+} Listener;
+
+typedef struct
+{
+ Message *message;
+ GList *listener;
+} IdMap;
+
+struct _PlumaMessageBusPrivate
+{
+ GHashTable *messages;
+ GHashTable *idmap;
+
+ GList *message_queue;
+ guint idle_id;
+
+ guint next_id;
+
+ GHashTable *types; /* mapping from identifier to PlumaMessageType */
+};
+
+/* signals */
+enum
+{
+ DISPATCH,
+ REGISTERED,
+ UNREGISTERED,
+ LAST_SIGNAL
+};
+
+static guint message_bus_signals[LAST_SIGNAL];
+
+static void pluma_message_bus_dispatch_real (PlumaMessageBus *bus,
+ PlumaMessage *message);
+
+G_DEFINE_TYPE(PlumaMessageBus, pluma_message_bus, G_TYPE_OBJECT)
+
+static void
+listener_free (Listener *listener)
+{
+ if (listener->destroy_data)
+ listener->destroy_data (listener->userdata);
+
+ g_free (listener);
+}
+
+static void
+message_free (Message *message)
+{
+ g_free (message->method);
+ g_free (message->object_path);
+
+ g_list_foreach (message->listeners, (GFunc)listener_free, NULL);
+ g_list_free (message->listeners);
+
+ g_free (message);
+}
+
+static void
+message_queue_free (GList *queue)
+{
+ g_list_foreach (queue, (GFunc)g_object_unref, NULL);
+ g_list_free (queue);
+}
+
+static void
+pluma_message_bus_finalize (GObject *object)
+{
+ PlumaMessageBus *bus = PLUMA_MESSAGE_BUS (object);
+
+ if (bus->priv->idle_id != 0)
+ g_source_remove (bus->priv->idle_id);
+
+ message_queue_free (bus->priv->message_queue);
+
+ g_hash_table_destroy (bus->priv->messages);
+ g_hash_table_destroy (bus->priv->idmap);
+ g_hash_table_destroy (bus->priv->types);
+
+ G_OBJECT_CLASS (pluma_message_bus_parent_class)->finalize (object);
+}
+
+static void
+pluma_message_bus_class_init (PlumaMessageBusClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_message_bus_finalize;
+
+ klass->dispatch = pluma_message_bus_dispatch_real;
+
+ /**
+ * PlumaMessageBus::dispatch:
+ * @bus: a #PlumaMessageBus
+ * @message: the #PlumaMessage to dispatch
+ *
+ * The "dispatch" signal is emitted when a message is to be dispatched.
+ * The message is dispatched in the default handler of this signal.
+ * Primary use of this signal is to customize the dispatch of a message
+ * (for instance to automatically dispatch all messages over DBus).
+ *2
+ */
+ message_bus_signals[DISPATCH] =
+ g_signal_new ("dispatch",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaMessageBusClass, dispatch),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_MESSAGE);
+
+ /**
+ * PlumaMessageBus::registered:
+ * @bus: a #PlumaMessageBus
+ * @message_type: the registered #PlumaMessageType
+ *
+ * The "registered" signal is emitted when a message has been registered
+ * on the bus.
+ *
+ */
+ message_bus_signals[REGISTERED] =
+ g_signal_new ("registered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaMessageBusClass, registered),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_MESSAGE_TYPE);
+
+ /**
+ * PlumaMessageBus::unregistered:
+ * @bus: a #PlumaMessageBus
+ * @message_type: the unregistered #PlumaMessageType
+ *
+ * The "unregistered" signal is emitted when a message has been
+ * unregistered from the bus.
+ *
+ */
+ message_bus_signals[UNREGISTERED] =
+ g_signal_new ("unregistered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaMessageBusClass, unregistered),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_MESSAGE_TYPE);
+
+ g_type_class_add_private (object_class, sizeof(PlumaMessageBusPrivate));
+}
+
+static Message *
+message_new (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method)
+{
+ Message *message = g_new (Message, 1);
+
+ message->object_path = g_strdup (object_path);
+ message->method = g_strdup (method);
+ message->listeners = NULL;
+
+ g_hash_table_insert (bus->priv->messages,
+ pluma_message_type_identifier (object_path, method),
+ message);
+ return message;
+}
+
+static Message *
+lookup_message (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ gboolean create)
+{
+ gchar *identifier;
+ Message *message;
+
+ identifier = pluma_message_type_identifier (object_path, method);
+ message = (Message *)g_hash_table_lookup (bus->priv->messages, identifier);
+ g_free (identifier);
+
+ if (!message && !create)
+ return NULL;
+
+ if (!message)
+ message = message_new (bus, object_path, method);
+
+ return message;
+}
+
+static guint
+add_listener (PlumaMessageBus *bus,
+ Message *message,
+ PlumaMessageCallback callback,
+ gpointer userdata,
+ GDestroyNotify destroy_data)
+{
+ Listener *listener;
+ IdMap *idmap;
+
+ listener = g_new (Listener, 1);
+ listener->id = ++bus->priv->next_id;
+ listener->callback = callback;
+ listener->userdata = userdata;
+ listener->blocked = FALSE;
+ listener->destroy_data = destroy_data;
+
+ message->listeners = g_list_append (message->listeners, listener);
+
+ idmap = g_new (IdMap, 1);
+ idmap->message = message;
+ idmap->listener = g_list_last (message->listeners);
+
+ g_hash_table_insert (bus->priv->idmap, GINT_TO_POINTER (listener->id), idmap);
+ return listener->id;
+}
+
+static void
+remove_listener (PlumaMessageBus *bus,
+ Message *message,
+ GList *listener)
+{
+ Listener *lst;
+
+ lst = (Listener *)listener->data;
+
+ /* remove from idmap */
+ g_hash_table_remove (bus->priv->idmap, GINT_TO_POINTER (lst->id));
+ listener_free (lst);
+
+ /* remove from list of listeners */
+ message->listeners = g_list_delete_link (message->listeners, listener);
+
+ if (!message->listeners)
+ {
+ /* remove message because it does not have any listeners */
+ g_hash_table_remove (bus->priv->messages, message);
+ }
+}
+
+static void
+block_listener (PlumaMessageBus *bus,
+ Message *message,
+ GList *listener)
+{
+ Listener *lst;
+
+ lst = (Listener *)listener->data;
+ lst->blocked = TRUE;
+}
+
+static void
+unblock_listener (PlumaMessageBus *bus,
+ Message *message,
+ GList *listener)
+{
+ Listener *lst;
+
+ lst = (Listener *)listener->data;
+ lst->blocked = FALSE;
+}
+
+static void
+dispatch_message_real (PlumaMessageBus *bus,
+ Message *msg,
+ PlumaMessage *message)
+{
+ GList *item;
+
+ for (item = msg->listeners; item; item = item->next)
+ {
+ Listener *listener = (Listener *)item->data;
+
+ if (!listener->blocked)
+ listener->callback (bus, message, listener->userdata);
+ }
+}
+
+static void
+pluma_message_bus_dispatch_real (PlumaMessageBus *bus,
+ PlumaMessage *message)
+{
+ const gchar *object_path;
+ const gchar *method;
+ Message *msg;
+
+ object_path = pluma_message_get_object_path (message);
+ method = pluma_message_get_method (message);
+
+ msg = lookup_message (bus, object_path, method, FALSE);
+
+ if (msg)
+ dispatch_message_real (bus, msg, message);
+}
+
+static void
+dispatch_message (PlumaMessageBus *bus,
+ PlumaMessage *message)
+{
+ g_signal_emit (bus, message_bus_signals[DISPATCH], 0, message);
+}
+
+static gboolean
+idle_dispatch (PlumaMessageBus *bus)
+{
+ GList *list;
+ GList *item;
+
+ /* make sure to set idle_id to 0 first so that any new async messages
+ will be queued properly */
+ bus->priv->idle_id = 0;
+
+ /* reverse queue to get correct delivery order */
+ list = g_list_reverse (bus->priv->message_queue);
+ bus->priv->message_queue = NULL;
+
+ for (item = list; item; item = item->next)
+ {
+ PlumaMessage *msg = PLUMA_MESSAGE (item->data);
+
+ dispatch_message (bus, msg);
+ }
+
+ message_queue_free (list);
+ return FALSE;
+}
+
+typedef void (*MatchCallback) (PlumaMessageBus *, Message *, GList *);
+
+static void
+process_by_id (PlumaMessageBus *bus,
+ guint id,
+ MatchCallback processor)
+{
+ IdMap *idmap;
+
+ idmap = (IdMap *)g_hash_table_lookup (bus->priv->idmap, GINT_TO_POINTER (id));
+
+ if (idmap == NULL)
+ {
+ g_warning ("No handler registered with id `%d'", id);
+ return;
+ }
+
+ processor (bus, idmap->message, idmap->listener);
+}
+
+static void
+process_by_match (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata,
+ MatchCallback processor)
+{
+ Message *message;
+ GList *item;
+
+ message = lookup_message (bus, object_path, method, FALSE);
+
+ if (!message)
+ {
+ g_warning ("No such handler registered for %s.%s", object_path, method);
+ return;
+ }
+
+ for (item = message->listeners; item; item = item->next)
+ {
+ Listener *listener = (Listener *)item->data;
+
+ if (listener->callback == callback &&
+ listener->userdata == userdata)
+ {
+ processor (bus, message, item);
+ return;
+ }
+ }
+
+ g_warning ("No such handler registered for %s.%s", object_path, method);
+}
+
+static void
+pluma_message_bus_init (PlumaMessageBus *self)
+{
+ self->priv = PLUMA_MESSAGE_BUS_GET_PRIVATE (self);
+
+ self->priv->messages = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)message_free);
+
+ self->priv->idmap = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)g_free);
+
+ self->priv->types = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)pluma_message_type_unref);
+}
+
+/**
+ * pluma_message_bus_get_default:
+ *
+ * Get the default application #PlumaMessageBus.
+ *
+ * Return value: the default #PlumaMessageBus
+ *
+ */
+PlumaMessageBus *
+pluma_message_bus_get_default (void)
+{
+ static PlumaMessageBus *default_bus = NULL;
+
+ if (G_UNLIKELY (default_bus == NULL))
+ {
+ default_bus = g_object_new (PLUMA_TYPE_MESSAGE_BUS, NULL);
+ g_object_add_weak_pointer (G_OBJECT (default_bus),
+ (gpointer) &default_bus);
+ }
+
+ return default_bus;
+}
+
+/**
+ * pluma_message_bus_new:
+ *
+ * Create a new message bus. Use pluma_message_bus_get_default() to get the
+ * default, application wide, message bus. Creating a new bus is useful for
+ * associating a specific bus with for instance a #PlumaWindow.
+ *
+ * Return value: a new #PlumaMessageBus
+ *
+ */
+PlumaMessageBus *
+pluma_message_bus_new (void)
+{
+ return PLUMA_MESSAGE_BUS (g_object_new (PLUMA_TYPE_MESSAGE_BUS, NULL));
+}
+
+/**
+ * pluma_message_bus_lookup:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ *
+ * Get the registered #PlumaMessageType for @method at @object_path. The
+ * returned #PlumaMessageType is owned by the bus and should not be unreffed.
+ *
+ * Return value: the registered #PlumaMessageType or %NULL if no message type
+ * is registered for @method at @object_path
+ *
+ */
+PlumaMessageType *
+pluma_message_bus_lookup (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method)
+{
+ gchar *identifier;
+ PlumaMessageType *message_type;
+
+ g_return_val_if_fail (PLUMA_IS_MESSAGE_BUS (bus), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+ g_return_val_if_fail (method != NULL, NULL);
+
+ identifier = pluma_message_type_identifier (object_path, method);
+ message_type = PLUMA_MESSAGE_TYPE (g_hash_table_lookup (bus->priv->types, identifier));
+
+ g_free (identifier);
+ return message_type;
+}
+
+/**
+ * pluma_message_bus_register:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method to register
+ * @num_optional: the number of optional arguments
+ * @...: NULL terminated list of key/gtype method argument pairs
+ *
+ * Register a message on the bus. A message must be registered on the bus before
+ * it can be send. This function registers the type arguments for @method at
+ * @object_path. The arguments are specified with the variable arguments which
+ * should contain pairs of const gchar *key and GType terminated by %NULL. The
+ * last @num_optional arguments are registered as optional (and are thus not
+ * required when sending a message).
+ *
+ * This function emits a #PlumaMessageBus::registered signal.
+ *
+ * Return value: the registered #PlumaMessageType. The returned reference is
+ * owned by the bus. If you want to keep it alive after
+ * unregistering, use pluma_message_type_ref().
+ *
+ */
+PlumaMessageType *
+pluma_message_bus_register (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ guint num_optional,
+ ...)
+{
+ gchar *identifier;
+ gpointer data;
+ va_list var_args;
+ PlumaMessageType *message_type;
+
+ g_return_val_if_fail (PLUMA_IS_MESSAGE_BUS (bus), NULL);
+ g_return_val_if_fail (pluma_message_type_is_valid_object_path (object_path), NULL);
+
+ if (pluma_message_bus_is_registered (bus, object_path, method))
+ {
+ g_warning ("Message type for '%s.%s' is already registered", object_path, method);
+ return NULL;
+ }
+
+ identifier = pluma_message_type_identifier (object_path, method);
+ data = g_hash_table_lookup (bus->priv->types, identifier);
+
+ va_start (var_args, num_optional);
+ message_type = pluma_message_type_new_valist (object_path,
+ method,
+ num_optional,
+ var_args);
+ va_end (var_args);
+
+ if (message_type)
+ {
+ g_hash_table_insert (bus->priv->types, identifier, message_type);
+ g_signal_emit (bus, message_bus_signals[REGISTERED], 0, message_type);
+ }
+ else
+ {
+ g_free (identifier);
+ }
+
+ return message_type;
+}
+
+static void
+pluma_message_bus_unregister_real (PlumaMessageBus *bus,
+ PlumaMessageType *message_type,
+ gboolean remove_from_store)
+{
+ gchar *identifier;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+
+ identifier = pluma_message_type_identifier (pluma_message_type_get_object_path (message_type),
+ pluma_message_type_get_method (message_type));
+
+ /* Keep message type alive for signal emission */
+ pluma_message_type_ref (message_type);
+
+ if (!remove_from_store || g_hash_table_remove (bus->priv->types, identifier))
+ g_signal_emit (bus, message_bus_signals[UNREGISTERED], 0, message_type);
+
+ pluma_message_type_unref (message_type);
+ g_free (identifier);
+}
+
+/**
+ * pluma_message_bus_unregister:
+ * @bus: a #PlumaMessageBus
+ * @message_type: the #PlumaMessageType to unregister
+ *
+ * Unregisters a previously registered message type. This is especially useful
+ * for plugins which should unregister message types when they are deactivated.
+ *
+ * This function emits the #PlumaMessageBus::unregistered signal.
+ *
+ */
+void
+pluma_message_bus_unregister (PlumaMessageBus *bus,
+ PlumaMessageType *message_type)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+ pluma_message_bus_unregister_real (bus, message_type, TRUE);
+}
+
+typedef struct
+{
+ PlumaMessageBus *bus;
+ const gchar *object_path;
+} UnregisterInfo;
+
+static gboolean
+unregister_each (const gchar *identifier,
+ PlumaMessageType *message_type,
+ UnregisterInfo *info)
+{
+ if (strcmp (pluma_message_type_get_object_path (message_type),
+ info->object_path) == 0)
+ {
+ pluma_message_bus_unregister_real (info->bus, message_type, FALSE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * pluma_message_bus_unregister_all:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ *
+ * Unregisters all message types for @object_path. This is especially useful for
+ * plugins which should unregister message types when they are deactivated.
+ *
+ * This function emits the #PlumaMessageBus::unregistered signal for all
+ * unregistered message types.
+ *
+ */
+void
+pluma_message_bus_unregister_all (PlumaMessageBus *bus,
+ const gchar *object_path)
+{
+ UnregisterInfo info = {bus, object_path};
+
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (object_path != NULL);
+
+ g_hash_table_foreach_remove (bus->priv->types,
+ (GHRFunc)unregister_each,
+ &info);
+}
+
+/**
+ * pluma_message_bus_is_registered:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ *
+ * Check whether a message type @method at @object_path is registered on the
+ * bus.
+ *
+ * Return value: %TRUE if the @method at @object_path is a registered message
+ * type on the bus
+ *
+ */
+gboolean
+pluma_message_bus_is_registered (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method)
+{
+ gchar *identifier;
+ gboolean ret;
+
+ g_return_val_if_fail (PLUMA_IS_MESSAGE_BUS (bus), FALSE);
+ g_return_val_if_fail (object_path != NULL, FALSE);
+ g_return_val_if_fail (method != NULL, FALSE);
+
+ identifier = pluma_message_type_identifier (object_path, method);
+ ret = g_hash_table_lookup (bus->priv->types, identifier) != NULL;
+
+ g_free(identifier);
+ return ret;
+}
+
+typedef struct
+{
+ PlumaMessageBusForeach func;
+ gpointer userdata;
+} ForeachInfo;
+
+static void
+foreach_type (const gchar *key,
+ PlumaMessageType *message_type,
+ ForeachInfo *info)
+{
+ pluma_message_type_ref (message_type);
+ info->func (message_type, info->userdata);
+ pluma_message_type_unref (message_type);
+}
+
+/**
+ * pluma_message_bus_foreach:
+ * @bus: the #PlumaMessagebus
+ * @func: the callback function
+ * @userdata: the user data to supply to the callback function
+ *
+ * Calls @func for each message type registered on the bus
+ *
+ */
+void
+pluma_message_bus_foreach (PlumaMessageBus *bus,
+ PlumaMessageBusForeach func,
+ gpointer userdata)
+{
+ ForeachInfo info = {func, userdata};
+
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (func != NULL);
+
+ g_hash_table_foreach (bus->priv->types, (GHFunc)foreach_type, &info);
+}
+
+/**
+ * pluma_message_bus_connect:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: function to be called when message @method at @object_path is sent
+ * @userdata: userdata to use for the callback
+ * @destroy_data: function to evoke with @userdata as argument when @userdata
+ * needs to be freed
+ *
+ * Connect a callback handler to be evoked when message @method at @object_path
+ * is sent over the bus.
+ *
+ * Return value: the callback identifier
+ *
+ */
+guint
+pluma_message_bus_connect (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata,
+ GDestroyNotify destroy_data)
+{
+ Message *message;
+
+ g_return_val_if_fail (PLUMA_IS_MESSAGE_BUS (bus), 0);
+ g_return_val_if_fail (object_path != NULL, 0);
+ g_return_val_if_fail (method != NULL, 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ /* lookup the message and create if it does not exist yet */
+ message = lookup_message (bus, object_path, method, TRUE);
+
+ return add_listener (bus, message, callback, userdata, destroy_data);
+}
+
+/**
+ * pluma_message_bus_disconnect:
+ * @bus: a #PlumaMessageBus
+ * @id: the callback id as returned by pluma_message_bus_connect()
+ *
+ * Disconnects a previously connected message callback.
+ *
+ */
+void
+pluma_message_bus_disconnect (PlumaMessageBus *bus,
+ guint id)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+
+ process_by_id (bus, id, remove_listener);
+}
+
+/**
+ * pluma_message_bus_disconnect_by_func:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: the connected callback
+ * @userdata: the userdata with which the callback was connected
+ *
+ * Disconnects a previously connected message callback by matching the
+ * provided callback function and userdata. See also
+ * pluma_message_bus_disconnect().
+ *
+ */
+void
+pluma_message_bus_disconnect_by_func (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+
+ process_by_match (bus, object_path, method, callback, userdata, remove_listener);
+}
+
+/**
+ * pluma_message_bus_block:
+ * @bus: a #PlumaMessageBus
+ * @id: the callback id
+ *
+ * Blocks evoking the callback specified by @id. Unblock the callback by
+ * using pluma_message_bus_unblock().
+ *
+ */
+void
+pluma_message_bus_block (PlumaMessageBus *bus,
+ guint id)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+
+ process_by_id (bus, id, block_listener);
+}
+
+/**
+ * pluma_message_bus_block_by_func:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: the callback to block
+ * @userdata: the userdata with which the callback was connected
+ *
+ * Blocks evoking the callback that matches provided @callback and @userdata.
+ * Unblock the callback using pluma_message_unblock_by_func().
+ *
+ */
+void
+pluma_message_bus_block_by_func (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+
+ process_by_match (bus, object_path, method, callback, userdata, block_listener);
+}
+
+/**
+ * pluma_message_bus_unblock:
+ * @bus: a #PlumaMessageBus
+ * @id: the callback id
+ *
+ * Unblocks the callback specified by @id.
+ *
+ */
+void
+pluma_message_bus_unblock (PlumaMessageBus *bus,
+ guint id)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+
+ process_by_id (bus, id, unblock_listener);
+}
+
+/**
+ * pluma_message_bus_unblock_by_func:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @callback: the callback to block
+ * @userdata: the userdata with which the callback was connected
+ *
+ * Unblocks the callback that matches provided @callback and @userdata.
+ *
+ */
+void
+pluma_message_bus_unblock_by_func (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+
+ process_by_match (bus, object_path, method, callback, userdata, unblock_listener);
+}
+
+static gboolean
+validate_message (PlumaMessage *message)
+{
+ if (!pluma_message_validate (message))
+ {
+ g_warning ("Message '%s.%s' is invalid", pluma_message_get_object_path (message),
+ pluma_message_get_method (message));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+send_message_real (PlumaMessageBus *bus,
+ PlumaMessage *message)
+{
+ if (!validate_message (message))
+ {
+ return;
+ }
+
+ bus->priv->message_queue = g_list_prepend (bus->priv->message_queue,
+ g_object_ref (message));
+
+ if (bus->priv->idle_id == 0)
+ bus->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH,
+ (GSourceFunc)idle_dispatch,
+ bus,
+ NULL);
+}
+
+/**
+ * pluma_message_bus_send_message:
+ * @bus: a #PlumaMessageBus
+ * @message: the message to send
+ *
+ * This sends the provided @message asynchronously over the bus. To send
+ * a message synchronously, use pluma_message_bus_send_message_sync(). The
+ * convenience function pluma_message_bus_send() can be used to easily send
+ * a message without constructing the message object explicitly first.
+ *
+ */
+void
+pluma_message_bus_send_message (PlumaMessageBus *bus,
+ PlumaMessage *message)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ send_message_real (bus, message);
+}
+
+static void
+send_message_sync_real (PlumaMessageBus *bus,
+ PlumaMessage *message)
+{
+ if (!validate_message (message))
+ {
+ return;
+ }
+
+ dispatch_message (bus, message);
+}
+
+/**
+ * pluma_message_bus_send_message_sync:
+ * @bus: a #PlumaMessageBus
+ * @message: the message to send
+ *
+ * This sends the provided @message synchronously over the bus. To send
+ * a message asynchronously, use pluma_message_bus_send_message(). The
+ * convenience function pluma_message_bus_send_sync() can be used to easily send
+ * a message without constructing the message object explicitly first.
+ *
+ */
+void
+pluma_message_bus_send_message_sync (PlumaMessageBus *bus,
+ PlumaMessage *message)
+{
+ g_return_if_fail (PLUMA_IS_MESSAGE_BUS (bus));
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ send_message_sync_real (bus, message);
+}
+
+static PlumaMessage *
+create_message (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ va_list var_args)
+{
+ PlumaMessageType *message_type;
+
+ message_type = pluma_message_bus_lookup (bus, object_path, method);
+
+ if (!message_type)
+ {
+ g_warning ("Could not find message type for '%s.%s'", object_path, method);
+ return NULL;
+ }
+
+ return pluma_message_type_instantiate_valist (message_type,
+ var_args);
+}
+
+/**
+ * pluma_message_bus_send:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @...: NULL terminated list of key/value pairs
+ *
+ * This provides a convenient way to quickly send a message @method at
+ * @object_path asynchronously over the bus. The variable argument list
+ * specifies key (string) value pairs used to construct the message arguments.
+ * To send a message synchronously use pluma_message_bus_send_sync().
+ *
+ */
+void
+pluma_message_bus_send (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ ...)
+{
+ va_list var_args;
+ PlumaMessage *message;
+
+ va_start (var_args, method);
+
+ message = create_message (bus, object_path, method, var_args);
+
+ if (message)
+ {
+ send_message_real (bus, message);
+ g_object_unref (message);
+ }
+ else
+ {
+ g_warning ("Could not instantiate message");
+ }
+
+ va_end (var_args);
+}
+
+/**
+ * pluma_message_bus_send_sync:
+ * @bus: a #PlumaMessageBus
+ * @object_path: the object path
+ * @method: the method
+ * @...: NULL terminated list of key/value pairs
+ *
+ * This provides a convenient way to quickly send a message @method at
+ * @object_path synchronously over the bus. The variable argument list
+ * specifies key (string) value pairs used to construct the message
+ * arguments. To send a message asynchronously use pluma_message_bus_send().
+ *
+ * Return value: the constructed #PlumaMessage. The caller owns a reference
+ * to the #PlumaMessage and should call g_object_unref() when
+ * it is no longer needed
+ */
+PlumaMessage *
+pluma_message_bus_send_sync (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ ...)
+{
+ va_list var_args;
+ PlumaMessage *message;
+
+ va_start (var_args, method);
+ message = create_message (bus, object_path, method, var_args);
+
+ if (message)
+ send_message_sync_real (bus, message);
+
+ va_end (var_args);
+
+ return message;
+}
+
+// ex:ts=8:noet:
diff --git a/pluma/pluma-message-bus.h b/pluma/pluma-message-bus.h
new file mode 100755
index 00000000..67b30688
--- /dev/null
+++ b/pluma/pluma-message-bus.h
@@ -0,0 +1,129 @@
+#ifndef __PLUMA_MESSAGE_BUS_H__
+#define __PLUMA_MESSAGE_BUS_H__
+
+#include <glib-object.h>
+#include <pluma/pluma-message.h>
+#include <pluma/pluma-message-type.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_MESSAGE_BUS (pluma_message_bus_get_type ())
+#define PLUMA_MESSAGE_BUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_MESSAGE_BUS, PlumaMessageBus))
+#define PLUMA_MESSAGE_BUS_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_MESSAGE_BUS, PlumaMessageBus const))
+#define PLUMA_MESSAGE_BUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_MESSAGE_BUS, PlumaMessageBusClass))
+#define PLUMA_IS_MESSAGE_BUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_MESSAGE_BUS))
+#define PLUMA_IS_MESSAGE_BUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_MESSAGE_BUS))
+#define PLUMA_MESSAGE_BUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_MESSAGE_BUS, PlumaMessageBusClass))
+
+typedef struct _PlumaMessageBus PlumaMessageBus;
+typedef struct _PlumaMessageBusClass PlumaMessageBusClass;
+typedef struct _PlumaMessageBusPrivate PlumaMessageBusPrivate;
+
+struct _PlumaMessageBus {
+ GObject parent;
+
+ PlumaMessageBusPrivate *priv;
+};
+
+struct _PlumaMessageBusClass {
+ GObjectClass parent_class;
+
+ void (*dispatch) (PlumaMessageBus *bus,
+ PlumaMessage *message);
+ void (*registered) (PlumaMessageBus *bus,
+ PlumaMessageType *message_type);
+ void (*unregistered) (PlumaMessageBus *bus,
+ PlumaMessageType *message_type);
+};
+
+typedef void (* PlumaMessageCallback) (PlumaMessageBus *bus,
+ PlumaMessage *message,
+ gpointer userdata);
+
+typedef void (* PlumaMessageBusForeach) (PlumaMessageType *message_type,
+ gpointer userdata);
+
+GType pluma_message_bus_get_type (void) G_GNUC_CONST;
+
+PlumaMessageBus *pluma_message_bus_get_default (void);
+PlumaMessageBus *pluma_message_bus_new (void);
+
+/* registering messages */
+PlumaMessageType *pluma_message_bus_lookup (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method);
+PlumaMessageType *pluma_message_bus_register (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ guint num_optional,
+ ...) G_GNUC_NULL_TERMINATED;
+
+void pluma_message_bus_unregister (PlumaMessageBus *bus,
+ PlumaMessageType *message_type);
+
+void pluma_message_bus_unregister_all (PlumaMessageBus *bus,
+ const gchar *object_path);
+
+gboolean pluma_message_bus_is_registered (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method);
+
+void pluma_message_bus_foreach (PlumaMessageBus *bus,
+ PlumaMessageBusForeach func,
+ gpointer userdata);
+
+
+/* connecting to message events */
+guint pluma_message_bus_connect (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata,
+ GDestroyNotify destroy_data);
+
+void pluma_message_bus_disconnect (PlumaMessageBus *bus,
+ guint id);
+
+void pluma_message_bus_disconnect_by_func (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata);
+
+/* blocking message event callbacks */
+void pluma_message_bus_block (PlumaMessageBus *bus,
+ guint id);
+void pluma_message_bus_block_by_func (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata);
+
+void pluma_message_bus_unblock (PlumaMessageBus *bus,
+ guint id);
+void pluma_message_bus_unblock_by_func (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ PlumaMessageCallback callback,
+ gpointer userdata);
+
+/* sending messages */
+void pluma_message_bus_send_message (PlumaMessageBus *bus,
+ PlumaMessage *message);
+void pluma_message_bus_send_message_sync (PlumaMessageBus *bus,
+ PlumaMessage *message);
+
+void pluma_message_bus_send (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ ...) G_GNUC_NULL_TERMINATED;
+PlumaMessage *pluma_message_bus_send_sync (PlumaMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* __PLUMA_MESSAGE_BUS_H__ */
+
+// ex:ts=8:noet:
diff --git a/pluma/pluma-message-type.c b/pluma/pluma-message-type.c
new file mode 100755
index 00000000..838dc7d6
--- /dev/null
+++ b/pluma/pluma-message-type.c
@@ -0,0 +1,526 @@
+#include "pluma-message-type.h"
+
+/**
+ * SECTION:pluma-message-type
+ * @short_description: message type description
+ * @include: pluma/pluma-message-type.h
+ *
+ * A message type is a prototype description for a #PlumaMessage used to
+ * transmit messages on a #PlumaMessageBus. The message type describes
+ * the Object Path, Method and Arguments of the message.
+ *
+ * A message type can contain any number of required and optional arguments.
+ * To instantiate a #PlumaMessage from a #PlumaMessageType, use
+ * pluma_message_type_instantiate().
+ *
+ * Registering a new message type on a #PlumaMessageBus with
+ * pluma_message_bus_register() internally creates a new #PlumaMessageType. When
+ * then using pluma_message_bus_send(), an actual instantiation of the
+ * registered type is internally created and send over the bus.
+ *
+ * <example>
+ * <programlisting>
+ * // Defining a new message type
+ * PlumaMessageType *message_type = pluma_message_type_new ("/plugins/example",
+ * "method",
+ * 0,
+ * "arg1", G_TYPE_STRING,
+ * NULL);
+ *
+ * // Instantiating an actual message from the type
+ * PlumaMessage *message = pluma_message_type_instantiate (message_type,
+ * "arg1", "Hello World",
+ * NULL);
+ * </programlisting>
+ * </example>
+ *
+ * Since: 2.25.3
+ *
+ */
+typedef struct
+{
+ GType type;
+ gboolean required;
+} ArgumentInfo;
+
+struct _PlumaMessageType
+{
+ gint ref_count;
+
+ gchar *object_path;
+ gchar *method;
+
+ guint num_arguments;
+ guint num_required;
+
+ GHashTable *arguments; // mapping of key -> ArgumentInfo
+};
+
+/**
+ * pluma_message_type_ref:
+ * @message_type: the #PlumaMessageType
+ *
+ * Increases the reference count on @message_type.
+ *
+ * Return value: @message_type
+ *
+ */
+PlumaMessageType *
+pluma_message_type_ref (PlumaMessageType *message_type)
+{
+ g_return_val_if_fail (message_type != NULL, NULL);
+ g_atomic_int_inc (&message_type->ref_count);
+
+ return message_type;
+}
+
+/**
+ * pluma_message_type_unref:
+ * @message_type: the #PlumaMessageType
+ *
+ * Decreases the reference count on @message_type. When the reference count
+ * drops to 0, @message_type is destroyed.
+ *
+ */
+void
+pluma_message_type_unref (PlumaMessageType *message_type)
+{
+ g_return_if_fail (message_type != NULL);
+
+ if (!g_atomic_int_dec_and_test (&message_type->ref_count))
+ return;
+
+ g_free (message_type->object_path);
+ g_free (message_type->method);
+
+ g_hash_table_destroy (message_type->arguments);
+ g_free (message_type);
+}
+
+/**
+ * pluma_message_type_get_type:
+ *
+ * Retrieves the GType object which is associated with the
+ * #PlumaMessageType class.
+ *
+ * Return value: the GType associated with #PlumaMessageType.
+ **/
+GType
+pluma_message_type_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (!our_type)
+ our_type = g_boxed_type_register_static (
+ "PlumaMessageType",
+ (GBoxedCopyFunc) pluma_message_type_ref,
+ (GBoxedFreeFunc) pluma_message_type_unref);
+
+ return our_type;
+}
+
+/**
+ * pluma_message_type_identifier:
+ * @object_path: the object path
+ * @method: the method
+ *
+ * Get the string identifier for @method at @object_path.
+ *
+ * Return value: the identifier for @method at @object_path
+ *
+ */
+gchar *
+pluma_message_type_identifier (const gchar *object_path,
+ const gchar *method)
+{
+ return g_strconcat (object_path, ".", method, NULL);
+}
+
+/**
+ * pluma_message_type_is_valid_object_path:
+ * @object_path: the object path
+ *
+ * Returns whether @object_path is a valid object path
+ *
+ * Return value: %TRUE if @object_path is a valid object path
+ *
+ */
+gboolean
+pluma_message_type_is_valid_object_path (const gchar *object_path)
+{
+ if (!object_path)
+ return FALSE;
+
+ /* needs to start with / */
+ if (*object_path != '/')
+ return FALSE;
+
+ while (*object_path)
+ {
+ if (*object_path == '/')
+ {
+ ++object_path;
+
+ if (!*object_path || !(g_ascii_isalpha (*object_path) || *object_path == '_'))
+ return FALSE;
+ }
+ else if (!(g_ascii_isalnum (*object_path) || *object_path == '_'))
+ {
+ return FALSE;
+ }
+
+ ++object_path;
+ }
+
+ return TRUE;
+}
+
+/**
+ * pluma_message_type_is_supported:
+ * @type: the #GType
+ *
+ * Returns if @type is #GType supported by the message system.
+ *
+ * Return value: %TRUE if @type is a supported #GType
+ *
+ */
+gboolean
+pluma_message_type_is_supported (GType type)
+{
+ gint i = 0;
+
+ static const GType type_list[] =
+ {
+ G_TYPE_BOOLEAN,
+ G_TYPE_CHAR,
+ G_TYPE_UCHAR,
+ G_TYPE_INT,
+ G_TYPE_UINT,
+ G_TYPE_LONG,
+ G_TYPE_ULONG,
+ G_TYPE_INT64,
+ G_TYPE_UINT64,
+ G_TYPE_ENUM,
+ G_TYPE_FLAGS,
+ G_TYPE_FLOAT,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING,
+ G_TYPE_POINTER,
+ G_TYPE_BOXED,
+ G_TYPE_OBJECT,
+ G_TYPE_INVALID
+ };
+
+ if (!G_TYPE_IS_VALUE_TYPE (type))
+ return FALSE;
+
+ while (type_list[i] != G_TYPE_INVALID)
+ {
+ if (g_type_is_a (type, type_list[i]))
+ return TRUE;
+ i++;
+ }
+
+ return FALSE;
+}
+
+/**
+ * pluma_message_type_new_valist:
+ * @object_path: the object path
+ * @method: the method
+ * @num_optional: number of optional arguments
+ * @var_args: key/gtype pair variable argument list
+ *
+ * Create a new #PlumaMessageType for @method at @object_path. Argument names
+ * and values are supplied by the NULL terminated variable argument list.
+ * The last @num_optional provided arguments are considered optional.
+ *
+ * Return value: the newly constructed #PlumaMessageType
+ *
+ */
+PlumaMessageType *
+pluma_message_type_new_valist (const gchar *object_path,
+ const gchar *method,
+ guint num_optional,
+ va_list var_args)
+{
+ PlumaMessageType *message_type;
+
+ g_return_val_if_fail (object_path != NULL, NULL);
+ g_return_val_if_fail (method != NULL, NULL);
+ g_return_val_if_fail (pluma_message_type_is_valid_object_path (object_path), NULL);
+
+ message_type = g_new0(PlumaMessageType, 1);
+
+ message_type->ref_count = 1;
+ message_type->object_path = g_strdup(object_path);
+ message_type->method = g_strdup(method);
+ message_type->num_arguments = 0;
+ message_type->arguments = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)g_free);
+
+ pluma_message_type_set_valist (message_type, num_optional, var_args);
+ return message_type;
+}
+
+/**
+ * pluma_message_type_new:
+ * @object_path: the object path
+ * @method: the method
+ * @num_optional: number of optional arguments
+ * @...: key/gtype pair variable argument list
+ *
+ * Create a new #PlumaMessageType for @method at @object_path. Argument names
+ * and values are supplied by the NULL terminated variable argument list.
+ * The last @num_optional provided arguments are considered optional.
+ *
+ * Return value: the newly constructed #PlumaMessageType
+ *
+ */
+PlumaMessageType *
+pluma_message_type_new (const gchar *object_path,
+ const gchar *method,
+ guint num_optional,
+ ...)
+{
+ PlumaMessageType *message_type;
+ va_list var_args;
+
+ va_start(var_args, num_optional);
+ message_type = pluma_message_type_new_valist (object_path, method, num_optional, var_args);
+ va_end(var_args);
+
+ return message_type;
+}
+
+/**
+ * pluma_message_type_set:
+ * @message_type: the #PlumaMessageType
+ * @num_optional: number of optional arguments
+ * @...: key/gtype pair variable argument list
+ *
+ * Sets argument names/types supplied by the NULL terminated variable
+ * argument list. The last @num_optional provided arguments are considered
+ * optional.
+ *
+ */
+void
+pluma_message_type_set (PlumaMessageType *message_type,
+ guint num_optional,
+ ...)
+{
+ va_list va_args;
+
+ va_start (va_args, num_optional);
+ pluma_message_type_set_valist (message_type, num_optional, va_args);
+ va_end (va_args);
+}
+
+/**
+ * pluma_message_type_set_valist:
+ * @message_type: the #PlumaMessageType
+ * @num_optional: number of optional arguments
+ * @var_args: key/gtype pair variable argument list
+ *
+ * Sets argument names/types supplied by the NULL terminated variable
+ * argument list @var_args. The last @num_optional provided arguments are
+ * considered optional.
+ *
+ */
+void
+pluma_message_type_set_valist (PlumaMessageType *message_type,
+ guint num_optional,
+ va_list var_args)
+{
+ const gchar *key;
+ ArgumentInfo **optional = g_new0(ArgumentInfo *, num_optional);
+ guint i;
+ guint added = 0;
+
+ // parse key -> gtype pair arguments
+ while ((key = va_arg (var_args, const gchar *)) != NULL)
+ {
+ // get corresponding GType
+ GType gtype = va_arg (var_args, GType);
+ ArgumentInfo *info;
+
+ if (!pluma_message_type_is_supported (gtype))
+ {
+ g_error ("Message type '%s' is not supported", g_type_name (gtype));
+
+ pluma_message_type_unref (message_type);
+ g_free (optional);
+
+ return;
+ }
+
+ info = g_new(ArgumentInfo, 1);
+ info->type = gtype;
+ info->required = TRUE;
+
+ g_hash_table_insert (message_type->arguments, g_strdup (key), info);
+
+ ++message_type->num_arguments;
+ ++added;
+
+ if (num_optional > 0)
+ {
+ for (i = num_optional - 1; i > 0; --i)
+ optional[i] = optional[i - 1];
+
+ *optional = info;
+ }
+ }
+
+ message_type->num_required += added;
+
+ // set required for last num_optional arguments
+ for (i = 0; i < num_optional; ++i)
+ {
+ if (optional[i])
+ {
+ optional[i]->required = FALSE;
+ --message_type->num_required;
+ }
+ }
+
+ g_free (optional);
+}
+
+/**
+ * pluma_message_type_instantiate_valist:
+ * @message_type: the #PlumaMessageType
+ * @va_args: NULL terminated variable list of key/value pairs
+ *
+ * Instantiate a new message from the message type with specific values
+ * for the message arguments.
+ *
+ * Return value: the newly created message
+ *
+ */
+PlumaMessage *
+pluma_message_type_instantiate_valist (PlumaMessageType *message_type,
+ va_list va_args)
+{
+ PlumaMessage *message;
+
+ g_return_val_if_fail (message_type != NULL, NULL);
+
+ message = PLUMA_MESSAGE (g_object_new (PLUMA_TYPE_MESSAGE, "type", message_type, NULL));
+ pluma_message_set_valist (message, va_args);
+
+ return message;
+}
+
+/**
+ * pluma_message_type_instantiate:
+ * @message_type: the #PlumaMessageType
+ * @...: NULL terminated variable list of key/value pairs
+ *
+ * Instantiate a new message from the message type with specific values
+ * for the message arguments.
+ *
+ * Return value: the newly created message
+ *
+ */
+PlumaMessage *
+pluma_message_type_instantiate (PlumaMessageType *message_type,
+ ...)
+{
+ PlumaMessage *message;
+ va_list va_args;
+
+ va_start (va_args, message_type);
+ message = pluma_message_type_instantiate_valist (message_type, va_args);
+ va_end (va_args);
+
+ return message;
+}
+
+/**
+ * pluma_message_type_get_object_path:
+ * @message_type: the #PlumaMessageType
+ *
+ * Get the message type object path.
+ *
+ * Return value: the message type object path
+ *
+ */
+const gchar *
+pluma_message_type_get_object_path (PlumaMessageType *message_type)
+{
+ return message_type->object_path;
+}
+
+/**
+ * pluma_message_type_get_method:
+ * @message_type: the #PlumaMessageType
+ *
+ * Get the message type method.
+ *
+ * Return value: the message type method
+ *
+ */
+const gchar *
+pluma_message_type_get_method (PlumaMessageType *message_type)
+{
+ return message_type->method;
+}
+
+/**
+ * pluma_message_type_lookup:
+ * @message_type: the #PlumaMessageType
+ * @key: the argument key
+ *
+ * Get the argument key #GType.
+ *
+ * Return value: the #GType of @key
+ *
+ */
+GType
+pluma_message_type_lookup (PlumaMessageType *message_type,
+ const gchar *key)
+{
+ ArgumentInfo *info = g_hash_table_lookup (message_type->arguments, key);
+
+ if (!info)
+ return G_TYPE_INVALID;
+
+ return info->type;
+}
+
+typedef struct
+{
+ PlumaMessageTypeForeach func;
+ gpointer user_data;
+} ForeachInfo;
+
+static void
+foreach_gtype (const gchar *key,
+ ArgumentInfo *info,
+ ForeachInfo *finfo)
+{
+ finfo->func (key, info->type, info->required, finfo->user_data);
+}
+
+/**
+ * pluma_message_type_foreach:
+ * @message_type: the #PlumaMessageType
+ * @func: the callback function
+ * @user_data: user data supplied to the callback function
+ *
+ * Calls @func for each argument in the message type.
+ *
+ */
+void
+pluma_message_type_foreach (PlumaMessageType *message_type,
+ PlumaMessageTypeForeach func,
+ gpointer user_data)
+{
+ ForeachInfo info = {func, user_data};
+ g_hash_table_foreach (message_type->arguments, (GHFunc)foreach_gtype, &info);
+}
+
+// ex:ts=8:noet:
diff --git a/pluma/pluma-message-type.h b/pluma/pluma-message-type.h
new file mode 100755
index 00000000..af5d47d9
--- /dev/null
+++ b/pluma/pluma-message-type.h
@@ -0,0 +1,67 @@
+#ifndef __PLUMA_MESSAGE_TYPE_H__
+#define __PLUMA_MESSAGE_TYPE_H__
+
+#include <glib-object.h>
+#include <stdarg.h>
+
+#include "pluma-message.h"
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_MESSAGE_TYPE (pluma_message_type_get_type ())
+#define PLUMA_MESSAGE_TYPE(x) ((PlumaMessageType *)(x))
+
+typedef void (*PlumaMessageTypeForeach) (const gchar *key,
+ GType type,
+ gboolean required,
+ gpointer user_data);
+
+typedef struct _PlumaMessageType PlumaMessageType;
+
+GType pluma_message_type_get_type (void) G_GNUC_CONST;
+
+gboolean pluma_message_type_is_supported (GType type);
+gchar *pluma_message_type_identifier (const gchar *object_path,
+ const gchar *method);
+gboolean pluma_message_type_is_valid_object_path (const gchar *object_path);
+
+PlumaMessageType *pluma_message_type_new (const gchar *object_path,
+ const gchar *method,
+ guint num_optional,
+ ...) G_GNUC_NULL_TERMINATED;
+PlumaMessageType *pluma_message_type_new_valist (const gchar *object_path,
+ const gchar *method,
+ guint num_optional,
+ va_list va_args);
+
+void pluma_message_type_set (PlumaMessageType *message_type,
+ guint num_optional,
+ ...) G_GNUC_NULL_TERMINATED;
+void pluma_message_type_set_valist (PlumaMessageType *message_type,
+ guint num_optional,
+ va_list va_args);
+
+PlumaMessageType *pluma_message_type_ref (PlumaMessageType *message_type);
+void pluma_message_type_unref (PlumaMessageType *message_type);
+
+
+PlumaMessage *pluma_message_type_instantiate_valist (PlumaMessageType *message_type,
+ va_list va_args);
+PlumaMessage *pluma_message_type_instantiate (PlumaMessageType *message_type,
+ ...) G_GNUC_NULL_TERMINATED;
+
+const gchar *pluma_message_type_get_object_path (PlumaMessageType *message_type);
+const gchar *pluma_message_type_get_method (PlumaMessageType *message_type);
+
+GType pluma_message_type_lookup (PlumaMessageType *message_type,
+ const gchar *key);
+
+void pluma_message_type_foreach (PlumaMessageType *message_type,
+ PlumaMessageTypeForeach func,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __PLUMA_MESSAGE_TYPE_H__ */
+
+// ex:ts=8:noet:
diff --git a/pluma/pluma-message.c b/pluma/pluma-message.c
new file mode 100755
index 00000000..2e9ffcc3
--- /dev/null
+++ b/pluma/pluma-message.c
@@ -0,0 +1,593 @@
+#include "pluma-message.h"
+#include "pluma-message-type.h"
+
+#include <string.h>
+#include <gobject/gvaluecollector.h>
+
+/**
+ * SECTION:pluma-message
+ * @short_description: message bus message object
+ * @include: pluma/pluma-message.h
+ *
+ * Communication on a #PlumaMessageBus is done through messages. Messages are
+ * sent over the bus and received by connecting callbacks on the message bus.
+ * A #PlumaMessage is an instantiation of a #PlumaMessageType, containing
+ * values for the arguments as specified in the message type.
+ *
+ * A message can be seen as a method call, or signal emission depending on
+ * who is the sender and who is the receiver. There is no explicit distinction
+ * between methods and signals.
+ *
+ * Since: 2.25.3
+ *
+ */
+#define PLUMA_MESSAGE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), PLUMA_TYPE_MESSAGE, PlumaMessagePrivate))
+
+enum {
+ PROP_0,
+
+ PROP_OBJECT_PATH,
+ PROP_METHOD,
+ PROP_TYPE
+};
+
+struct _PlumaMessagePrivate
+{
+ PlumaMessageType *type;
+ gboolean valid;
+
+ GHashTable *values;
+};
+
+G_DEFINE_TYPE (PlumaMessage, pluma_message, G_TYPE_OBJECT)
+
+static void
+pluma_message_finalize (GObject *object)
+{
+ PlumaMessage *message = PLUMA_MESSAGE (object);
+
+ pluma_message_type_unref (message->priv->type);
+ g_hash_table_destroy (message->priv->values);
+
+ G_OBJECT_CLASS (pluma_message_parent_class)->finalize (object);
+}
+
+static void
+pluma_message_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaMessage *msg = PLUMA_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, pluma_message_type_get_object_path (msg->priv->type));
+ break;
+ case PROP_METHOD:
+ g_value_set_string (value, pluma_message_type_get_method (msg->priv->type));
+ break;
+ case PROP_TYPE:
+ g_value_set_boxed (value, msg->priv->type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_message_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaMessage *msg = PLUMA_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_TYPE:
+ msg->priv->type = PLUMA_MESSAGE_TYPE (g_value_dup_boxed (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GValue *
+add_value (PlumaMessage *message,
+ const gchar *key)
+{
+ GValue *value;
+ GType type = pluma_message_type_lookup (message->priv->type, key);
+
+ if (type == G_TYPE_INVALID)
+ return NULL;
+
+ value = g_new0 (GValue, 1);
+ g_value_init (value, type);
+ g_value_reset (value);
+
+ g_hash_table_insert (message->priv->values, g_strdup (key), value);
+
+ return value;
+}
+
+static void
+pluma_message_class_init (PlumaMessageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = pluma_message_finalize;
+ object_class->get_property = pluma_message_get_property;
+ object_class->set_property = pluma_message_set_property;
+
+ /**
+ * PlumaMessage:object_path:
+ *
+ * The messages object path (e.g. /pluma/object/path).
+ *
+ */
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ "OBJECT_PATH",
+ "The message object path",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * PlumaMessage:method:
+ *
+ * The messages method.
+ *
+ */
+ g_object_class_install_property (object_class, PROP_METHOD,
+ g_param_spec_string ("method",
+ "METHOD",
+ "The message method",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * PlumaMEssage:type:
+ *
+ * The message type.
+ *
+ */
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_boxed ("type",
+ "TYPE",
+ "The message type",
+ PLUMA_TYPE_MESSAGE_TYPE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof(PlumaMessagePrivate));
+}
+
+static void
+destroy_value (GValue *value)
+{
+ g_value_unset (value);
+ g_free (value);
+}
+
+static void
+pluma_message_init (PlumaMessage *self)
+{
+ self->priv = PLUMA_MESSAGE_GET_PRIVATE (self);
+
+ self->priv->values = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)destroy_value);
+}
+
+static gboolean
+set_value_real (GValue *to,
+ const GValue *from)
+{
+ GType from_type;
+ GType to_type;
+
+ from_type = G_VALUE_TYPE (from);
+ to_type = G_VALUE_TYPE (to);
+
+ if (!g_type_is_a (from_type, to_type))
+ {
+ if (!g_value_transform (from, to))
+ {
+ g_warning ("%s: Unable to make conversion from %s to %s",
+ G_STRLOC,
+ g_type_name (from_type),
+ g_type_name (to_type));
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ g_value_copy (from, to);
+ return TRUE;
+}
+
+inline static GValue *
+value_lookup (PlumaMessage *message,
+ const gchar *key,
+ gboolean create)
+{
+ GValue *ret = (GValue *)g_hash_table_lookup (message->priv->values, key);
+
+ if (!ret && create)
+ ret = add_value (message, key);
+
+ return ret;
+}
+
+/**
+ * pluma_message_get_method:
+ * @message: the #PlumaMessage
+ *
+ * Get the message method.
+ *
+ * Return value: the message method
+ *
+ */
+const gchar *
+pluma_message_get_method (PlumaMessage *message)
+{
+ g_return_val_if_fail (PLUMA_IS_MESSAGE (message), NULL);
+
+ return pluma_message_type_get_method (message->priv->type);
+}
+
+/**
+ * pluma_message_get_object_path:
+ * @message: the #PlumaMessage
+ *
+ * Get the message object path.
+ *
+ * Return value: the message object path
+ *
+ */
+const gchar *
+pluma_message_get_object_path (PlumaMessage *message)
+{
+ g_return_val_if_fail (PLUMA_IS_MESSAGE (message), NULL);
+
+ return pluma_message_type_get_object_path (message->priv->type);
+}
+
+/**
+ * pluma_message_set:
+ * @message: the #PlumaMessage
+ * @...: a NULL terminated variable list of key/value pairs
+ *
+ * Set values of message arguments. The supplied @var_args should contain
+ * pairs of keys and argument values.
+ *
+ */
+void
+pluma_message_set (PlumaMessage *message,
+ ...)
+{
+ va_list ap;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ va_start (ap, message);
+ pluma_message_set_valist (message, ap);
+ va_end (ap);
+}
+
+/**
+ * pluma_message_set_valist:
+ * @message: the #PlumaMessage
+ * @var_args: a NULL terminated variable list of key/value pairs
+ *
+ * Set values of message arguments. The supplied @var_args should contain
+ * pairs of keys and argument values.
+ *
+ */
+void
+pluma_message_set_valist (PlumaMessage *message,
+ va_list var_args)
+{
+ const gchar *key;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ while ((key = va_arg (var_args, const gchar *)) != NULL)
+ {
+ /* lookup the key */
+ GValue *container = value_lookup (message, key, TRUE);
+ GValue value = {0,};
+ gchar *error = NULL;
+
+ if (!container)
+ {
+ g_warning ("%s: Cannot set value for %s, does not exist",
+ G_STRLOC,
+ key);
+
+ /* skip value */
+ va_arg (var_args, gpointer);
+ continue;
+ }
+
+ g_value_init (&value, G_VALUE_TYPE (container));
+ G_VALUE_COLLECT (&value, var_args, 0, &error);
+
+ if (error)
+ {
+ g_warning ("%s: %s", G_STRLOC, error);
+ continue;
+ }
+
+ set_value_real (container, &value);
+ g_value_unset (&value);
+ }
+}
+
+/**
+ * pluma_message_set_value:
+ * @message: the #PlumaMessage
+ * @key: the argument key
+ * @value: the argument value
+ *
+ * Set value of message argument @key to @value.
+ *
+ */
+void
+pluma_message_set_value (PlumaMessage *message,
+ const gchar *key,
+ GValue *value)
+{
+ GValue *container;
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ container = value_lookup (message, key, TRUE);
+
+ if (!container)
+ {
+ g_warning ("%s: Cannot set value for %s, does not exist",
+ G_STRLOC,
+ key);
+ return;
+ }
+
+ set_value_real (container, value);
+}
+
+/**
+ * pluma_message_set_valuesv:
+ * @message: the #PlumaMessage
+ * @keys: keys to set values for
+ * @values: values to set
+ * @n_values: number of arguments to set values for
+ *
+ * Set message argument values.
+ *
+ */
+void
+pluma_message_set_valuesv (PlumaMessage *message,
+ const gchar **keys,
+ GValue *values,
+ gint n_values)
+{
+ gint i;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ for (i = 0; i < n_values; i++)
+ {
+ pluma_message_set_value (message, keys[i], &values[i]);
+ }
+}
+
+/**
+ * pluma_message_get:
+ * @message: the #PlumaMessage
+ * @...: a NULL variable argument list of key/value container pairs
+ *
+ * Get values of message arguments. The supplied @var_args should contain
+ * pairs of keys and pointers to variables which are set to the argument
+ * value for the specified key.
+ *
+ */
+void
+pluma_message_get (PlumaMessage *message,
+ ...)
+{
+ va_list ap;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ va_start (ap, message);
+ pluma_message_get_valist (message, ap);
+ va_end (ap);
+}
+
+/**
+ * pluma_message_get_valist:
+ * @message: the #PlumaMessage
+ * @var_args: a NULL variable argument list of key/value container pairs
+ *
+ * Get values of message arguments. The supplied @var_args should contain
+ * pairs of keys and pointers to variables which are set to the argument
+ * value for the specified key.
+ *
+ */
+void
+pluma_message_get_valist (PlumaMessage *message,
+ va_list var_args)
+{
+ const gchar *key;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ while ((key = va_arg (var_args, const gchar *)) != NULL)
+ {
+ GValue *container;
+ GValue copy = {0,};
+ gchar *error = NULL;
+
+ container = value_lookup (message, key, FALSE);
+
+ if (!container)
+ {
+ /* skip value */
+ va_arg (var_args, gpointer);
+ continue;
+ }
+
+ /* copy the value here, to be sure it isn't tainted */
+ g_value_init (&copy, G_VALUE_TYPE (container));
+ g_value_copy (container, &copy);
+
+ G_VALUE_LCOPY (&copy, var_args, 0, &error);
+
+ if (error)
+ {
+ g_warning ("%s: %s", G_STRLOC, error);
+ g_free (error);
+
+ /* purposely leak the value here, because it might
+ be in a bad state */
+ continue;
+ }
+
+ g_value_unset (&copy);
+ }
+}
+
+/**
+ * pluma_message_get_value:
+ * @message: the #PlumaMessage
+ * @key: the argument key
+ * @value: value return container
+ *
+ * Get the value of a specific message argument. @value will be initialized
+ * with the correct type.
+ *
+ */
+void
+pluma_message_get_value (PlumaMessage *message,
+ const gchar *key,
+ GValue *value)
+{
+ GValue *container;
+
+ g_return_if_fail (PLUMA_IS_MESSAGE (message));
+
+ container = value_lookup (message, key, FALSE);
+
+ if (!container)
+ {
+ g_warning ("%s: Invalid key `%s'",
+ G_STRLOC,
+ key);
+ return;
+ }
+
+ g_value_init (value, G_VALUE_TYPE (container));
+ set_value_real (value, container);
+}
+
+/**
+ * pluma_message_get_key_type:
+ * @message: the #PlumaMessage
+ * @key: the argument key
+ *
+ * Get the type of a message argument.
+ *
+ * Return value: the type of @key
+ *
+ */
+GType
+pluma_message_get_key_type (PlumaMessage *message,
+ const gchar *key)
+{
+ g_return_val_if_fail (PLUMA_IS_MESSAGE (message), G_TYPE_INVALID);
+ g_return_val_if_fail (message->priv->type != NULL, G_TYPE_INVALID);
+
+ return pluma_message_type_lookup (message->priv->type, key);
+}
+
+/**
+ * pluma_message_has_key:
+ * @message: the #PlumaMessage
+ * @key: the argument key
+ *
+ * Check whether the message has a specific key.
+ *
+ * Return value: %TRUE if @message has argument @key
+ *
+ */
+gboolean
+pluma_message_has_key (PlumaMessage *message,
+ const gchar *key)
+{
+ g_return_val_if_fail (PLUMA_IS_MESSAGE (message), FALSE);
+
+ return value_lookup (message, key, FALSE) != NULL;
+}
+
+typedef struct
+{
+ PlumaMessage *message;
+ gboolean valid;
+} ValidateInfo;
+
+static void
+validate_key (const gchar *key,
+ GType type,
+ gboolean required,
+ ValidateInfo *info)
+{
+ GValue *value;
+
+ if (!info->valid || !required)
+ return;
+
+ value = value_lookup (info->message, key, FALSE);
+
+ if (!value)
+ info->valid = FALSE;
+}
+
+/**
+ * pluma_message_validate:
+ * @message: the #PlumaMessage
+ *
+ * Validates the message arguments according to the message type.
+ *
+ * Return value: %TRUE if the message is valid
+ *
+ */
+gboolean
+pluma_message_validate (PlumaMessage *message)
+{
+ ValidateInfo info = {message, TRUE};
+
+ g_return_val_if_fail (PLUMA_IS_MESSAGE (message), FALSE);
+ g_return_val_if_fail (message->priv->type != NULL, FALSE);
+
+ if (!message->priv->valid)
+ {
+ pluma_message_type_foreach (message->priv->type,
+ (PlumaMessageTypeForeach)validate_key,
+ &info);
+
+ message->priv->valid = info.valid;
+ }
+
+ return message->priv->valid;
+}
+
+// ex:ts=8:noet:
diff --git a/pluma/pluma-message.h b/pluma/pluma-message.h
new file mode 100755
index 00000000..48d1a81a
--- /dev/null
+++ b/pluma/pluma-message.h
@@ -0,0 +1,71 @@
+#ifndef __PLUMA_MESSAGE_H__
+#define __PLUMA_MESSAGE_H__
+
+#include <glib-object.h>
+#include <stdarg.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_MESSAGE (pluma_message_get_type ())
+#define PLUMA_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_MESSAGE, PlumaMessage))
+#define PLUMA_MESSAGE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_MESSAGE, PlumaMessage const))
+#define PLUMA_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_MESSAGE, PlumaMessageClass))
+#define PLUMA_IS_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_MESSAGE))
+#define PLUMA_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_MESSAGE))
+#define PLUMA_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_MESSAGE, PlumaMessageClass))
+
+typedef struct _PlumaMessage PlumaMessage;
+typedef struct _PlumaMessageClass PlumaMessageClass;
+typedef struct _PlumaMessagePrivate PlumaMessagePrivate;
+
+struct _PlumaMessage {
+ GObject parent;
+
+ PlumaMessagePrivate *priv;
+};
+
+struct _PlumaMessageClass {
+ GObjectClass parent_class;
+};
+
+GType pluma_message_get_type (void) G_GNUC_CONST;
+
+struct _PlumaMessageType pluma_message_get_message_type (PlumaMessage *message);
+
+void pluma_message_get (PlumaMessage *message,
+ ...) G_GNUC_NULL_TERMINATED;
+void pluma_message_get_valist (PlumaMessage *message,
+ va_list var_args);
+void pluma_message_get_value (PlumaMessage *message,
+ const gchar *key,
+ GValue *value);
+
+void pluma_message_set (PlumaMessage *message,
+ ...) G_GNUC_NULL_TERMINATED;
+void pluma_message_set_valist (PlumaMessage *message,
+ va_list var_args);
+void pluma_message_set_value (PlumaMessage *message,
+ const gchar *key,
+ GValue *value);
+void pluma_message_set_valuesv (PlumaMessage *message,
+ const gchar **keys,
+ GValue *values,
+ gint n_values);
+
+const gchar *pluma_message_get_object_path (PlumaMessage *message);
+const gchar *pluma_message_get_method (PlumaMessage *message);
+
+gboolean pluma_message_has_key (PlumaMessage *message,
+ const gchar *key);
+
+GType pluma_message_get_key_type (PlumaMessage *message,
+ const gchar *key);
+
+gboolean pluma_message_validate (PlumaMessage *message);
+
+
+G_END_DECLS
+
+#endif /* __PLUMA_MESSAGE_H__ */
+
+// ex:ts=8:noet:
diff --git a/pluma/pluma-metadata-manager.c b/pluma/pluma-metadata-manager.c
new file mode 100755
index 00000000..0bce26ee
--- /dev/null
+++ b/pluma/pluma-metadata-manager.c
@@ -0,0 +1,563 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-metadata-manager.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2003-2007 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <time.h>
+#include <stdlib.h>
+#include <libxml/xmlreader.h>
+#include "pluma-metadata-manager.h"
+#include "pluma-debug.h"
+#include "pluma-dirs.h"
+
+/*
+#define PLUMA_METADATA_VERBOSE_DEBUG 1
+*/
+
+#define METADATA_FILE "pluma-metadata.xml"
+
+#define MAX_ITEMS 50
+
+typedef struct _PlumaMetadataManager PlumaMetadataManager;
+
+typedef struct _Item Item;
+
+struct _Item
+{
+ time_t atime; /* time of last access */
+
+ GHashTable *values;
+};
+
+struct _PlumaMetadataManager
+{
+ gboolean values_loaded; /* It is true if the file
+ has been read */
+
+ guint timeout_id;
+
+ GHashTable *items;
+};
+
+static gboolean pluma_metadata_manager_save (gpointer data);
+
+
+static PlumaMetadataManager *pluma_metadata_manager = NULL;
+
+static void
+item_free (gpointer data)
+{
+ Item *item;
+
+ g_return_if_fail (data != NULL);
+
+#ifdef PLUMA_METADATA_VERBOSE_DEBUG
+ pluma_debug (DEBUG_METADATA);
+#endif
+
+ item = (Item *)data;
+
+ if (item->values != NULL)
+ g_hash_table_destroy (item->values);
+
+ g_free (item);
+}
+
+static void
+pluma_metadata_manager_arm_timeout (void)
+{
+ if (pluma_metadata_manager->timeout_id == 0)
+ {
+ pluma_metadata_manager->timeout_id =
+ g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE,
+ 2,
+ (GSourceFunc)pluma_metadata_manager_save,
+ NULL,
+ NULL);
+ }
+}
+
+static gboolean
+pluma_metadata_manager_init (void)
+{
+ pluma_debug (DEBUG_METADATA);
+
+ if (pluma_metadata_manager != NULL)
+ return TRUE;
+
+ pluma_metadata_manager = g_new0 (PlumaMetadataManager, 1);
+
+ pluma_metadata_manager->values_loaded = FALSE;
+
+ pluma_metadata_manager->items =
+ g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ item_free);
+
+ return TRUE;
+}
+
+/* This function must be called before exiting pluma */
+void
+pluma_metadata_manager_shutdown (void)
+{
+ pluma_debug (DEBUG_METADATA);
+
+ if (pluma_metadata_manager == NULL)
+ return;
+
+ if (pluma_metadata_manager->timeout_id)
+ {
+ g_source_remove (pluma_metadata_manager->timeout_id);
+ pluma_metadata_manager->timeout_id = 0;
+ pluma_metadata_manager_save (NULL);
+ }
+
+ if (pluma_metadata_manager->items != NULL)
+ g_hash_table_destroy (pluma_metadata_manager->items);
+
+ g_free (pluma_metadata_manager);
+ pluma_metadata_manager = NULL;
+}
+
+static void
+parseItem (xmlDocPtr doc, xmlNodePtr cur)
+{
+ Item *item;
+
+ xmlChar *uri;
+ xmlChar *atime;
+
+#ifdef PLUMA_METADATA_VERBOSE_DEBUG
+ pluma_debug (DEBUG_METADATA);
+#endif
+
+ if (xmlStrcmp (cur->name, (const xmlChar *)"document") != 0)
+ return;
+
+ uri = xmlGetProp (cur, (const xmlChar *)"uri");
+ if (uri == NULL)
+ return;
+
+ atime = xmlGetProp (cur, (const xmlChar *)"atime");
+ if (atime == NULL)
+ {
+ xmlFree (uri);
+ return;
+ }
+
+ item = g_new0 (Item, 1);
+
+ item->atime = g_ascii_strtoull ((char *)atime, NULL, 0);
+
+ item->values = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ cur = cur->xmlChildrenNode;
+
+ while (cur != NULL)
+ {
+ if (xmlStrcmp (cur->name, (const xmlChar *)"entry") == 0)
+ {
+ xmlChar *key;
+ xmlChar *value;
+
+ key = xmlGetProp (cur, (const xmlChar *)"key");
+ value = xmlGetProp (cur, (const xmlChar *)"value");
+
+ if ((key != NULL) && (value != NULL))
+ g_hash_table_insert (item->values,
+ g_strdup ((gchar *)key),
+ g_strdup ((gchar *)value));
+
+ if (key != NULL)
+ xmlFree (key);
+ if (value != NULL)
+ xmlFree (value);
+ }
+
+ cur = cur->next;
+ }
+
+ g_hash_table_insert (pluma_metadata_manager->items,
+ g_strdup ((gchar *)uri),
+ item);
+
+ xmlFree (uri);
+ xmlFree (atime);
+}
+
+static gchar *
+get_metadata_filename (void)
+{
+ gchar *cache_dir;
+ gchar *metadata;
+
+ cache_dir = pluma_dirs_get_user_cache_dir ();
+
+ metadata = g_build_filename (cache_dir,
+ METADATA_FILE,
+ NULL);
+
+ g_free (cache_dir);
+
+ return metadata;
+}
+
+static gboolean
+load_values (void)
+{
+ xmlDocPtr doc;
+ xmlNodePtr cur;
+ gchar *file_name;
+
+ pluma_debug (DEBUG_METADATA);
+
+ g_return_val_if_fail (pluma_metadata_manager != NULL, FALSE);
+ g_return_val_if_fail (pluma_metadata_manager->values_loaded == FALSE, FALSE);
+
+ pluma_metadata_manager->values_loaded = TRUE;
+
+ xmlKeepBlanksDefault (0);
+
+ /* FIXME: file locking - Paolo */
+ file_name = get_metadata_filename ();
+ if ((file_name == NULL) ||
+ (!g_file_test (file_name, G_FILE_TEST_EXISTS)))
+ {
+ g_free (file_name);
+ return FALSE;
+ }
+
+ doc = xmlParseFile (file_name);
+ g_free (file_name);
+
+ if (doc == NULL)
+ {
+ return FALSE;
+ }
+
+ cur = xmlDocGetRootElement (doc);
+ if (cur == NULL)
+ {
+ g_message ("The metadata file '%s' is empty", METADATA_FILE);
+ xmlFreeDoc (doc);
+
+ return FALSE;
+ }
+
+ if (xmlStrcmp (cur->name, (const xmlChar *) "metadata"))
+ {
+ g_message ("File '%s' is of the wrong type", METADATA_FILE);
+ xmlFreeDoc (doc);
+
+ return FALSE;
+ }
+
+ cur = xmlDocGetRootElement (doc);
+ cur = cur->xmlChildrenNode;
+
+ while (cur != NULL)
+ {
+ parseItem (doc, cur);
+
+ cur = cur->next;
+ }
+
+ xmlFreeDoc (doc);
+
+ return TRUE;
+}
+
+gchar *
+pluma_metadata_manager_get (const gchar *uri,
+ const gchar *key)
+{
+ Item *item;
+ gchar *value;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ pluma_debug_message (DEBUG_METADATA, "URI: %s --- key: %s", uri, key );
+
+ pluma_metadata_manager_init ();
+
+ if (!pluma_metadata_manager->values_loaded)
+ {
+ gboolean res;
+
+ res = load_values ();
+
+ if (!res)
+ return NULL;
+ }
+
+ item = (Item *)g_hash_table_lookup (pluma_metadata_manager->items,
+ uri);
+
+ if (item == NULL)
+ return NULL;
+
+ item->atime = time (NULL);
+
+ if (item->values == NULL)
+ return NULL;
+
+ value = g_hash_table_lookup (item->values, key);
+
+ if (value == NULL)
+ return NULL;
+ else
+ return g_strdup (value);
+}
+
+void
+pluma_metadata_manager_set (const gchar *uri,
+ const gchar *key,
+ const gchar *value)
+{
+ Item *item;
+
+ g_return_if_fail (uri != NULL);
+ g_return_if_fail (key != NULL);
+
+ pluma_debug_message (DEBUG_METADATA, "URI: %s --- key: %s --- value: %s", uri, key, value);
+
+ pluma_metadata_manager_init ();
+
+ if (!pluma_metadata_manager->values_loaded)
+ {
+ gboolean res;
+
+ res = load_values ();
+
+ if (!res)
+ return;
+ }
+
+ item = (Item *)g_hash_table_lookup (pluma_metadata_manager->items,
+ uri);
+
+ if (item == NULL)
+ {
+ item = g_new0 (Item, 1);
+
+ g_hash_table_insert (pluma_metadata_manager->items,
+ g_strdup (uri),
+ item);
+ }
+
+ if (item->values == NULL)
+ item->values = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+ if (value != NULL)
+ g_hash_table_insert (item->values,
+ g_strdup (key),
+ g_strdup (value));
+ else
+ g_hash_table_remove (item->values,
+ key);
+
+ item->atime = time (NULL);
+
+ pluma_metadata_manager_arm_timeout ();
+}
+
+static void
+save_values (const gchar *key, const gchar *value, xmlNodePtr parent)
+{
+ xmlNodePtr xml_node;
+
+#ifdef PLUMA_METADATA_VERBOSE_DEBUG
+ pluma_debug (DEBUG_METADATA);
+#endif
+
+ g_return_if_fail (key != NULL);
+
+ if (value == NULL)
+ return;
+
+ xml_node = xmlNewChild (parent,
+ NULL,
+ (const xmlChar *)"entry",
+ NULL);
+
+ xmlSetProp (xml_node,
+ (const xmlChar *)"key",
+ (const xmlChar *)key);
+ xmlSetProp (xml_node,
+ (const xmlChar *)"value",
+ (const xmlChar *)value);
+
+#ifdef PLUMA_METADATA_VERBOSE_DEBUG
+ pluma_debug_message (DEBUG_METADATA, "entry: %s = %s", key, value);
+#endif
+}
+
+static void
+save_item (const gchar *key, const gpointer *data, xmlNodePtr parent)
+{
+ xmlNodePtr xml_node;
+ const Item *item = (const Item *)data;
+ gchar *atime;
+
+#ifdef PLUMA_METADATA_VERBOSE_DEBUG
+ pluma_debug (DEBUG_METADATA);
+#endif
+
+ g_return_if_fail (key != NULL);
+
+ if (item == NULL)
+ return;
+
+ xml_node = xmlNewChild (parent, NULL, (const xmlChar *)"document", NULL);
+
+ xmlSetProp (xml_node, (const xmlChar *)"uri", (const xmlChar *)key);
+
+#ifdef PLUMA_METADATA_VERBOSE_DEBUG
+ pluma_debug_message (DEBUG_METADATA, "uri: %s", key);
+#endif
+
+ atime = g_strdup_printf ("%ld", item->atime);
+ xmlSetProp (xml_node, (const xmlChar *)"atime", (const xmlChar *)atime);
+
+#ifdef PLUMA_METADATA_VERBOSE_DEBUG
+ pluma_debug_message (DEBUG_METADATA, "atime: %s", atime);
+#endif
+
+ g_free (atime);
+
+ g_hash_table_foreach (item->values,
+ (GHFunc)save_values,
+ xml_node);
+}
+
+static void
+get_oldest (const gchar *key, const gpointer value, const gchar ** key_to_remove)
+{
+ const Item *item = (const Item *)value;
+
+ if (*key_to_remove == NULL)
+ {
+ *key_to_remove = key;
+ }
+ else
+ {
+ const Item *item_to_remove =
+ g_hash_table_lookup (pluma_metadata_manager->items,
+ *key_to_remove);
+
+ g_return_if_fail (item_to_remove != NULL);
+
+ if (item->atime < item_to_remove->atime)
+ {
+ *key_to_remove = key;
+ }
+ }
+}
+
+static void
+resize_items (void)
+{
+ while (g_hash_table_size (pluma_metadata_manager->items) > MAX_ITEMS)
+ {
+ gpointer key_to_remove = NULL;
+
+ g_hash_table_foreach (pluma_metadata_manager->items,
+ (GHFunc)get_oldest,
+ &key_to_remove);
+
+ g_return_if_fail (key_to_remove != NULL);
+
+ g_hash_table_remove (pluma_metadata_manager->items,
+ key_to_remove);
+ }
+}
+
+static gboolean
+pluma_metadata_manager_save (gpointer data)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ gchar *file_name;
+
+ pluma_debug (DEBUG_METADATA);
+
+ pluma_metadata_manager->timeout_id = 0;
+
+ resize_items ();
+
+ xmlIndentTreeOutput = TRUE;
+
+ doc = xmlNewDoc ((const xmlChar *)"1.0");
+ if (doc == NULL)
+ return TRUE;
+
+ /* Create metadata root */
+ root = xmlNewDocNode (doc, NULL, (const xmlChar *)"metadata", NULL);
+ xmlDocSetRootElement (doc, root);
+
+ g_hash_table_foreach (pluma_metadata_manager->items,
+ (GHFunc)save_item,
+ root);
+
+ /* FIXME: lock file - Paolo */
+ file_name = get_metadata_filename ();
+ if (file_name != NULL)
+ {
+ gchar *cache_dir;
+ int res;
+
+ /* make sure the cache dir exists */
+ cache_dir = pluma_dirs_get_user_cache_dir ();
+ res = g_mkdir_with_parents (cache_dir, 0755);
+ if (res != -1)
+ {
+ xmlSaveFormatFile (file_name, doc, 1);
+ }
+
+ g_free (cache_dir);
+ g_free (file_name);
+ }
+
+ xmlFreeDoc (doc);
+
+ pluma_debug_message (DEBUG_METADATA, "DONE");
+
+ return FALSE;
+}
+
diff --git a/pluma/pluma-metadata-manager.h b/pluma/pluma-metadata-manager.h
new file mode 100755
index 00000000..26280d72
--- /dev/null
+++ b/pluma/pluma-metadata-manager.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-metadata-manager.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2003 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2003. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __PLUMA_METADATA_MANAGER_H__
+#define __PLUMA_METADATA_MANAGER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+
+/* This function must be called before exiting pluma */
+void pluma_metadata_manager_shutdown (void);
+
+
+gchar *pluma_metadata_manager_get (const gchar *uri,
+ const gchar *key);
+void pluma_metadata_manager_set (const gchar *uri,
+ const gchar *key,
+ const gchar *value);
+
+G_END_DECLS
+
+#endif /* __PLUMA_METADATA_MANAGER_H__ */
diff --git a/pluma/pluma-notebook.c b/pluma/pluma-notebook.c
new file mode 100755
index 00000000..85515b3a
--- /dev/null
+++ b/pluma/pluma-notebook.c
@@ -0,0 +1,1099 @@
+/*
+ * pluma-notebook.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+/* This file is a modified version of the epiphany file ephy-notebook.c
+ * Here the relevant copyright:
+ *
+ * Copyright (C) 2002 Christophe Fergeau
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ * Copyright (C) 2003, 2004 Christian Persch
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "pluma-notebook.h"
+#include "pluma-tab.h"
+#include "pluma-tab-label.h"
+#include "pluma-marshal.h"
+#include "pluma-window.h"
+
+#ifdef BUILD_SPINNER
+#include "pluma-spinner.h"
+#endif
+
+#define AFTER_ALL_TABS -1
+#define NOT_IN_APP_WINDOWS -2
+
+#define PLUMA_NOTEBOOK_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_NOTEBOOK, PlumaNotebookPrivate))
+
+struct _PlumaNotebookPrivate
+{
+ GList *focused_pages;
+ gulong motion_notify_handler_id;
+ gint x_start;
+ gint y_start;
+ gint drag_in_progress : 1;
+ gint always_show_tabs : 1;
+ gint close_buttons_sensitive : 1;
+ gint tab_drag_and_drop_enabled : 1;
+ guint destroy_has_run : 1;
+};
+
+G_DEFINE_TYPE(PlumaNotebook, pluma_notebook, GTK_TYPE_NOTEBOOK)
+
+static void pluma_notebook_finalize (GObject *object);
+
+static gboolean pluma_notebook_change_current_page (GtkNotebook *notebook,
+ gint offset);
+
+static void move_current_tab_to_another_notebook (PlumaNotebook *src,
+ PlumaNotebook *dest,
+ GdkEventMotion *event,
+ gint dest_position);
+
+/* Local variables */
+static GdkCursor *cursor = NULL;
+
+/* Signals */
+enum
+{
+ TAB_ADDED,
+ TAB_REMOVED,
+ TABS_REORDERED,
+ TAB_DETACHED,
+ TAB_CLOSE_REQUEST,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+pluma_notebook_destroy (GtkObject *object)
+{
+ PlumaNotebook *notebook = PLUMA_NOTEBOOK (object);
+
+ if (!notebook->priv->destroy_has_run)
+ {
+ GList *children, *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (notebook));
+
+ for (l = children; l != NULL; l = g_list_next (l))
+ {
+ pluma_notebook_remove_tab (notebook,
+ PLUMA_TAB (l->data));
+ }
+
+ g_list_free (children);
+ notebook->priv->destroy_has_run = TRUE;
+ }
+
+ GTK_OBJECT_CLASS (pluma_notebook_parent_class)->destroy (object);
+}
+
+static void
+pluma_notebook_class_init (PlumaNotebookClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
+ GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
+
+ object_class->finalize = pluma_notebook_finalize;
+ gtkobject_class->destroy = pluma_notebook_destroy;
+
+ notebook_class->change_current_page = pluma_notebook_change_current_page;
+
+ signals[TAB_ADDED] =
+ g_signal_new ("tab_added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaNotebookClass, tab_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_TAB);
+ signals[TAB_REMOVED] =
+ g_signal_new ("tab_removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaNotebookClass, tab_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_TAB);
+ signals[TAB_DETACHED] =
+ g_signal_new ("tab_detached",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaNotebookClass, tab_detached),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_TAB);
+ signals[TABS_REORDERED] =
+ g_signal_new ("tabs_reordered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaNotebookClass, tabs_reordered),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ signals[TAB_CLOSE_REQUEST] =
+ g_signal_new ("tab-close-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaNotebookClass, tab_close_request),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_TAB);
+
+ g_type_class_add_private (object_class, sizeof(PlumaNotebookPrivate));
+}
+
+static PlumaNotebook *
+find_notebook_at_pointer (gint abs_x, gint abs_y)
+{
+ GdkWindow *win_at_pointer;
+ GdkWindow *toplevel_win;
+ gpointer toplevel = NULL;
+ gint x, y;
+
+ /* FIXME multi-head */
+ win_at_pointer = gdk_window_at_pointer (&x, &y);
+ if (win_at_pointer == NULL)
+ {
+ /* We are outside all windows of the same application */
+ return NULL;
+ }
+
+ toplevel_win = gdk_window_get_toplevel (win_at_pointer);
+
+ /* get the GtkWidget which owns the toplevel GdkWindow */
+ gdk_window_get_user_data (toplevel_win, &toplevel);
+
+ /* toplevel should be an PlumaWindow */
+ if ((toplevel != NULL) &&
+ PLUMA_IS_WINDOW (toplevel))
+ {
+ return PLUMA_NOTEBOOK (_pluma_window_get_notebook
+ (PLUMA_WINDOW (toplevel)));
+ }
+
+ /* We are outside all windows containing a notebook */
+ return NULL;
+}
+
+static gboolean
+is_in_notebook_window (PlumaNotebook *notebook,
+ gint abs_x,
+ gint abs_y)
+{
+ PlumaNotebook *nb_at_pointer;
+
+ g_return_val_if_fail (notebook != NULL, FALSE);
+
+ nb_at_pointer = find_notebook_at_pointer (abs_x, abs_y);
+
+ return (nb_at_pointer == notebook);
+}
+
+static gint
+find_tab_num_at_pos (PlumaNotebook *notebook,
+ gint abs_x,
+ gint abs_y)
+{
+ GtkPositionType tab_pos;
+ int page_num = 0;
+ GtkNotebook *nb = GTK_NOTEBOOK (notebook);
+ GtkWidget *page;
+
+ tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook));
+
+ if (GTK_NOTEBOOK (notebook)->first_tab == NULL)
+ {
+ return AFTER_ALL_TABS;
+ }
+
+ /* For some reason unfullscreen + quick click can
+ cause a wrong click event to be reported to the tab */
+ if (!is_in_notebook_window (notebook, abs_x, abs_y))
+ {
+ return NOT_IN_APP_WINDOWS;
+ }
+
+ while ((page = gtk_notebook_get_nth_page (nb, page_num)) != NULL)
+ {
+ GtkWidget *tab;
+ gint max_x, max_y;
+ gint x_root, y_root;
+
+ tab = gtk_notebook_get_tab_label (nb, page);
+ g_return_val_if_fail (tab != NULL, AFTER_ALL_TABS);
+
+ if (!GTK_WIDGET_MAPPED (GTK_WIDGET (tab)))
+ {
+ ++page_num;
+ continue;
+ }
+
+ gdk_window_get_origin (GDK_WINDOW (tab->window),
+ &x_root, &y_root);
+
+ max_x = x_root + tab->allocation.x + tab->allocation.width;
+ max_y = y_root + tab->allocation.y + tab->allocation.height;
+
+ if (((tab_pos == GTK_POS_TOP) ||
+ (tab_pos == GTK_POS_BOTTOM)) &&
+ (abs_x <= max_x))
+ {
+ return page_num;
+ }
+ else if (((tab_pos == GTK_POS_LEFT) ||
+ (tab_pos == GTK_POS_RIGHT)) &&
+ (abs_y <= max_y))
+ {
+ return page_num;
+ }
+
+ ++page_num;
+ }
+
+ return AFTER_ALL_TABS;
+}
+
+static gint
+find_notebook_and_tab_at_pos (gint abs_x,
+ gint abs_y,
+ PlumaNotebook **notebook,
+ gint *page_num)
+{
+ *notebook = find_notebook_at_pointer (abs_x, abs_y);
+ if (*notebook == NULL)
+ {
+ return NOT_IN_APP_WINDOWS;
+ }
+
+ *page_num = find_tab_num_at_pos (*notebook, abs_x, abs_y);
+
+ if (*page_num < 0)
+ {
+ return *page_num;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/**
+ * pluma_notebook_move_tab:
+ * @src: a #PlumaNotebook
+ * @dest: a #PlumaNotebook
+ * @tab: a #PlumaTab
+ * @dest_position: the position for @tab
+ *
+ * Moves @tab from @src to @dest.
+ * If dest_position is greater than or equal to the number of tabs
+ * of the destination nootebook or negative, tab will be moved to the
+ * end of the tabs.
+ */
+void
+pluma_notebook_move_tab (PlumaNotebook *src,
+ PlumaNotebook *dest,
+ PlumaTab *tab,
+ gint dest_position)
+{
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (src));
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (dest));
+ g_return_if_fail (src != dest);
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ /* make sure the tab isn't destroyed while we move it */
+ g_object_ref (tab);
+ pluma_notebook_remove_tab (src, tab);
+ pluma_notebook_add_tab (dest, tab, dest_position, TRUE);
+ g_object_unref (tab);
+}
+
+/**
+ * pluma_notebook_reorder_tab:
+ * @src: a #PlumaNotebook
+ * @tab: a #PlumaTab
+ * @dest_position: the position for @tab
+ *
+ * Reorders the page containing @tab, so that it appears in @dest_position position.
+ * If dest_position is greater than or equal to the number of tabs
+ * of the destination notebook or negative, tab will be moved to the
+ * end of the tabs.
+ */
+void
+pluma_notebook_reorder_tab (PlumaNotebook *src,
+ PlumaTab *tab,
+ gint dest_position)
+{
+ gint old_position;
+
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (src));
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ old_position = gtk_notebook_page_num (GTK_NOTEBOOK (src),
+ GTK_WIDGET (tab));
+
+ if (old_position == dest_position)
+ return;
+
+ gtk_notebook_reorder_child (GTK_NOTEBOOK (src),
+ GTK_WIDGET (tab),
+ dest_position);
+
+ if (!src->priv->drag_in_progress)
+ {
+ g_signal_emit (G_OBJECT (src),
+ signals[TABS_REORDERED],
+ 0);
+ }
+}
+
+static void
+drag_start (PlumaNotebook *notebook,
+ guint32 time)
+{
+ notebook->priv->drag_in_progress = TRUE;
+
+ /* get a new cursor, if necessary */
+ /* FIXME multi-head */
+ if (cursor == NULL)
+ cursor = gdk_cursor_new (GDK_FLEUR);
+
+ /* grab the pointer */
+ gtk_grab_add (GTK_WIDGET (notebook));
+
+ /* FIXME multi-head */
+ if (!gdk_pointer_is_grabbed ())
+ {
+ gdk_pointer_grab (gtk_widget_get_window (GTK_WIDGET (notebook)),
+ FALSE,
+ GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
+ NULL,
+ cursor,
+ time);
+ }
+}
+
+static void
+drag_stop (PlumaNotebook *notebook)
+{
+ if (notebook->priv->drag_in_progress)
+ {
+ g_signal_emit (G_OBJECT (notebook),
+ signals[TABS_REORDERED],
+ 0);
+ }
+
+ notebook->priv->drag_in_progress = FALSE;
+ if (notebook->priv->motion_notify_handler_id != 0)
+ {
+ g_signal_handler_disconnect (G_OBJECT (notebook),
+ notebook->priv->motion_notify_handler_id);
+ notebook->priv->motion_notify_handler_id = 0;
+ }
+}
+
+/* This function is only called during dnd, we don't need to emit TABS_REORDERED
+ * here, instead we do it on drag_stop
+ */
+static void
+move_current_tab (PlumaNotebook *notebook,
+ gint dest_position)
+{
+ gint cur_page_num;
+
+ cur_page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+
+ if (dest_position != cur_page_num)
+ {
+ GtkWidget *cur_tab;
+
+ cur_tab = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook),
+ cur_page_num);
+
+ pluma_notebook_reorder_tab (PLUMA_NOTEBOOK (notebook),
+ PLUMA_TAB (cur_tab),
+ dest_position);
+ }
+}
+
+static gboolean
+motion_notify_cb (PlumaNotebook *notebook,
+ GdkEventMotion *event,
+ gpointer data)
+{
+ PlumaNotebook *dest;
+ gint page_num;
+ gint result;
+
+ if (notebook->priv->drag_in_progress == FALSE)
+ {
+ if (notebook->priv->tab_drag_and_drop_enabled == FALSE)
+ return FALSE;
+
+ if (gtk_drag_check_threshold (GTK_WIDGET (notebook),
+ notebook->priv->x_start,
+ notebook->priv->y_start,
+ event->x_root,
+ event->y_root))
+ {
+ drag_start (notebook, event->time);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ result = find_notebook_and_tab_at_pos ((gint)event->x_root,
+ (gint)event->y_root,
+ &dest,
+ &page_num);
+
+ if (result != NOT_IN_APP_WINDOWS)
+ {
+ if (dest != notebook)
+ {
+ move_current_tab_to_another_notebook (notebook,
+ dest,
+ event,
+ page_num);
+ }
+ else
+ {
+ g_return_val_if_fail (page_num >= -1, FALSE);
+ move_current_tab (notebook, page_num);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+move_current_tab_to_another_notebook (PlumaNotebook *src,
+ PlumaNotebook *dest,
+ GdkEventMotion *event,
+ gint dest_position)
+{
+ PlumaTab *tab;
+ gint cur_page;
+
+ /* This is getting tricky, the tab was dragged in a notebook
+ * in another window of the same app, we move the tab
+ * to that new notebook, and let this notebook handle the
+ * drag
+ */
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (dest));
+ g_return_if_fail (dest != src);
+
+ cur_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (src));
+ tab = PLUMA_TAB (gtk_notebook_get_nth_page (GTK_NOTEBOOK (src),
+ cur_page));
+
+ /* stop drag in origin window */
+ /* ungrab the pointer if it's grabbed */
+ drag_stop (src);
+ if (gdk_pointer_is_grabbed ())
+ {
+ gdk_pointer_ungrab (event->time);
+ }
+ gtk_grab_remove (GTK_WIDGET (src));
+
+ pluma_notebook_move_tab (src, dest, tab, dest_position);
+
+ /* start drag handling in dest notebook */
+ dest->priv->motion_notify_handler_id =
+ g_signal_connect (G_OBJECT (dest),
+ "motion-notify-event",
+ G_CALLBACK (motion_notify_cb),
+ NULL);
+
+ drag_start (dest, event->time);
+}
+
+static gboolean
+button_release_cb (PlumaNotebook *notebook,
+ GdkEventButton *event,
+ gpointer data)
+{
+ if (notebook->priv->drag_in_progress)
+ {
+ gint cur_page_num;
+ GtkWidget *cur_page;
+
+ cur_page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+ cur_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook),
+ cur_page_num);
+
+ /* CHECK: I don't follow the code here -- Paolo */
+ if (!is_in_notebook_window (notebook, event->x_root, event->y_root) &&
+ (gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)) > 1))
+ {
+ /* Tab was detached */
+ g_signal_emit (G_OBJECT (notebook),
+ signals[TAB_DETACHED],
+ 0,
+ cur_page);
+ }
+
+ /* ungrab the pointer if it's grabbed */
+ if (gdk_pointer_is_grabbed ())
+ {
+ gdk_pointer_ungrab (event->time);
+ }
+ gtk_grab_remove (GTK_WIDGET (notebook));
+ }
+
+ /* This must be called even if a drag isn't happening */
+ drag_stop (notebook);
+
+ return FALSE;
+}
+
+static gboolean
+button_press_cb (PlumaNotebook *notebook,
+ GdkEventButton *event,
+ gpointer data)
+{
+ gint tab_clicked;
+
+ if (notebook->priv->drag_in_progress)
+ return TRUE;
+
+ tab_clicked = find_tab_num_at_pos (notebook,
+ event->x_root,
+ event->y_root);
+
+ if ((event->button == 1) &&
+ (event->type == GDK_BUTTON_PRESS) &&
+ (tab_clicked >= 0))
+ {
+ notebook->priv->x_start = event->x_root;
+ notebook->priv->y_start = event->y_root;
+
+ notebook->priv->motion_notify_handler_id =
+ g_signal_connect (G_OBJECT (notebook),
+ "motion-notify-event",
+ G_CALLBACK (motion_notify_cb),
+ NULL);
+ }
+ else if ((event->type == GDK_BUTTON_PRESS) &&
+ (event->button == 3))
+ {
+ if (tab_clicked == -1)
+ {
+ // CHECK: do we really need it?
+
+ /* consume event, so that we don't pop up the context menu when
+ * the mouse if not over a tab label
+ */
+ return TRUE;
+ }
+ else
+ {
+ /* Switch to the page the mouse is over, but don't consume the event */
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook),
+ tab_clicked);
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * pluma_notebook_new:
+ *
+ * Creates a new #PlumaNotebook object.
+ *
+ * Returns: a new #PlumaNotebook
+ */
+GtkWidget *
+pluma_notebook_new (void)
+{
+ return GTK_WIDGET (g_object_new (PLUMA_TYPE_NOTEBOOK, NULL));
+}
+
+static void
+pluma_notebook_switch_page_cb (GtkNotebook *notebook,
+ GtkNotebookPage *page,
+ guint page_num,
+ gpointer data)
+{
+ PlumaNotebook *nb = PLUMA_NOTEBOOK (notebook);
+ GtkWidget *child;
+ PlumaView *view;
+
+ child = gtk_notebook_get_nth_page (notebook, page_num);
+
+ /* Remove the old page, we dont want to grow unnecessarily
+ * the list */
+ if (nb->priv->focused_pages)
+ {
+ nb->priv->focused_pages =
+ g_list_remove (nb->priv->focused_pages, child);
+ }
+
+ nb->priv->focused_pages = g_list_append (nb->priv->focused_pages,
+ child);
+
+ /* give focus to the view */
+ view = pluma_tab_get_view (PLUMA_TAB (child));
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+/*
+ * update_tabs_visibility: Hide tabs if there is only one tab
+ * and the pref is not set.
+ */
+static void
+update_tabs_visibility (PlumaNotebook *nb,
+ gboolean before_inserting)
+{
+ gboolean show_tabs;
+ guint num;
+
+ num = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
+
+ if (before_inserting) num++;
+
+ show_tabs = (nb->priv->always_show_tabs || num > 1);
+
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), show_tabs);
+}
+
+static void
+pluma_notebook_init (PlumaNotebook *notebook)
+{
+ notebook->priv = PLUMA_NOTEBOOK_GET_PRIVATE (notebook);
+
+ notebook->priv->close_buttons_sensitive = TRUE;
+ notebook->priv->tab_drag_and_drop_enabled = TRUE;
+
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
+
+ notebook->priv->always_show_tabs = TRUE;
+
+ g_signal_connect (notebook,
+ "button-press-event",
+ (GCallback)button_press_cb,
+ NULL);
+ g_signal_connect (notebook,
+ "button-release-event",
+ (GCallback)button_release_cb,
+ NULL);
+ gtk_widget_add_events (GTK_WIDGET (notebook),
+ GDK_BUTTON1_MOTION_MASK);
+
+ g_signal_connect_after (G_OBJECT (notebook),
+ "switch_page",
+ G_CALLBACK (pluma_notebook_switch_page_cb),
+ NULL);
+}
+
+static void
+pluma_notebook_finalize (GObject *object)
+{
+ PlumaNotebook *notebook = PLUMA_NOTEBOOK (object);
+
+ g_list_free (notebook->priv->focused_pages);
+
+ G_OBJECT_CLASS (pluma_notebook_parent_class)->finalize (object);
+}
+
+/*
+ * We need to override this because when we don't show the tabs, like in
+ * fullscreen we need to have wrap around too
+ */
+static gboolean
+pluma_notebook_change_current_page (GtkNotebook *notebook,
+ gint offset)
+{
+ gboolean wrap_around;
+ gint current;
+
+ current = gtk_notebook_get_current_page (notebook);
+
+ if (current != -1)
+ {
+ current = current + offset;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+ "gtk-keynav-wrap-around", &wrap_around,
+ NULL);
+
+ if (wrap_around)
+ {
+ if (current < 0)
+ {
+ current = gtk_notebook_get_n_pages (notebook) - 1;
+ }
+ else if (current >= gtk_notebook_get_n_pages (notebook))
+ {
+ current = 0;
+ }
+ }
+
+ gtk_notebook_set_current_page (notebook, current);
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (notebook));
+ }
+
+ return TRUE;
+}
+
+static void
+close_button_clicked_cb (PlumaTabLabel *tab_label, PlumaNotebook *notebook)
+{
+ PlumaTab *tab;
+
+ tab = pluma_tab_label_get_tab (tab_label);
+ g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, tab);
+}
+
+static GtkWidget *
+create_tab_label (PlumaNotebook *nb,
+ PlumaTab *tab)
+{
+ GtkWidget *tab_label;
+
+ tab_label = pluma_tab_label_new (tab);
+
+ g_signal_connect (tab_label,
+ "close-clicked",
+ G_CALLBACK (close_button_clicked_cb),
+ nb);
+
+ g_object_set_data (G_OBJECT (tab), "tab-label", tab_label);
+
+ return tab_label;
+}
+
+static void
+remove_tab_label (PlumaNotebook *nb,
+ PlumaTab *tab)
+{
+ GtkWidget *tab_label;
+
+ tab_label = pluma_tab_label_new (tab);
+
+ g_signal_handlers_disconnect_by_func (tab_label,
+ G_CALLBACK (close_button_clicked_cb),
+ nb);
+
+ g_object_set_data (G_OBJECT (tab), "tab-label", NULL);
+}
+
+static GtkWidget *
+get_tab_label (PlumaTab *tab)
+{
+ GtkWidget *tab_label;
+
+ tab_label = GTK_WIDGET (g_object_get_data (G_OBJECT (tab), "tab-label"));
+ g_return_val_if_fail (tab_label != NULL, NULL);
+
+ return tab_label;
+}
+
+/**
+ * pluma_notebook_set_always_show_tabs:
+ * @nb: a #PlumaNotebook
+ * @show_tabs: %TRUE to always show the tabs
+ *
+ * Sets the visibility of the tabs in the @nb.
+ */
+void
+pluma_notebook_set_always_show_tabs (PlumaNotebook *nb,
+ gboolean show_tabs)
+{
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (nb));
+
+ nb->priv->always_show_tabs = (show_tabs != FALSE);
+
+ update_tabs_visibility (nb, FALSE);
+}
+
+/**
+ * pluma_notebook_add_tab:
+ * @nb: a #PlumaNotebook
+ * @tab: a #PlumaTab
+ * @position: the position where the @tab should be added
+ * @jump_to: %TRUE to set the @tab as active
+ *
+ * Adds the specified @tab to the @nb.
+ */
+void
+pluma_notebook_add_tab (PlumaNotebook *nb,
+ PlumaTab *tab,
+ gint position,
+ gboolean jump_to)
+{
+ GtkWidget *tab_label;
+
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (nb));
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ tab_label = create_tab_label (nb, tab);
+ gtk_notebook_insert_page (GTK_NOTEBOOK (nb),
+ GTK_WIDGET (tab),
+ tab_label,
+ position);
+ update_tabs_visibility (nb, TRUE);
+
+ g_signal_emit (G_OBJECT (nb), signals[TAB_ADDED], 0, tab);
+
+ /* The signal handler may have reordered the tabs */
+ position = gtk_notebook_page_num (GTK_NOTEBOOK (nb),
+ GTK_WIDGET (tab));
+
+ if (jump_to)
+ {
+ PlumaView *view;
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (nb), position);
+ g_object_set_data (G_OBJECT (tab),
+ "jump_to",
+ GINT_TO_POINTER (jump_to));
+ view = pluma_tab_get_view (tab);
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+ }
+}
+
+static void
+smart_tab_switching_on_closure (PlumaNotebook *nb,
+ PlumaTab *tab)
+{
+ gboolean jump_to;
+
+ jump_to = GPOINTER_TO_INT (g_object_get_data
+ (G_OBJECT (tab), "jump_to"));
+
+ if (!jump_to || !nb->priv->focused_pages)
+ {
+ gtk_notebook_next_page (GTK_NOTEBOOK (nb));
+ }
+ else
+ {
+ GList *l;
+ GtkWidget *child;
+ int page_num;
+
+ /* activate the last focused tab */
+ l = g_list_last (nb->priv->focused_pages);
+ child = GTK_WIDGET (l->data);
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (nb),
+ child);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (nb),
+ page_num);
+ }
+}
+
+static void
+remove_tab (PlumaTab *tab,
+ PlumaNotebook *nb)
+{
+ gint position;
+
+ position = gtk_notebook_page_num (GTK_NOTEBOOK (nb), GTK_WIDGET (tab));
+
+ /* we ref the tab so that it's still alive while the tabs_removed
+ * signal is processed.
+ */
+ g_object_ref (tab);
+
+ remove_tab_label (nb, tab);
+ gtk_notebook_remove_page (GTK_NOTEBOOK (nb), position);
+ update_tabs_visibility (nb, FALSE);
+
+ g_signal_emit (G_OBJECT (nb), signals[TAB_REMOVED], 0, tab);
+
+ g_object_unref (tab);
+}
+
+/**
+ * pluma_notebook_remove_tab:
+ * @nb: a #PlumaNotebook
+ * @tab: a #PlumaTab
+ *
+ * Removes @tab from @nb.
+ */
+void
+pluma_notebook_remove_tab (PlumaNotebook *nb,
+ PlumaTab *tab)
+{
+ gint position, curr;
+
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (nb));
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ /* Remove the page from the focused pages list */
+ nb->priv->focused_pages = g_list_remove (nb->priv->focused_pages,
+ tab);
+
+ position = gtk_notebook_page_num (GTK_NOTEBOOK (nb), GTK_WIDGET (tab));
+ curr = gtk_notebook_get_current_page (GTK_NOTEBOOK (nb));
+
+ if (position == curr)
+ {
+ smart_tab_switching_on_closure (nb, tab);
+ }
+
+ remove_tab (tab, nb);
+}
+
+/**
+ * pluma_notebook_remove_all_tabs:
+ * @nb: a #PlumaNotebook
+ *
+ * Removes all #PlumaTab from @nb.
+ */
+void
+pluma_notebook_remove_all_tabs (PlumaNotebook *nb)
+{
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (nb));
+
+ g_list_free (nb->priv->focused_pages);
+ nb->priv->focused_pages = NULL;
+
+ gtk_container_foreach (GTK_CONTAINER (nb),
+ (GtkCallback)remove_tab,
+ nb);
+}
+
+static void
+set_close_buttons_sensitivity (PlumaTab *tab,
+ PlumaNotebook *nb)
+{
+ GtkWidget *tab_label;
+
+ tab_label = get_tab_label (tab);
+
+ pluma_tab_label_set_close_button_sensitive (PLUMA_TAB_LABEL (tab_label),
+ nb->priv->close_buttons_sensitive);
+}
+
+/**
+ * pluma_notebook_set_close_buttons_sensitive:
+ * @nb: a #PlumaNotebook
+ * @sensitive: %TRUE to make the buttons sensitive
+ *
+ * Sets whether the close buttons in the tabs of @nb are sensitive.
+ */
+void
+pluma_notebook_set_close_buttons_sensitive (PlumaNotebook *nb,
+ gboolean sensitive)
+{
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (nb));
+
+ sensitive = (sensitive != FALSE);
+
+ if (sensitive == nb->priv->close_buttons_sensitive)
+ return;
+
+ nb->priv->close_buttons_sensitive = sensitive;
+
+ gtk_container_foreach (GTK_CONTAINER (nb),
+ (GtkCallback)set_close_buttons_sensitivity,
+ nb);
+}
+
+/**
+ * pluma_notebook_get_close_buttons_sensitive:
+ * @nb: a #PlumaNotebook
+ *
+ * Whether the close buttons are sensitive.
+ *
+ * Returns: %TRUE if the close buttons are sensitive
+ */
+gboolean
+pluma_notebook_get_close_buttons_sensitive (PlumaNotebook *nb)
+{
+ g_return_val_if_fail (PLUMA_IS_NOTEBOOK (nb), TRUE);
+
+ return nb->priv->close_buttons_sensitive;
+}
+
+/**
+ * pluma_notebook_set_tab_drag_and_drop_enabled:
+ * @nb: a #PlumaNotebook
+ * @enable: %TRUE to enable the drag and drop
+ *
+ * Sets whether drag and drop of tabs in the @nb is enabled.
+ */
+void
+pluma_notebook_set_tab_drag_and_drop_enabled (PlumaNotebook *nb,
+ gboolean enable)
+{
+ g_return_if_fail (PLUMA_IS_NOTEBOOK (nb));
+
+ enable = (enable != FALSE);
+
+ if (enable == nb->priv->tab_drag_and_drop_enabled)
+ return;
+
+ nb->priv->tab_drag_and_drop_enabled = enable;
+}
+
+/**
+ * pluma_notebook_get_tab_drag_and_drop_enabled:
+ * @nb: a #PlumaNotebook
+ *
+ * Whether the drag and drop is enabled in the @nb.
+ *
+ * Returns: %TRUE if the drag and drop is enabled.
+ */
+gboolean
+pluma_notebook_get_tab_drag_and_drop_enabled (PlumaNotebook *nb)
+{
+ g_return_val_if_fail (PLUMA_IS_NOTEBOOK (nb), TRUE);
+
+ return nb->priv->tab_drag_and_drop_enabled;
+}
+
diff --git a/pluma/pluma-notebook.h b/pluma/pluma-notebook.h
new file mode 100755
index 00000000..e90d1fcf
--- /dev/null
+++ b/pluma/pluma-notebook.h
@@ -0,0 +1,143 @@
+/*
+ * pluma-notebook.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+/* This file is a modified version of the epiphany file ephy-notebook.h
+ * Here the relevant copyright:
+ *
+ * Copyright (C) 2002 Christophe Fergeau
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ * Copyright (C) 2003, 2004 Christian Persch
+ *
+ */
+
+#ifndef PLUMA_NOTEBOOK_H
+#define PLUMA_NOTEBOOK_H
+
+#include <pluma/pluma-tab.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_NOTEBOOK (pluma_notebook_get_type ())
+#define PLUMA_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), PLUMA_TYPE_NOTEBOOK, PlumaNotebook))
+#define PLUMA_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), PLUMA_TYPE_NOTEBOOK, PlumaNotebookClass))
+#define PLUMA_IS_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), PLUMA_TYPE_NOTEBOOK))
+#define PLUMA_IS_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), PLUMA_TYPE_NOTEBOOK))
+#define PLUMA_NOTEBOOK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), PLUMA_TYPE_NOTEBOOK, PlumaNotebookClass))
+
+/* Private structure type */
+typedef struct _PlumaNotebookPrivate PlumaNotebookPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaNotebook PlumaNotebook;
+
+struct _PlumaNotebook
+{
+ GtkNotebook notebook;
+
+ /*< private >*/
+ PlumaNotebookPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaNotebookClass PlumaNotebookClass;
+
+struct _PlumaNotebookClass
+{
+ GtkNotebookClass parent_class;
+
+ /* Signals */
+ void (* tab_added) (PlumaNotebook *notebook,
+ PlumaTab *tab);
+ void (* tab_removed) (PlumaNotebook *notebook,
+ PlumaTab *tab);
+ void (* tab_detached) (PlumaNotebook *notebook,
+ PlumaTab *tab);
+ void (* tabs_reordered) (PlumaNotebook *notebook);
+ void (* tab_close_request)
+ (PlumaNotebook *notebook,
+ PlumaTab *tab);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_notebook_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_notebook_new (void);
+
+void pluma_notebook_add_tab (PlumaNotebook *nb,
+ PlumaTab *tab,
+ gint position,
+ gboolean jump_to);
+
+void pluma_notebook_remove_tab (PlumaNotebook *nb,
+ PlumaTab *tab);
+
+void pluma_notebook_remove_all_tabs (PlumaNotebook *nb);
+
+void pluma_notebook_reorder_tab (PlumaNotebook *src,
+ PlumaTab *tab,
+ gint dest_position);
+
+void pluma_notebook_move_tab (PlumaNotebook *src,
+ PlumaNotebook *dest,
+ PlumaTab *tab,
+ gint dest_position);
+
+/* FIXME: do we really need this function ? */
+void pluma_notebook_set_always_show_tabs
+ (PlumaNotebook *nb,
+ gboolean show_tabs);
+
+void pluma_notebook_set_close_buttons_sensitive
+ (PlumaNotebook *nb,
+ gboolean sensitive);
+
+gboolean pluma_notebook_get_close_buttons_sensitive
+ (PlumaNotebook *nb);
+
+void pluma_notebook_set_tab_drag_and_drop_enabled
+ (PlumaNotebook *nb,
+ gboolean enable);
+
+gboolean pluma_notebook_get_tab_drag_and_drop_enabled
+ (PlumaNotebook *nb);
+
+G_END_DECLS
+
+#endif /* PLUMA_NOTEBOOK_H */
diff --git a/pluma/pluma-object-module.c b/pluma/pluma-object-module.c
new file mode 100755
index 00000000..3265de2c
--- /dev/null
+++ b/pluma/pluma-object-module.c
@@ -0,0 +1,343 @@
+/*
+ * pluma-object-module.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+/* This is a modified version of ephy-module.c from Epiphany source code.
+ * Here the original copyright assignment:
+ *
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ * Copyright (C) 2003, 2004 Christian Persch
+ *
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: pluma-module.c 6314 2008-06-05 12:57:53Z pborelli $
+ */
+
+#include "config.h"
+
+#include "pluma-object-module.h"
+#include "pluma-debug.h"
+
+typedef GType (*PlumaObjectModuleRegisterFunc) (GTypeModule *);
+
+enum {
+ PROP_0,
+ PROP_MODULE_NAME,
+ PROP_PATH,
+ PROP_TYPE_REGISTRATION,
+ PROP_RESIDENT
+};
+
+struct _PlumaObjectModulePrivate
+{
+ GModule *library;
+
+ GType type;
+ gchar *path;
+ gchar *module_name;
+ gchar *type_registration;
+
+ gboolean resident;
+};
+
+G_DEFINE_TYPE (PlumaObjectModule, pluma_object_module, G_TYPE_TYPE_MODULE);
+
+static gboolean
+pluma_object_module_load (GTypeModule *gmodule)
+{
+ PlumaObjectModule *module = PLUMA_OBJECT_MODULE (gmodule);
+ PlumaObjectModuleRegisterFunc register_func;
+ gchar *path;
+
+ pluma_debug_message (DEBUG_PLUGINS, "Loading %s module from %s",
+ module->priv->module_name, module->priv->path);
+
+ path = g_module_build_path (module->priv->path, module->priv->module_name);
+ g_return_val_if_fail (path != NULL, FALSE);
+ pluma_debug_message (DEBUG_PLUGINS, "Module filename: %s", path);
+
+ module->priv->library = g_module_open (path,
+ G_MODULE_BIND_LAZY);
+ g_free (path);
+
+ if (module->priv->library == NULL)
+ {
+ g_warning ("%s: %s", module->priv->module_name, g_module_error());
+
+ return FALSE;
+ }
+
+ /* extract symbols from the lib */
+ if (!g_module_symbol (module->priv->library, module->priv->type_registration,
+ (void *) &register_func))
+ {
+ g_warning ("%s: %s", module->priv->module_name, g_module_error());
+ g_module_close (module->priv->library);
+
+ return FALSE;
+ }
+
+ /* symbol can still be NULL even though g_module_symbol
+ * returned TRUE */
+ if (register_func == NULL)
+ {
+ g_warning ("Symbol '%s' should not be NULL", module->priv->type_registration);
+ g_module_close (module->priv->library);
+
+ return FALSE;
+ }
+
+ module->priv->type = register_func (gmodule);
+
+ if (module->priv->type == 0)
+ {
+ g_warning ("Invalid object contained by module %s", module->priv->module_name);
+ return FALSE;
+ }
+
+ if (module->priv->resident)
+ {
+ g_module_make_resident (module->priv->library);
+ }
+
+ return TRUE;
+}
+
+static void
+pluma_object_module_unload (GTypeModule *gmodule)
+{
+ PlumaObjectModule *module = PLUMA_OBJECT_MODULE (gmodule);
+
+ pluma_debug_message (DEBUG_PLUGINS, "Unloading %s", module->priv->path);
+
+ g_module_close (module->priv->library);
+
+ module->priv->library = NULL;
+ module->priv->type = 0;
+}
+
+static void
+pluma_object_module_init (PlumaObjectModule *module)
+{
+ pluma_debug_message (DEBUG_PLUGINS, "PlumaObjectModule %p initialising", module);
+
+ module->priv = G_TYPE_INSTANCE_GET_PRIVATE (module,
+ PLUMA_TYPE_OBJECT_MODULE,
+ PlumaObjectModulePrivate);
+}
+
+static void
+pluma_object_module_finalize (GObject *object)
+{
+ PlumaObjectModule *module = PLUMA_OBJECT_MODULE (object);
+
+ pluma_debug_message (DEBUG_PLUGINS, "PlumaObjectModule %p finalising", module);
+
+ g_free (module->priv->path);
+ g_free (module->priv->module_name);
+ g_free (module->priv->type_registration);
+
+ G_OBJECT_CLASS (pluma_object_module_parent_class)->finalize (object);
+}
+
+static void
+pluma_object_module_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaObjectModule *module = PLUMA_OBJECT_MODULE (object);
+
+ switch (prop_id)
+ {
+ case PROP_MODULE_NAME:
+ g_value_set_string (value, module->priv->module_name);
+ break;
+ case PROP_PATH:
+ g_value_set_string (value, module->priv->path);
+ break;
+ case PROP_TYPE_REGISTRATION:
+ g_value_set_string (value, module->priv->type_registration);
+ break;
+ case PROP_RESIDENT:
+ g_value_set_boolean (value, module->priv->resident);
+ break;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+pluma_object_module_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaObjectModule *module = PLUMA_OBJECT_MODULE (object);
+
+ switch (prop_id)
+ {
+ case PROP_MODULE_NAME:
+ module->priv->module_name = g_value_dup_string (value);
+ g_type_module_set_name (G_TYPE_MODULE (object),
+ module->priv->module_name);
+ break;
+ case PROP_PATH:
+ module->priv->path = g_value_dup_string (value);
+ break;
+ case PROP_TYPE_REGISTRATION:
+ module->priv->type_registration = g_value_dup_string (value);
+ break;
+ case PROP_RESIDENT:
+ module->priv->resident = g_value_get_boolean (value);
+ break;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+pluma_object_module_class_init (PlumaObjectModuleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (klass);
+
+ object_class->set_property = pluma_object_module_set_property;
+ object_class->get_property = pluma_object_module_get_property;
+ object_class->finalize = pluma_object_module_finalize;
+
+ module_class->load = pluma_object_module_load;
+ module_class->unload = pluma_object_module_unload;
+
+ g_object_class_install_property (object_class,
+ PROP_MODULE_NAME,
+ g_param_spec_string ("module-name",
+ "Module Name",
+ "The module to load for this object",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_PATH,
+ g_param_spec_string ("path",
+ "Path",
+ "The path to use when loading this module",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_TYPE_REGISTRATION,
+ g_param_spec_string ("type-registration",
+ "Type Registration",
+ "The name of the type registration function",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_RESIDENT,
+ g_param_spec_boolean ("resident",
+ "Resident",
+ "Whether the module is resident",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (klass, sizeof (PlumaObjectModulePrivate));
+}
+
+PlumaObjectModule *
+pluma_object_module_new (const gchar *module_name,
+ const gchar *path,
+ const gchar *type_registration,
+ gboolean resident)
+{
+ return (PlumaObjectModule *)g_object_new (PLUMA_TYPE_OBJECT_MODULE,
+ "module-name",
+ module_name,
+ "path",
+ path,
+ "type-registration",
+ type_registration,
+ "resident",
+ resident,
+ NULL);
+}
+
+GObject *
+pluma_object_module_new_object (PlumaObjectModule *module,
+ const gchar *first_property_name,
+ ...)
+{
+ va_list var_args;
+ GObject *result;
+
+ g_return_val_if_fail (module->priv->type != 0, NULL);
+
+ pluma_debug_message (DEBUG_PLUGINS, "Creating object of type %s",
+ g_type_name (module->priv->type));
+
+ va_start (var_args, first_property_name);
+ result = g_object_new_valist (module->priv->type, first_property_name, var_args);
+ va_end (var_args);
+
+ return result;
+}
+
+const gchar *
+pluma_object_module_get_path (PlumaObjectModule *module)
+{
+ g_return_val_if_fail (PLUMA_IS_OBJECT_MODULE (module), NULL);
+
+ return module->priv->path;
+}
+
+const gchar *
+pluma_object_module_get_module_name (PlumaObjectModule *module)
+{
+ g_return_val_if_fail (PLUMA_IS_OBJECT_MODULE (module), NULL);
+
+ return module->priv->module_name;
+}
+
+const gchar *
+pluma_object_module_get_type_registration (PlumaObjectModule *module)
+{
+ g_return_val_if_fail (PLUMA_IS_OBJECT_MODULE (module), NULL);
+
+ return module->priv->type_registration;
+}
+
+GType
+pluma_object_module_get_object_type (PlumaObjectModule *module)
+{
+ g_return_val_if_fail (PLUMA_IS_OBJECT_MODULE (module), 0);
+
+ return module->priv->type;
+}
diff --git a/pluma/pluma-object-module.h b/pluma/pluma-object-module.h
new file mode 100755
index 00000000..d197b008
--- /dev/null
+++ b/pluma/pluma-object-module.h
@@ -0,0 +1,94 @@
+/*
+ * pluma-object-module.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+/* This is a modified version of pluma-module.h from Epiphany source code.
+ * Here the original copyright assignment:
+ *
+ * Copyright (C) 2003 Marco Pesenti Gritti
+ * Copyright (C) 2003, 2004 Christian Persch
+ *
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: pluma-module.h 6263 2008-05-05 10:52:10Z sfre $
+ */
+
+#ifndef __PLUMA_OBJECT_MODULE_H__
+#define __PLUMA_OBJECT_MODULE_H__
+
+#include <glib-object.h>
+#include <gmodule.h>
+#include <stdarg.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_OBJECT_MODULE (pluma_object_module_get_type ())
+#define PLUMA_OBJECT_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_OBJECT_MODULE, PlumaObjectModule))
+#define PLUMA_OBJECT_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_OBJECT_MODULE, PlumaObjectModuleClass))
+#define PLUMA_IS_OBJECT_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_OBJECT_MODULE))
+#define PLUMA_IS_OBJECT_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_OBJECT_MODULE))
+#define PLUMA_OBJECT_MODULE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_OBJECT_MODULE, PlumaObjectModuleClass))
+
+typedef struct _PlumaObjectModule PlumaObjectModule;
+typedef struct _PlumaObjectModulePrivate PlumaObjectModulePrivate;
+
+struct _PlumaObjectModule
+{
+ GTypeModule parent;
+
+ PlumaObjectModulePrivate *priv;
+};
+
+typedef struct _PlumaObjectModuleClass PlumaObjectModuleClass;
+
+struct _PlumaObjectModuleClass
+{
+ GTypeModuleClass parent_class;
+
+ /* Virtual class methods */
+ void (* garbage_collect) ();
+};
+
+GType pluma_object_module_get_type (void) G_GNUC_CONST;
+
+PlumaObjectModule *pluma_object_module_new (const gchar *module_name,
+ const gchar *path,
+ const gchar *type_registration,
+ gboolean resident);
+
+GObject *pluma_object_module_new_object (PlumaObjectModule *module,
+ const gchar *first_property_name,
+ ...);
+
+GType pluma_object_module_get_object_type (PlumaObjectModule *module);
+const gchar *pluma_object_module_get_path (PlumaObjectModule *module);
+const gchar *pluma_object_module_get_module_name (PlumaObjectModule *module);
+const gchar *pluma_object_module_get_type_registration (PlumaObjectModule *module);
+
+G_END_DECLS
+
+#endif
diff --git a/pluma/pluma-panel.c b/pluma/pluma-panel.c
new file mode 100755
index 00000000..1e02f6ca
--- /dev/null
+++ b/pluma/pluma-panel.c
@@ -0,0 +1,950 @@
+/*
+ * pluma-panel.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#include "pluma-panel.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "pluma-close-button.h"
+#include "pluma-window.h"
+#include "pluma-debug.h"
+
+#define PANEL_ITEM_KEY "PlumaPanelItemKey"
+
+#define PLUMA_PANEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_PANEL, PlumaPanelPrivate))
+
+struct _PlumaPanelPrivate
+{
+ GtkOrientation orientation;
+
+ /* Title bar (vertical panel only) */
+ GtkWidget *title_image;
+ GtkWidget *title_label;
+
+ /* Notebook */
+ GtkWidget *notebook;
+};
+
+typedef struct _PlumaPanelItem PlumaPanelItem;
+
+struct _PlumaPanelItem
+{
+ gchar *name;
+ GtkWidget *icon;
+};
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_ORIENTATION
+};
+
+/* Signals */
+enum {
+ ITEM_ADDED,
+ ITEM_REMOVED,
+ CLOSE,
+ FOCUS_DOCUMENT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static GObject *pluma_panel_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties);
+
+
+G_DEFINE_TYPE(PlumaPanel, pluma_panel, GTK_TYPE_VBOX)
+
+static void
+pluma_panel_finalize (GObject *obj)
+{
+ if (G_OBJECT_CLASS (pluma_panel_parent_class)->finalize)
+ (*G_OBJECT_CLASS (pluma_panel_parent_class)->finalize) (obj);
+}
+
+static void
+pluma_panel_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaPanel *panel = PLUMA_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum(value, panel->priv->orientation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_panel_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaPanel *panel = PLUMA_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ panel->priv->orientation = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_panel_close (PlumaPanel *panel)
+{
+ gtk_widget_hide (GTK_WIDGET (panel));
+}
+
+static void
+pluma_panel_focus_document (PlumaPanel *panel)
+{
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (panel));
+#if !GTK_CHECK_VERSION (2, 18, 0)
+ if (GTK_WIDGET_TOPLEVEL (toplevel) && PLUMA_IS_WINDOW (toplevel))
+#else
+ if (gtk_widget_is_toplevel (toplevel) && PLUMA_IS_WINDOW (toplevel))
+#endif
+ {
+ PlumaView *view;
+
+ view = pluma_window_get_active_view (PLUMA_WINDOW (toplevel));
+ if (view != NULL)
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+ }
+}
+
+static void
+pluma_panel_grab_focus (GtkWidget *w)
+{
+ gint n;
+ GtkWidget *tab;
+ PlumaPanel *panel = PLUMA_PANEL (w);
+
+ n = gtk_notebook_get_current_page (GTK_NOTEBOOK (panel->priv->notebook));
+ if (n == -1)
+ return;
+
+ tab = gtk_notebook_get_nth_page (GTK_NOTEBOOK (panel->priv->notebook),
+ n);
+ g_return_if_fail (tab != NULL);
+
+ gtk_widget_grab_focus (tab);
+}
+
+static void
+pluma_panel_class_init (PlumaPanelClass *klass)
+{
+ GtkBindingSet *binding_set;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (PlumaPanelPrivate));
+
+ object_class->constructor = pluma_panel_constructor;
+ object_class->finalize = pluma_panel_finalize;
+ object_class->get_property = pluma_panel_get_property;
+ object_class->set_property = pluma_panel_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ORIENTATION,
+ g_param_spec_enum ("orientation",
+ "Orientation",
+ "The panel's orientation",
+ GTK_TYPE_ORIENTATION,
+ GTK_ORIENTATION_VERTICAL,
+ G_PARAM_WRITABLE |
+ G_PARAM_READABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ widget_class->grab_focus = pluma_panel_grab_focus;
+
+ klass->close = pluma_panel_close;
+ klass->focus_document = pluma_panel_focus_document;
+
+ signals[ITEM_ADDED] =
+ g_signal_new ("item_added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaPanelClass, item_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ GTK_TYPE_WIDGET);
+ signals[ITEM_REMOVED] =
+ g_signal_new ("item_removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaPanelClass, item_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ GTK_TYPE_WIDGET);
+
+ /* Keybinding signals */
+ signals[CLOSE] =
+ g_signal_new ("close",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaPanelClass, close),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[FOCUS_DOCUMENT] =
+ g_signal_new ("focus_document",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaPanelClass, focus_document),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_Escape,
+ 0,
+ "close",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_Return,
+ GDK_CONTROL_MASK,
+ "focus_document",
+ 0);
+}
+
+/* This is ugly, since it supports only known
+ * storage types of GtkImage, otherwise fall back
+ * to the empty icon.
+ * See http://bugzilla.gnome.org/show_bug.cgi?id=317520.
+ */
+static void
+set_gtk_image_from_gtk_image (GtkImage *image,
+ GtkImage *source)
+{
+ switch (gtk_image_get_storage_type (source))
+ {
+ case GTK_IMAGE_EMPTY:
+ gtk_image_clear (image);
+ break;
+ case GTK_IMAGE_PIXMAP:
+ {
+ GdkPixmap *pm;
+ GdkBitmap *bm;
+
+ gtk_image_get_pixmap (source, &pm, &bm);
+ gtk_image_set_from_pixmap (image, pm, bm);
+ }
+ break;
+ case GTK_IMAGE_IMAGE:
+ {
+ GdkImage *i;
+ GdkBitmap *bm;
+
+ gtk_image_get_image (source, &i, &bm);
+ gtk_image_set_from_image (image, i, bm);
+ }
+ break;
+ case GTK_IMAGE_PIXBUF:
+ {
+ GdkPixbuf *pb;
+
+ pb = gtk_image_get_pixbuf (source);
+ gtk_image_set_from_pixbuf (image, pb);
+ }
+ break;
+ case GTK_IMAGE_STOCK:
+ {
+ gchar *s_id;
+ GtkIconSize s;
+
+ gtk_image_get_stock (source, &s_id, &s);
+ gtk_image_set_from_stock (image, s_id, s);
+ }
+ break;
+ case GTK_IMAGE_ICON_SET:
+ {
+ GtkIconSet *is;
+ GtkIconSize s;
+
+ gtk_image_get_icon_set (source, &is, &s);
+ gtk_image_set_from_icon_set (image, is, s);
+ }
+ break;
+ case GTK_IMAGE_ANIMATION:
+ {
+ GdkPixbufAnimation *a;
+
+ a = gtk_image_get_animation (source);
+ gtk_image_set_from_animation (image, a);
+ }
+ break;
+ case GTK_IMAGE_ICON_NAME:
+ {
+ const gchar *n;
+ GtkIconSize s;
+
+ gtk_image_get_icon_name (source, &n, &s);
+ gtk_image_set_from_icon_name (image, n, s);
+ }
+ break;
+ default:
+ gtk_image_set_from_stock (image,
+ GTK_STOCK_FILE,
+ GTK_ICON_SIZE_MENU);
+ }
+}
+
+static void
+sync_title (PlumaPanel *panel,
+ PlumaPanelItem *item)
+{
+ if (panel->priv->orientation != GTK_ORIENTATION_VERTICAL)
+ return;
+
+ if (item != NULL)
+ {
+ gtk_label_set_text (GTK_LABEL (panel->priv->title_label),
+ item->name);
+
+ set_gtk_image_from_gtk_image (GTK_IMAGE (panel->priv->title_image),
+ GTK_IMAGE (item->icon));
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (panel->priv->title_label),
+ _("Empty"));
+
+ gtk_image_set_from_stock (GTK_IMAGE (panel->priv->title_image),
+ GTK_STOCK_FILE,
+ GTK_ICON_SIZE_MENU);
+ }
+}
+
+static void
+notebook_page_changed (GtkNotebook *notebook,
+ GtkNotebookPage *page,
+ guint page_num,
+ PlumaPanel *panel)
+{
+ GtkWidget *item;
+ PlumaPanelItem *data;
+
+ item = gtk_notebook_get_nth_page (notebook, page_num);
+ g_return_if_fail (item != NULL);
+
+ data = (PlumaPanelItem *)g_object_get_data (G_OBJECT (item),
+ PANEL_ITEM_KEY);
+ g_return_if_fail (data != NULL);
+
+ sync_title (panel, data);
+}
+
+static void
+panel_show (PlumaPanel *panel,
+ gpointer user_data)
+{
+ gint page;
+ GtkNotebook *nb;
+
+ nb = GTK_NOTEBOOK (panel->priv->notebook);
+
+ page = gtk_notebook_get_current_page (nb);
+
+ if (page != -1)
+ notebook_page_changed (nb, NULL, page, panel);
+}
+
+static void
+pluma_panel_init (PlumaPanel *panel)
+{
+ panel->priv = PLUMA_PANEL_GET_PRIVATE (panel);
+}
+
+static void
+close_button_clicked_cb (GtkWidget *widget,
+ GtkWidget *panel)
+{
+ gtk_widget_hide (panel);
+}
+
+static GtkWidget *
+create_close_button (PlumaPanel *panel)
+{
+ GtkWidget *button;
+
+ button = pluma_close_button_new ();
+
+ gtk_widget_set_tooltip_text (button, _("Hide panel"));
+
+ g_signal_connect (button,
+ "clicked",
+ G_CALLBACK (close_button_clicked_cb),
+ panel);
+
+ return button;
+}
+
+static void
+build_notebook_for_panel (PlumaPanel *panel)
+{
+ /* Create the panel notebook */
+ panel->priv->notebook = gtk_notebook_new ();
+
+ gtk_notebook_set_tab_pos (GTK_NOTEBOOK (panel->priv->notebook),
+ GTK_POS_BOTTOM);
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (panel->priv->notebook),
+ TRUE);
+ gtk_notebook_popup_enable (GTK_NOTEBOOK (panel->priv->notebook));
+
+ gtk_widget_show (GTK_WIDGET (panel->priv->notebook));
+
+ g_signal_connect (panel->priv->notebook,
+ "switch-page",
+ G_CALLBACK (notebook_page_changed),
+ panel);
+}
+
+static void
+build_horizontal_panel (PlumaPanel *panel)
+{
+ GtkWidget *box;
+ GtkWidget *sidebar;
+ GtkWidget *close_button;
+
+ box = gtk_hbox_new(FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (box),
+ panel->priv->notebook,
+ TRUE,
+ TRUE,
+ 0);
+
+ /* Toolbar, close button and first separator */
+ sidebar = gtk_vbox_new(FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (sidebar), 4);
+
+ gtk_box_pack_start (GTK_BOX (box),
+ sidebar,
+ FALSE,
+ FALSE,
+ 0);
+
+ close_button = create_close_button (panel);
+
+ gtk_box_pack_start (GTK_BOX (sidebar),
+ close_button,
+ FALSE,
+ FALSE,
+ 0);
+
+ gtk_widget_show_all (box);
+
+ gtk_box_pack_start (GTK_BOX (panel),
+ box,
+ TRUE,
+ TRUE,
+ 0);
+}
+
+static void
+build_vertical_panel (PlumaPanel *panel)
+{
+ GtkWidget *close_button;
+ GtkWidget *title_hbox;
+ GtkWidget *icon_name_hbox;
+ GtkWidget *dummy_label;
+
+ /* Create title hbox */
+ title_hbox = gtk_hbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (title_hbox), 5);
+
+ gtk_box_pack_start (GTK_BOX (panel), title_hbox, FALSE, FALSE, 0);
+
+ icon_name_hbox = gtk_hbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (title_hbox),
+ icon_name_hbox,
+ TRUE,
+ TRUE,
+ 0);
+
+ panel->priv->title_image =
+ gtk_image_new_from_stock (GTK_STOCK_FILE,
+ GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (icon_name_hbox),
+ panel->priv->title_image,
+ FALSE,
+ TRUE,
+ 0);
+
+ dummy_label = gtk_label_new (" ");
+
+ gtk_box_pack_start (GTK_BOX (icon_name_hbox),
+ dummy_label,
+ FALSE,
+ FALSE,
+ 0);
+
+ panel->priv->title_label = gtk_label_new (_("Empty"));
+ gtk_misc_set_alignment (GTK_MISC (panel->priv->title_label), 0, 0.5);
+ gtk_label_set_ellipsize(GTK_LABEL (panel->priv->title_label), PANGO_ELLIPSIZE_END);
+
+ gtk_box_pack_start (GTK_BOX (icon_name_hbox),
+ panel->priv->title_label,
+ TRUE,
+ TRUE,
+ 0);
+
+ close_button = create_close_button (panel);
+
+ gtk_box_pack_start (GTK_BOX (title_hbox),
+ close_button,
+ FALSE,
+ FALSE,
+ 0);
+
+ gtk_widget_show_all (title_hbox);
+
+ gtk_box_pack_start (GTK_BOX (panel),
+ panel->priv->notebook,
+ TRUE,
+ TRUE,
+ 0);
+}
+
+static GObject *
+pluma_panel_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+
+ /* Invoke parent constructor. */
+ PlumaPanelClass *klass = PLUMA_PANEL_CLASS (g_type_class_peek (PLUMA_TYPE_PANEL));
+ GObjectClass *parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ GObject *obj = parent_class->constructor (type,
+ n_construct_properties,
+ construct_properties);
+
+ /* Build the panel, now that we know the orientation
+ (_init has been called previously) */
+ PlumaPanel *panel = PLUMA_PANEL (obj);
+
+ build_notebook_for_panel (panel);
+ if (panel->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ build_horizontal_panel (panel);
+ else
+ build_vertical_panel (panel);
+
+ g_signal_connect (panel,
+ "show",
+ G_CALLBACK (panel_show),
+ NULL);
+
+ return obj;
+}
+
+/**
+ * pluma_panel_new:
+ * @orientation: a #GtkOrientation
+ *
+ * Creates a new #PlumaPanel with the given @orientation. You shouldn't create
+ * a new panel use pluma_window_get_side_panel() or pluma_window_get_bottom_panel()
+ * instead.
+ *
+ * Returns: a new #PlumaPanel object.
+ */
+GtkWidget *
+pluma_panel_new (GtkOrientation orientation)
+{
+ return GTK_WIDGET (g_object_new (PLUMA_TYPE_PANEL, "orientation", orientation, NULL));
+}
+
+static GtkWidget *
+build_tab_label (PlumaPanel *panel,
+ GtkWidget *item,
+ const gchar *name,
+ GtkWidget *icon)
+{
+ GtkWidget *hbox, *label_hbox, *label_ebox;
+ GtkWidget *label;
+
+ /* set hbox spacing and label padding (see below) so that there's an
+ * equal amount of space around the label */
+ hbox = gtk_hbox_new (FALSE, 4);
+
+ label_ebox = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (label_ebox), FALSE);
+ gtk_box_pack_start (GTK_BOX (hbox), label_ebox, TRUE, TRUE, 0);
+
+ label_hbox = gtk_hbox_new (FALSE, 4);
+ gtk_container_add (GTK_CONTAINER (label_ebox), label_hbox);
+
+ /* setup icon */
+ gtk_box_pack_start (GTK_BOX (label_hbox), icon, FALSE, FALSE, 0);
+
+ /* setup label */
+ label = gtk_label_new (name);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (label), 0, 0);
+ gtk_box_pack_start (GTK_BOX (label_hbox), label, TRUE, TRUE, 0);
+
+ gtk_widget_set_tooltip_text (label_ebox, name);
+
+ gtk_widget_show_all (hbox);
+
+ if (panel->priv->orientation == GTK_ORIENTATION_VERTICAL)
+ gtk_widget_hide(label);
+
+ g_object_set_data (G_OBJECT (item), "label", label);
+ g_object_set_data (G_OBJECT (item), "hbox", hbox);
+
+ return hbox;
+}
+
+/**
+ * pluma_panel_add_item:
+ * @panel: a #PlumaPanel
+ * @item: the #GtkWidget to add to the @panel
+ * @name: the name to be shown in the @panel
+ * @image: the image to be shown in the @panel
+ *
+ * Adds a new item to the @panel.
+ */
+void
+pluma_panel_add_item (PlumaPanel *panel,
+ GtkWidget *item,
+ const gchar *name,
+ GtkWidget *image)
+{
+ PlumaPanelItem *data;
+ GtkWidget *tab_label;
+ GtkWidget *menu_label;
+ gint w, h;
+
+ g_return_if_fail (PLUMA_IS_PANEL (panel));
+ g_return_if_fail (GTK_IS_WIDGET (item));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (image == NULL || GTK_IS_IMAGE (image));
+
+ data = g_new (PlumaPanelItem, 1);
+
+ data->name = g_strdup (name);
+
+ if (image == NULL)
+ {
+ /* default to empty */
+ data->icon = gtk_image_new_from_stock (GTK_STOCK_FILE,
+ GTK_ICON_SIZE_MENU);
+ }
+ else
+ {
+ data->icon = image;
+ }
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
+ gtk_widget_set_size_request (data->icon, w, h);
+
+ g_object_set_data (G_OBJECT (item),
+ PANEL_ITEM_KEY,
+ data);
+
+ tab_label = build_tab_label (panel, item, data->name, data->icon);
+
+ menu_label = gtk_label_new (name);
+ gtk_misc_set_alignment (GTK_MISC (menu_label), 0.0, 0.5);
+
+ if (!GTK_WIDGET_VISIBLE (item))
+ gtk_widget_show (item);
+
+ gtk_notebook_append_page_menu (GTK_NOTEBOOK (panel->priv->notebook),
+ item,
+ tab_label,
+ menu_label);
+
+ g_signal_emit (G_OBJECT (panel), signals[ITEM_ADDED], 0, item);
+}
+
+/**
+ * pluma_panel_add_item_with_stock_icon:
+ * @panel: a #PlumaPanel
+ * @item: the #GtkWidget to add to the @panel
+ * @name: the name to be shown in the @panel
+ * @stock_id: a stock id
+ *
+ * Same as pluma_panel_add_item() but using an image from stock.
+ */
+void
+pluma_panel_add_item_with_stock_icon (PlumaPanel *panel,
+ GtkWidget *item,
+ const gchar *name,
+ const gchar *stock_id)
+{
+ GtkWidget *icon = NULL;
+
+ if (stock_id != NULL)
+ {
+ icon = gtk_image_new_from_stock (stock_id,
+ GTK_ICON_SIZE_MENU);
+ }
+
+ pluma_panel_add_item (panel, item, name, icon);
+}
+
+/**
+ * pluma_panel_remove_item:
+ * @panel: a #PlumaPanel
+ * @item: the item to be removed from the panel
+ *
+ * Removes the widget @item from the panel if it is in the @panel and returns
+ * TRUE if there was not any problem.
+ *
+ * Returns: TRUE if it was well removed.
+ */
+gboolean
+pluma_panel_remove_item (PlumaPanel *panel,
+ GtkWidget *item)
+{
+ PlumaPanelItem *data;
+ gint page_num;
+
+ g_return_val_if_fail (PLUMA_IS_PANEL (panel), FALSE);
+ g_return_val_if_fail (GTK_IS_WIDGET (item), FALSE);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (panel->priv->notebook),
+ item);
+
+ if (page_num == -1)
+ return FALSE;
+
+ data = (PlumaPanelItem *)g_object_get_data (G_OBJECT (item),
+ PANEL_ITEM_KEY);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ g_free (data->name);
+ g_free (data);
+
+ g_object_set_data (G_OBJECT (item),
+ PANEL_ITEM_KEY,
+ NULL);
+
+ /* ref the item to keep it alive during signal emission */
+ g_object_ref (G_OBJECT (item));
+
+ gtk_notebook_remove_page (GTK_NOTEBOOK (panel->priv->notebook),
+ page_num);
+
+ /* if we removed all the pages, reset the title */
+ if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (panel->priv->notebook)) == 0)
+ sync_title (panel, NULL);
+
+ g_signal_emit (G_OBJECT (panel), signals[ITEM_REMOVED], 0, item);
+
+ g_object_unref (G_OBJECT (item));
+
+ return TRUE;
+}
+
+/**
+ * pluma_panel_activate_item:
+ * @panel: a #PlumaPanel
+ * @item: the item to be activated
+ *
+ * Switches to the page that contains @item.
+ *
+ * Returns: TRUE if it was activated
+ */
+gboolean
+pluma_panel_activate_item (PlumaPanel *panel,
+ GtkWidget *item)
+{
+ gint page_num;
+
+ g_return_val_if_fail (PLUMA_IS_PANEL (panel), FALSE);
+ g_return_val_if_fail (GTK_IS_WIDGET (item), FALSE);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (panel->priv->notebook),
+ item);
+
+ if (page_num == -1)
+ return FALSE;
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (panel->priv->notebook),
+ page_num);
+
+ return TRUE;
+}
+
+/**
+ * pluma_panel_item_is_active:
+ * @panel: a #PlumaPanel
+ * @item: a widget contained in #PlumaPanel
+ *
+ * Wheter @item is the one current active in @panel
+ *
+ * Returns: TRUE if the widget is active
+ */
+gboolean
+pluma_panel_item_is_active (PlumaPanel *panel,
+ GtkWidget *item)
+{
+ gint cur_page;
+ gint page_num;
+
+ g_return_val_if_fail (PLUMA_IS_PANEL (panel), FALSE);
+ g_return_val_if_fail (GTK_IS_WIDGET (item), FALSE);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (panel->priv->notebook),
+ item);
+
+ if (page_num == -1)
+ return FALSE;
+
+ cur_page = gtk_notebook_get_current_page (
+ GTK_NOTEBOOK (panel->priv->notebook));
+
+ return (page_num == cur_page);
+}
+
+/**
+ * pluma_panel_get_orientation:
+ * @panel: a #PlumaPanel
+ *
+ * Gets the orientation of the @panel.
+ *
+ * Returns: the #GtkOrientation of #PlumaPanel
+ */
+GtkOrientation
+pluma_panel_get_orientation (PlumaPanel *panel)
+{
+ g_return_val_if_fail (PLUMA_IS_PANEL (panel), GTK_ORIENTATION_VERTICAL);
+
+ return panel->priv->orientation;
+}
+
+/**
+ * pluma_panel_get_n_items:
+ * @panel: a #PlumaPanel
+ *
+ * Gets the number of items in a @panel.
+ *
+ * Returns: the number of items contained in #PlumaPanel
+ */
+gint
+pluma_panel_get_n_items (PlumaPanel *panel)
+{
+ g_return_val_if_fail (PLUMA_IS_PANEL (panel), -1);
+
+ return gtk_notebook_get_n_pages (GTK_NOTEBOOK (panel->priv->notebook));
+}
+
+gint
+_pluma_panel_get_active_item_id (PlumaPanel *panel)
+{
+ gint cur_page;
+ GtkWidget *item;
+ PlumaPanelItem *data;
+
+ g_return_val_if_fail (PLUMA_IS_PANEL (panel), 0);
+
+ cur_page = gtk_notebook_get_current_page (
+ GTK_NOTEBOOK (panel->priv->notebook));
+ if (cur_page == -1)
+ return 0;
+
+ item = gtk_notebook_get_nth_page (
+ GTK_NOTEBOOK (panel->priv->notebook),
+ cur_page);
+
+ /* FIXME: for now we use as the hash of the name as id.
+ * However the name is not guaranteed to be unique and
+ * it is a translated string, so it's subotimal, but should
+ * be good enough for now since we don't want to add an
+ * ad hoc id argument.
+ */
+
+ data = (PlumaPanelItem *)g_object_get_data (G_OBJECT (item),
+ PANEL_ITEM_KEY);
+ g_return_val_if_fail (data != NULL, 0);
+
+ return g_str_hash (data->name);
+}
+
+void
+_pluma_panel_set_active_item_by_id (PlumaPanel *panel,
+ gint id)
+{
+ gint n, i;
+
+ g_return_if_fail (PLUMA_IS_PANEL (panel));
+
+ if (id == 0)
+ return;
+
+ n = gtk_notebook_get_n_pages (
+ GTK_NOTEBOOK (panel->priv->notebook));
+
+ for (i = 0; i < n; i++)
+ {
+ GtkWidget *item;
+ PlumaPanelItem *data;
+
+ item = gtk_notebook_get_nth_page (
+ GTK_NOTEBOOK (panel->priv->notebook), i);
+
+ data = (PlumaPanelItem *)g_object_get_data (G_OBJECT (item),
+ PANEL_ITEM_KEY);
+ g_return_if_fail (data != NULL);
+
+ if (g_str_hash (data->name) == id)
+ {
+ gtk_notebook_set_current_page (
+ GTK_NOTEBOOK (panel->priv->notebook), i);
+
+ return;
+ }
+ }
+}
diff --git a/pluma/pluma-panel.h b/pluma/pluma-panel.h
new file mode 100755
index 00000000..e3e647be
--- /dev/null
+++ b/pluma/pluma-panel.h
@@ -0,0 +1,130 @@
+/*
+ * pluma-panel.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PANEL_H__
+#define __PLUMA_PANEL_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_PANEL (pluma_panel_get_type())
+#define PLUMA_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PANEL, PlumaPanel))
+#define PLUMA_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_PANEL, PlumaPanelClass))
+#define PLUMA_IS_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_PANEL))
+#define PLUMA_IS_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PANEL))
+#define PLUMA_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_PANEL, PlumaPanelClass))
+
+/* Private structure type */
+typedef struct _PlumaPanelPrivate PlumaPanelPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaPanel PlumaPanel;
+
+struct _PlumaPanel
+{
+ GtkVBox vbox;
+
+ /*< private > */
+ PlumaPanelPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaPanelClass PlumaPanelClass;
+
+struct _PlumaPanelClass
+{
+ GtkVBoxClass parent_class;
+
+ void (* item_added) (PlumaPanel *panel,
+ GtkWidget *item);
+ void (* item_removed) (PlumaPanel *panel,
+ GtkWidget *item);
+
+ /* Keybinding signals */
+ void (* close) (PlumaPanel *panel);
+ void (* focus_document) (PlumaPanel *panel);
+
+ /* Padding for future expansion */
+ void (*_pluma_reserved1) (void);
+ void (*_pluma_reserved2) (void);
+ void (*_pluma_reserved3) (void);
+ void (*_pluma_reserved4) (void);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_panel_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_panel_new (GtkOrientation orientation);
+
+void pluma_panel_add_item (PlumaPanel *panel,
+ GtkWidget *item,
+ const gchar *name,
+ GtkWidget *image);
+
+void pluma_panel_add_item_with_stock_icon (PlumaPanel *panel,
+ GtkWidget *item,
+ const gchar *name,
+ const gchar *stock_id);
+
+gboolean pluma_panel_remove_item (PlumaPanel *panel,
+ GtkWidget *item);
+
+gboolean pluma_panel_activate_item (PlumaPanel *panel,
+ GtkWidget *item);
+
+gboolean pluma_panel_item_is_active (PlumaPanel *panel,
+ GtkWidget *item);
+
+GtkOrientation pluma_panel_get_orientation (PlumaPanel *panel);
+
+gint pluma_panel_get_n_items (PlumaPanel *panel);
+
+
+/*
+ * Non exported functions
+ */
+gint _pluma_panel_get_active_item_id (PlumaPanel *panel);
+
+void _pluma_panel_set_active_item_by_id (PlumaPanel *panel,
+ gint id);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PANEL_H__ */
diff --git a/pluma/pluma-plugin-info-priv.h b/pluma/pluma-plugin-info-priv.h
new file mode 100755
index 00000000..d46e4f29
--- /dev/null
+++ b/pluma/pluma-plugin-info-priv.h
@@ -0,0 +1,68 @@
+/*
+ * pluma-plugin-info-priv.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PLUGIN_INFO_PRIV_H__
+#define __PLUMA_PLUGIN_INFO_PRIV_H__
+
+#include "pluma-plugin-info.h"
+#include "pluma-plugin.h"
+
+struct _PlumaPluginInfo
+{
+ gint refcount;
+
+ PlumaPlugin *plugin;
+ gchar *file;
+
+ gchar *module_name;
+ gchar *loader;
+ gchar **dependencies;
+
+ gchar *name;
+ gchar *desc;
+ gchar *icon_name;
+ gchar **authors;
+ gchar *copyright;
+ gchar *website;
+ gchar *version;
+
+ /* A plugin is unavailable if it is not possible to activate it
+ due to an error loading the plugin module (e.g. for Python plugins
+ when the interpreter has not been correctly initializated) */
+ gint available : 1;
+};
+
+PlumaPluginInfo *_pluma_plugin_info_new (const gchar *file);
+void _pluma_plugin_info_ref (PlumaPluginInfo *info);
+void _pluma_plugin_info_unref (PlumaPluginInfo *info);
+
+
+#endif /* __PLUMA_PLUGIN_INFO_PRIV_H__ */
diff --git a/pluma/pluma-plugin-info.c b/pluma/pluma-plugin-info.c
new file mode 100755
index 00000000..76daf3b5
--- /dev/null
+++ b/pluma/pluma-plugin-info.c
@@ -0,0 +1,394 @@
+/*
+ * pluma-plugin-info.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+
+#include "pluma-plugin-info.h"
+#include "pluma-plugin-info-priv.h"
+#include "pluma-debug.h"
+#include "pluma-plugin.h"
+
+void
+_pluma_plugin_info_ref (PlumaPluginInfo *info)
+{
+ g_atomic_int_inc (&info->refcount);
+}
+
+static PlumaPluginInfo *
+pluma_plugin_info_copy (PlumaPluginInfo *info)
+{
+ _pluma_plugin_info_ref (info);
+ return info;
+}
+
+void
+_pluma_plugin_info_unref (PlumaPluginInfo *info)
+{
+ if (!g_atomic_int_dec_and_test (&info->refcount))
+ return;
+
+ if (info->plugin != NULL)
+ {
+ pluma_debug_message (DEBUG_PLUGINS, "Unref plugin %s", info->name);
+
+ g_object_unref (info->plugin);
+ }
+
+ g_free (info->file);
+ g_free (info->module_name);
+ g_strfreev (info->dependencies);
+ g_free (info->name);
+ g_free (info->desc);
+ g_free (info->icon_name);
+ g_free (info->website);
+ g_free (info->copyright);
+ g_free (info->loader);
+ g_free (info->version);
+ g_strfreev (info->authors);
+
+ g_free (info);
+}
+
+/**
+ * pluma_plugin_info_get_type:
+ *
+ * Retrieves the #GType object which is associated with the #PlumaPluginInfo
+ * class.
+ *
+ * Return value: the GType associated with #PlumaPluginInfo.
+ **/
+GType
+pluma_plugin_info_get_type (void)
+{
+ static GType the_type = 0;
+
+ if (G_UNLIKELY (!the_type))
+ the_type = g_boxed_type_register_static (
+ "PlumaPluginInfo",
+ (GBoxedCopyFunc) pluma_plugin_info_copy,
+ (GBoxedFreeFunc) _pluma_plugin_info_unref);
+
+ return the_type;
+}
+
+/**
+ * pluma_plugin_info_new:
+ * @filename: the filename where to read the plugin information
+ *
+ * Creates a new #PlumaPluginInfo from a file on the disk.
+ *
+ * Return value: a newly created #PlumaPluginInfo.
+ */
+PlumaPluginInfo *
+_pluma_plugin_info_new (const gchar *file)
+{
+ PlumaPluginInfo *info;
+ GKeyFile *plugin_file = NULL;
+ gchar *str;
+
+ g_return_val_if_fail (file != NULL, NULL);
+
+ pluma_debug_message (DEBUG_PLUGINS, "Loading plugin: %s", file);
+
+ info = g_new0 (PlumaPluginInfo, 1);
+ info->refcount = 1;
+ info->file = g_strdup (file);
+
+ plugin_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (plugin_file, file, G_KEY_FILE_NONE, NULL))
+ {
+ g_warning ("Bad plugin file: %s", file);
+ goto error;
+ }
+
+ if (!g_key_file_has_key (plugin_file,
+ "Pluma Plugin",
+ "IAge",
+ NULL))
+ {
+ pluma_debug_message (DEBUG_PLUGINS,
+ "IAge key does not exist in file: %s", file);
+ goto error;
+ }
+
+ /* Check IAge=2 */
+ if (g_key_file_get_integer (plugin_file,
+ "Pluma Plugin",
+ "IAge",
+ NULL) != 2)
+ {
+ pluma_debug_message (DEBUG_PLUGINS,
+ "Wrong IAge in file: %s", file);
+ goto error;
+ }
+
+ /* Get module name */
+ str = g_key_file_get_string (plugin_file,
+ "Pluma Plugin",
+ "Module",
+ NULL);
+
+ if ((str != NULL) && (*str != '\0'))
+ {
+ info->module_name = str;
+ }
+ else
+ {
+ g_warning ("Could not find 'Module' in %s", file);
+ goto error;
+ }
+
+ /* Get the dependency list */
+ info->dependencies = g_key_file_get_string_list (plugin_file,
+ "Pluma Plugin",
+ "Depends",
+ NULL,
+ NULL);
+ if (info->dependencies == NULL)
+ {
+ pluma_debug_message (DEBUG_PLUGINS, "Could not find 'Depends' in %s", file);
+ info->dependencies = g_new0 (gchar *, 1);
+ }
+
+ /* Get the loader for this plugin */
+ str = g_key_file_get_string (plugin_file,
+ "Pluma Plugin",
+ "Loader",
+ NULL);
+
+ if ((str != NULL) && (*str != '\0'))
+ {
+ info->loader = str;
+ }
+ else
+ {
+ /* default to the C loader */
+ info->loader = g_strdup("c");
+ }
+
+ /* Get Name */
+ str = g_key_file_get_locale_string (plugin_file,
+ "Pluma Plugin",
+ "Name",
+ NULL, NULL);
+ if (str)
+ info->name = str;
+ else
+ {
+ g_warning ("Could not find 'Name' in %s", file);
+ goto error;
+ }
+
+ /* Get Description */
+ str = g_key_file_get_locale_string (plugin_file,
+ "Pluma Plugin",
+ "Description",
+ NULL, NULL);
+ if (str)
+ info->desc = str;
+ else
+ pluma_debug_message (DEBUG_PLUGINS, "Could not find 'Description' in %s", file);
+
+ /* Get Icon */
+ str = g_key_file_get_locale_string (plugin_file,
+ "Pluma Plugin",
+ "Icon",
+ NULL, NULL);
+ if (str)
+ info->icon_name = str;
+ else
+ pluma_debug_message (DEBUG_PLUGINS, "Could not find 'Icon' in %s, using 'pluma-plugin'", file);
+
+
+ /* Get Authors */
+ info->authors = g_key_file_get_string_list (plugin_file,
+ "Pluma Plugin",
+ "Authors",
+ NULL,
+ NULL);
+ if (info->authors == NULL)
+ pluma_debug_message (DEBUG_PLUGINS, "Could not find 'Authors' in %s", file);
+
+
+ /* Get Copyright */
+ str = g_key_file_get_string (plugin_file,
+ "Pluma Plugin",
+ "Copyright",
+ NULL);
+ if (str)
+ info->copyright = str;
+ else
+ pluma_debug_message (DEBUG_PLUGINS, "Could not find 'Copyright' in %s", file);
+
+ /* Get Website */
+ str = g_key_file_get_string (plugin_file,
+ "Pluma Plugin",
+ "Website",
+ NULL);
+ if (str)
+ info->website = str;
+ else
+ pluma_debug_message (DEBUG_PLUGINS, "Could not find 'Website' in %s", file);
+
+ /* Get Version */
+ str = g_key_file_get_string (plugin_file,
+ "Pluma Plugin",
+ "Version",
+ NULL);
+ if (str)
+ info->version = str;
+ else
+ pluma_debug_message (DEBUG_PLUGINS, "Could not find 'Version' in %s", file);
+
+ g_key_file_free (plugin_file);
+
+ /* If we know nothing about the availability of the plugin,
+ set it as available */
+ info->available = TRUE;
+
+ return info;
+
+error:
+ g_free (info->file);
+ g_free (info->module_name);
+ g_free (info->name);
+ g_free (info->loader);
+ g_free (info);
+ g_key_file_free (plugin_file);
+
+ return NULL;
+}
+
+gboolean
+pluma_plugin_info_is_active (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ return info->available && info->plugin != NULL;
+}
+
+gboolean
+pluma_plugin_info_is_available (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ return info->available != FALSE;
+}
+
+gboolean
+pluma_plugin_info_is_configurable (PlumaPluginInfo *info)
+{
+ pluma_debug_message (DEBUG_PLUGINS, "Is '%s' configurable?", info->name);
+
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ if (info->plugin == NULL || !info->available)
+ return FALSE;
+
+ return pluma_plugin_is_configurable (info->plugin);
+}
+
+const gchar *
+pluma_plugin_info_get_module_name (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return info->module_name;
+}
+
+const gchar *
+pluma_plugin_info_get_name (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return info->name;
+}
+
+const gchar *
+pluma_plugin_info_get_description (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return info->desc;
+}
+
+const gchar *
+pluma_plugin_info_get_icon_name (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ /* use the pluma-plugin icon as a default if the plugin does not
+ have its own */
+ if (info->icon_name != NULL &&
+ gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+ info->icon_name))
+ return info->icon_name;
+ else
+ return "pluma-plugin";
+}
+
+const gchar **
+pluma_plugin_info_get_authors (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, (const gchar **)NULL);
+
+ return (const gchar **) info->authors;
+}
+
+const gchar *
+pluma_plugin_info_get_website (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return info->website;
+}
+
+const gchar *
+pluma_plugin_info_get_copyright (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return info->copyright;
+}
+
+const gchar *
+pluma_plugin_info_get_version (PlumaPluginInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return info->version;
+}
diff --git a/pluma/pluma-plugin-info.h b/pluma/pluma-plugin-info.h
new file mode 100755
index 00000000..e66861c0
--- /dev/null
+++ b/pluma/pluma-plugin-info.h
@@ -0,0 +1,63 @@
+/*
+ * pluma-plugin-info.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PLUGIN_INFO_H__
+#define __PLUMA_PLUGIN_INFO_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_PLUGIN_INFO (pluma_plugin_info_get_type ())
+#define PLUMA_PLUGIN_INFO(obj) ((PlumaPluginInfo *) (obj))
+
+typedef struct _PlumaPluginInfo PlumaPluginInfo;
+
+GType pluma_plugin_info_get_type (void) G_GNUC_CONST;
+
+gboolean pluma_plugin_info_is_active (PlumaPluginInfo *info);
+gboolean pluma_plugin_info_is_available (PlumaPluginInfo *info);
+gboolean pluma_plugin_info_is_configurable (PlumaPluginInfo *info);
+
+const gchar *pluma_plugin_info_get_module_name (PlumaPluginInfo *info);
+
+const gchar *pluma_plugin_info_get_name (PlumaPluginInfo *info);
+const gchar *pluma_plugin_info_get_description (PlumaPluginInfo *info);
+const gchar *pluma_plugin_info_get_icon_name (PlumaPluginInfo *info);
+const gchar **pluma_plugin_info_get_authors (PlumaPluginInfo *info);
+const gchar *pluma_plugin_info_get_website (PlumaPluginInfo *info);
+const gchar *pluma_plugin_info_get_copyright (PlumaPluginInfo *info);
+const gchar *pluma_plugin_info_get_version (PlumaPluginInfo *info);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PLUGIN_INFO_H__ */
+
diff --git a/pluma/pluma-plugin-loader.c b/pluma/pluma-plugin-loader.c
new file mode 100755
index 00000000..3bf367bd
--- /dev/null
+++ b/pluma/pluma-plugin-loader.c
@@ -0,0 +1,131 @@
+/*
+ * pluma-plugin-loader.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+#include "pluma-plugin-loader.h"
+
+static void
+pluma_plugin_loader_base_init (gpointer g_class)
+{
+ static gboolean initialized = FALSE;
+
+ if (G_UNLIKELY (!initialized))
+ {
+ /* create interface signals here. */
+ initialized = TRUE;
+ }
+}
+
+GType
+pluma_plugin_loader_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ static const GTypeInfo info =
+ {
+ sizeof (PlumaPluginLoaderInterface),
+ pluma_plugin_loader_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE, "PlumaPluginLoader", &info, 0);
+ }
+
+ return type;
+}
+
+const gchar *
+pluma_plugin_loader_type_get_id (GType type)
+{
+ GTypeClass *klass;
+ PlumaPluginLoaderInterface *iface;
+
+ klass = g_type_class_ref (type);
+
+ if (klass == NULL)
+ {
+ g_warning ("Could not get class info for plugin loader");
+ return NULL;
+ }
+
+ iface = g_type_interface_peek (klass, PLUMA_TYPE_PLUGIN_LOADER);
+
+ if (iface == NULL)
+ {
+ g_warning ("Could not get plugin loader interface");
+ g_type_class_unref (klass);
+
+ return NULL;
+ }
+
+ g_return_val_if_fail (iface->get_id != NULL, NULL);
+ return iface->get_id ();
+}
+
+PlumaPlugin *
+pluma_plugin_loader_load (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info,
+ const gchar *path)
+{
+ PlumaPluginLoaderInterface *iface;
+
+ g_return_val_if_fail (PLUMA_IS_PLUGIN_LOADER (loader), NULL);
+
+ iface = PLUMA_PLUGIN_LOADER_GET_INTERFACE (loader);
+ g_return_val_if_fail (iface->load != NULL, NULL);
+
+ return iface->load (loader, info, path);
+}
+
+void
+pluma_plugin_loader_unload (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info)
+{
+ PlumaPluginLoaderInterface *iface;
+
+ g_return_if_fail (PLUMA_IS_PLUGIN_LOADER (loader));
+
+ iface = PLUMA_PLUGIN_LOADER_GET_INTERFACE (loader);
+ g_return_if_fail (iface->unload != NULL);
+
+ iface->unload (loader, info);
+}
+
+void
+pluma_plugin_loader_garbage_collect (PlumaPluginLoader *loader)
+{
+ PlumaPluginLoaderInterface *iface;
+
+ g_return_if_fail (PLUMA_IS_PLUGIN_LOADER (loader));
+
+ iface = PLUMA_PLUGIN_LOADER_GET_INTERFACE (loader);
+
+ if (iface->garbage_collect != NULL)
+ iface->garbage_collect (loader);
+}
diff --git a/pluma/pluma-plugin-loader.h b/pluma/pluma-plugin-loader.h
new file mode 100755
index 00000000..aff7469b
--- /dev/null
+++ b/pluma/pluma-plugin-loader.h
@@ -0,0 +1,106 @@
+/*
+ * pluma-plugin-loader.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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 __PLUMA_PLUGIN_LOADER_H__
+#define __PLUMA_PLUGIN_LOADER_H__
+
+#include <glib-object.h>
+#include <pluma/pluma-plugin.h>
+#include <pluma/pluma-plugin-info.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_PLUGIN_LOADER (pluma_plugin_loader_get_type ())
+#define PLUMA_PLUGIN_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_PLUGIN_LOADER, PlumaPluginLoader))
+#define PLUMA_IS_PLUGIN_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_PLUGIN_LOADER))
+#define PLUMA_PLUGIN_LOADER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), PLUMA_TYPE_PLUGIN_LOADER, PlumaPluginLoaderInterface))
+
+typedef struct _PlumaPluginLoader PlumaPluginLoader; /* dummy object */
+typedef struct _PlumaPluginLoaderInterface PlumaPluginLoaderInterface;
+
+struct _PlumaPluginLoaderInterface {
+ GTypeInterface parent;
+
+ const gchar *(*get_id) (void);
+
+ PlumaPlugin *(*load) (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info,
+ const gchar *path);
+
+ void (*unload) (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info);
+
+ void (*garbage_collect) (PlumaPluginLoader *loader);
+};
+
+GType pluma_plugin_loader_get_type (void);
+
+const gchar *pluma_plugin_loader_type_get_id (GType type);
+PlumaPlugin *pluma_plugin_loader_load (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info,
+ const gchar *path);
+void pluma_plugin_loader_unload (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info);
+void pluma_plugin_loader_garbage_collect (PlumaPluginLoader *loader);
+
+/**
+ * PLUMA_PLUGIN_LOADER_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init):
+ *
+ * Utility macro used to register interfaces for gobject types in plugin loaders.
+ */
+#define PLUMA_PLUGIN_LOADER_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init) \
+ const GInterfaceInfo g_implement_interface_info = \
+ { \
+ (GInterfaceInitFunc) iface_init, \
+ NULL, \
+ NULL \
+ }; \
+ \
+ g_type_module_add_interface (type_module, \
+ g_define_type_id, \
+ TYPE_IFACE, \
+ &g_implement_interface_info);
+
+/**
+ * PLUMA_PLUGIN_LOADER_REGISTER_TYPE(PluginLoaderName, plugin_loader_name, PARENT_TYPE, loader_interface_init):
+ *
+ * Utility macro used to register plugin loaders.
+ */
+#define PLUMA_PLUGIN_LOADER_REGISTER_TYPE(PluginLoaderName, plugin_loader_name, PARENT_TYPE, loader_iface_init) \
+ G_DEFINE_DYNAMIC_TYPE_EXTENDED (PluginLoaderName, \
+ plugin_loader_name, \
+ PARENT_TYPE, \
+ 0, \
+ PLUMA_PLUGIN_LOADER_IMPLEMENT_INTERFACE(PLUMA_TYPE_PLUGIN_LOADER, loader_iface_init)); \
+ \
+ \
+G_MODULE_EXPORT GType \
+register_pluma_plugin_loader (GTypeModule *type_module) \
+{ \
+ plugin_loader_name##_register_type (type_module); \
+ \
+ return plugin_loader_name##_get_type(); \
+}
+
+G_END_DECLS
+
+#endif /* __PLUMA_PLUGIN_LOADER_H__ */
diff --git a/pluma/pluma-plugin-manager.c b/pluma/pluma-plugin-manager.c
new file mode 100755
index 00000000..1f2f5380
--- /dev/null
+++ b/pluma/pluma-plugin-manager.c
@@ -0,0 +1,889 @@
+/*
+ * pluma-plugin-manager.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002 Paolo Maggi and James Willcox
+ * Copyright (C) 2003-2006 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "pluma-plugin-manager.h"
+#include "pluma-utils.h"
+#include "pluma-plugins-engine.h"
+#include "pluma-plugin.h"
+#include "pluma-debug.h"
+
+enum
+{
+ ACTIVE_COLUMN,
+ AVAILABLE_COLUMN,
+ INFO_COLUMN,
+ N_COLUMNS
+};
+
+#define PLUGIN_MANAGER_NAME_TITLE _("Plugin")
+#define PLUGIN_MANAGER_ACTIVE_TITLE _("Enabled")
+
+#define PLUMA_PLUGIN_MANAGER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_PLUGIN_MANAGER, PlumaPluginManagerPrivate))
+
+struct _PlumaPluginManagerPrivate
+{
+ GtkWidget *tree;
+
+ GtkWidget *about_button;
+ GtkWidget *configure_button;
+
+ PlumaPluginsEngine *engine;
+
+ GtkWidget *about;
+
+ GtkWidget *popup_menu;
+};
+
+G_DEFINE_TYPE(PlumaPluginManager, pluma_plugin_manager, GTK_TYPE_VBOX)
+
+static PlumaPluginInfo *plugin_manager_get_selected_plugin (PlumaPluginManager *pm);
+static void plugin_manager_toggle_active (PlumaPluginManager *pm, GtkTreeIter *iter, GtkTreeModel *model);
+static void pluma_plugin_manager_finalize (GObject *object);
+
+static void
+pluma_plugin_manager_class_init (PlumaPluginManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_plugin_manager_finalize;
+
+ g_type_class_add_private (object_class, sizeof (PlumaPluginManagerPrivate));
+}
+
+static void
+about_button_cb (GtkWidget *button,
+ PlumaPluginManager *pm)
+{
+ PlumaPluginInfo *info;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ info = plugin_manager_get_selected_plugin (pm);
+
+ g_return_if_fail (info != NULL);
+
+ /* if there is another about dialog already open destroy it */
+ if (pm->priv->about)
+ gtk_widget_destroy (pm->priv->about);
+
+ pm->priv->about = g_object_new (GTK_TYPE_ABOUT_DIALOG,
+ "program-name", pluma_plugin_info_get_name (info),
+ "copyright", pluma_plugin_info_get_copyright (info),
+ "authors", pluma_plugin_info_get_authors (info),
+ "comments", pluma_plugin_info_get_description (info),
+ "website", pluma_plugin_info_get_website (info),
+ "logo-icon-name", pluma_plugin_info_get_icon_name (info),
+ "version", pluma_plugin_info_get_version (info),
+ NULL);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (pm->priv->about),
+ TRUE);
+
+ g_signal_connect (pm->priv->about,
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+ g_signal_connect (pm->priv->about,
+ "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &pm->priv->about);
+
+ gtk_window_set_transient_for (GTK_WINDOW (pm->priv->about),
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET(pm))));
+ gtk_widget_show (pm->priv->about);
+}
+
+static void
+configure_button_cb (GtkWidget *button,
+ PlumaPluginManager *pm)
+{
+ PlumaPluginInfo *info;
+ GtkWindow *toplevel;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ info = plugin_manager_get_selected_plugin (pm);
+
+ g_return_if_fail (info != NULL);
+
+ pluma_debug_message (DEBUG_PLUGINS, "Configuring: %s\n",
+ pluma_plugin_info_get_name (info));
+
+ toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET(pm)));
+
+ pluma_plugins_engine_configure_plugin (pm->priv->engine,
+ info, toplevel);
+
+ pluma_debug_message (DEBUG_PLUGINS, "Done");
+}
+
+static void
+plugin_manager_view_info_cell_cb (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ PlumaPluginInfo *info;
+ gchar *text;
+
+ g_return_if_fail (tree_model != NULL);
+ g_return_if_fail (tree_column != NULL);
+
+ gtk_tree_model_get (tree_model, iter, INFO_COLUMN, &info, -1);
+
+ if (info == NULL)
+ return;
+
+ text = g_markup_printf_escaped ("<b>%s</b>\n%s",
+ pluma_plugin_info_get_name (info),
+ pluma_plugin_info_get_description (info));
+ g_object_set (G_OBJECT (cell),
+ "markup", text,
+ "sensitive", pluma_plugin_info_is_available (info),
+ NULL);
+
+ g_free (text);
+}
+
+static void
+plugin_manager_view_icon_cell_cb (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ PlumaPluginInfo *info;
+
+ g_return_if_fail (tree_model != NULL);
+ g_return_if_fail (tree_column != NULL);
+
+ gtk_tree_model_get (tree_model, iter, INFO_COLUMN, &info, -1);
+
+ if (info == NULL)
+ return;
+
+ g_object_set (G_OBJECT (cell),
+ "icon-name", pluma_plugin_info_get_icon_name (info),
+ "sensitive", pluma_plugin_info_is_available (info),
+ NULL);
+}
+
+
+static void
+active_toggled_cb (GtkCellRendererToggle *cell,
+ gchar *path_str,
+ PlumaPluginManager *pm)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeModel *model;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ path = gtk_tree_path_new_from_string (path_str);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree));
+ g_return_if_fail (model != NULL);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ if (&iter != NULL)
+ plugin_manager_toggle_active (pm, &iter, model);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+cursor_changed_cb (GtkTreeView *view,
+ gpointer data)
+{
+ PlumaPluginManager *pm = data;
+ PlumaPluginInfo *info;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ info = plugin_manager_get_selected_plugin (pm);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (pm->priv->about_button),
+ info != NULL);
+ gtk_widget_set_sensitive (GTK_WIDGET (pm->priv->configure_button),
+ (info != NULL) &&
+ pluma_plugin_info_is_configurable (info));
+}
+
+static void
+row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer data)
+{
+ PlumaPluginManager *pm = data;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree));
+
+ g_return_if_fail (model != NULL);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ g_return_if_fail (&iter != NULL);
+
+ plugin_manager_toggle_active (pm, &iter, model);
+}
+
+static void
+plugin_manager_populate_lists (PlumaPluginManager *pm)
+{
+ const GList *plugins;
+ GtkListStore *model;
+ GtkTreeIter iter;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ plugins = pluma_plugins_engine_get_plugin_list (pm->priv->engine);
+
+ model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree)));
+
+ while (plugins)
+ {
+ PlumaPluginInfo *info;
+ info = (PlumaPluginInfo *)plugins->data;
+
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter,
+ ACTIVE_COLUMN, pluma_plugin_info_is_active (info),
+ AVAILABLE_COLUMN, pluma_plugin_info_is_available (info),
+ INFO_COLUMN, info,
+ -1);
+
+ plugins = plugins->next;
+ }
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
+ {
+ GtkTreeSelection *selection;
+ PlumaPluginInfo* info;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pm->priv->tree));
+ g_return_if_fail (selection != NULL);
+
+ gtk_tree_selection_select_iter (selection, &iter);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+ INFO_COLUMN, &info, -1);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (pm->priv->configure_button),
+ pluma_plugin_info_is_configurable (info));
+ }
+}
+
+static gboolean
+plugin_manager_set_active (PlumaPluginManager *pm,
+ GtkTreeIter *iter,
+ GtkTreeModel *model,
+ gboolean active)
+{
+ PlumaPluginInfo *info;
+ gboolean res = TRUE;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ gtk_tree_model_get (model, iter, INFO_COLUMN, &info, -1);
+
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ if (active)
+ {
+ /* activate the plugin */
+ if (!pluma_plugins_engine_activate_plugin (pm->priv->engine, info)) {
+ pluma_debug_message (DEBUG_PLUGINS, "Could not activate %s.\n",
+ pluma_plugin_info_get_name (info));
+
+ res = FALSE;
+ }
+ }
+ else
+ {
+ /* deactivate the plugin */
+ if (!pluma_plugins_engine_deactivate_plugin (pm->priv->engine, info)) {
+ pluma_debug_message (DEBUG_PLUGINS, "Could not deactivate %s.\n",
+ pluma_plugin_info_get_name (info));
+
+ res = FALSE;
+ }
+ }
+
+ return res;
+}
+
+static void
+plugin_manager_toggle_active (PlumaPluginManager *pm,
+ GtkTreeIter *iter,
+ GtkTreeModel *model)
+{
+ gboolean active;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ gtk_tree_model_get (model, iter, ACTIVE_COLUMN, &active, -1);
+
+ active ^= 1;
+
+ plugin_manager_set_active (pm, iter, model, active);
+}
+
+static PlumaPluginInfo *
+plugin_manager_get_selected_plugin (PlumaPluginManager *pm)
+{
+ PlumaPluginInfo *info = NULL;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree));
+ g_return_val_if_fail (model != NULL, NULL);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pm->priv->tree));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ gtk_tree_model_get (model, &iter, INFO_COLUMN, &info, -1);
+ }
+
+ return info;
+}
+
+static void
+plugin_manager_set_active_all (PlumaPluginManager *pm,
+ gboolean active)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree));
+
+ g_return_if_fail (model != NULL);
+
+ gtk_tree_model_get_iter_first (model, &iter);
+
+ do {
+ plugin_manager_set_active (pm, &iter, model, active);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+}
+
+/* Callback used as the interactive search comparison function */
+static gboolean
+name_search_cb (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ PlumaPluginInfo *info;
+ gchar *normalized_string;
+ gchar *normalized_key;
+ gchar *case_normalized_string;
+ gchar *case_normalized_key;
+ gint key_len;
+ gboolean retval;
+
+ gtk_tree_model_get (model, iter, INFO_COLUMN, &info, -1);
+ if (!info)
+ return FALSE;
+
+ normalized_string = g_utf8_normalize (pluma_plugin_info_get_name (info), -1, G_NORMALIZE_ALL);
+ normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
+ case_normalized_string = g_utf8_casefold (normalized_string, -1);
+ case_normalized_key = g_utf8_casefold (normalized_key, -1);
+
+ key_len = strlen (case_normalized_key);
+
+ /* Oddly enough, this callback must return whether to stop the search
+ * because we found a match, not whether we actually matched.
+ */
+ retval = (strncmp (case_normalized_key, case_normalized_string, key_len) != 0);
+
+ g_free (normalized_key);
+ g_free (normalized_string);
+ g_free (case_normalized_key);
+ g_free (case_normalized_string);
+
+ return retval;
+}
+
+static void
+enable_plugin_menu_cb (GtkMenu *menu,
+ PlumaPluginManager *pm)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (pm->priv->tree));
+ g_return_if_fail (model != NULL);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pm->priv->tree));
+ g_return_if_fail (selection != NULL);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ plugin_manager_toggle_active (pm, &iter, model);
+}
+
+static void
+enable_all_menu_cb (GtkMenu *menu,
+ PlumaPluginManager *pm)
+{
+ plugin_manager_set_active_all (pm, TRUE);
+}
+
+static void
+disable_all_menu_cb (GtkMenu *menu,
+ PlumaPluginManager *pm)
+{
+ plugin_manager_set_active_all (pm, FALSE);
+}
+
+static GtkWidget *
+create_tree_popup_menu (PlumaPluginManager *pm)
+{
+ GtkWidget *menu;
+ GtkWidget *item;
+ GtkWidget *image;
+ PlumaPluginInfo *info;
+
+ info = plugin_manager_get_selected_plugin (pm);
+
+ menu = gtk_menu_new ();
+
+ item = gtk_image_menu_item_new_with_mnemonic (_("_About"));
+ image = gtk_image_new_from_stock (GTK_STOCK_ABOUT,
+ GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+ g_signal_connect (item, "activate",
+ G_CALLBACK (about_button_cb), pm);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ item = gtk_image_menu_item_new_with_mnemonic (_("C_onfigure"));
+ image = gtk_image_new_from_stock (GTK_STOCK_PREFERENCES,
+ GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+ g_signal_connect (item, "activate",
+ G_CALLBACK (configure_button_cb), pm);
+ gtk_widget_set_sensitive (item, pluma_plugin_info_is_configurable (info));
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ item = gtk_check_menu_item_new_with_mnemonic (_("A_ctivate"));
+ gtk_widget_set_sensitive (item, pluma_plugin_info_is_available (info));
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
+ pluma_plugin_info_is_active (info));
+ g_signal_connect (item, "toggled",
+ G_CALLBACK (enable_plugin_menu_cb), pm);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("Ac_tivate All"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (enable_all_menu_cb), pm);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Deactivate All"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (disable_all_menu_cb), pm);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ gtk_widget_show_all (menu);
+
+ return menu;
+}
+
+static void
+tree_popup_menu_detach (PlumaPluginManager *pm,
+ GtkMenu *menu)
+{
+ pm->priv->popup_menu = NULL;
+}
+
+static void
+show_tree_popup_menu (GtkTreeView *tree,
+ PlumaPluginManager *pm,
+ GdkEventButton *event)
+{
+ if (pm->priv->popup_menu)
+ gtk_widget_destroy (pm->priv->popup_menu);
+
+ pm->priv->popup_menu = create_tree_popup_menu (pm);
+
+ gtk_menu_attach_to_widget (GTK_MENU (pm->priv->popup_menu),
+ GTK_WIDGET (pm),
+ (GtkMenuDetachFunc) tree_popup_menu_detach);
+
+ if (event != NULL)
+ {
+ gtk_menu_popup (GTK_MENU (pm->priv->popup_menu), NULL, NULL,
+ NULL, NULL,
+ event->button, event->time);
+ }
+ else
+ {
+ gtk_menu_popup (GTK_MENU (pm->priv->popup_menu), NULL, NULL,
+ pluma_utils_menu_position_under_tree_view, tree,
+ 0, gtk_get_current_event_time ());
+
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (pm->priv->popup_menu),
+ FALSE);
+ }
+}
+
+static gboolean
+button_press_event_cb (GtkWidget *tree,
+ GdkEventButton *event,
+ PlumaPluginManager *pm)
+{
+ /* We want the treeview selection to be updated before showing the menu.
+ * This code is evil, thanks to Federico Mena Quintero's black magic.
+ * See: http://mail.gnome.org/archives/gtk-devel-list/2006-February/msg00168.html
+ * FIXME: Let's remove it asap.
+ */
+
+ static gboolean in_press = FALSE;
+ gboolean handled;
+
+ if (in_press)
+ return FALSE; /* we re-entered */
+
+ if (GDK_BUTTON_PRESS != event->type || 3 != event->button)
+ return FALSE; /* let the normal handler run */
+
+ in_press = TRUE;
+ handled = gtk_widget_event (tree, (GdkEvent *) event);
+ in_press = FALSE;
+
+ if (!handled)
+ return FALSE;
+
+ /* The selection is fully updated by now */
+ show_tree_popup_menu (GTK_TREE_VIEW (tree), pm, event);
+ return TRUE;
+}
+
+static gboolean
+popup_menu_cb (GtkTreeView *tree,
+ PlumaPluginManager *pm)
+{
+ show_tree_popup_menu (tree, pm, NULL);
+ return TRUE;
+}
+
+static gint
+model_name_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter1,
+ GtkTreeIter *iter2,
+ gpointer user_data)
+{
+ PlumaPluginInfo *info1, *info2;
+
+ gtk_tree_model_get (model, iter1, INFO_COLUMN, &info1, -1);
+ gtk_tree_model_get (model, iter2, INFO_COLUMN, &info2, -1);
+
+ return g_utf8_collate (pluma_plugin_info_get_name (info1),
+ pluma_plugin_info_get_name (info2));
+}
+
+static void
+plugin_manager_construct_tree (PlumaPluginManager *pm)
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkListStore *model;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ model = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN,
+ G_TYPE_POINTER);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (pm->priv->tree),
+ GTK_TREE_MODEL (model));
+ g_object_unref (model);
+
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (pm->priv->tree), TRUE);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (pm->priv->tree), FALSE);
+
+ /* first column */
+ cell = gtk_cell_renderer_toggle_new ();
+ g_object_set (cell, "xpad", 6, NULL);
+ g_signal_connect (cell,
+ "toggled",
+ G_CALLBACK (active_toggled_cb),
+ pm);
+ column = gtk_tree_view_column_new_with_attributes (PLUGIN_MANAGER_ACTIVE_TITLE,
+ cell,
+ "active",
+ ACTIVE_COLUMN,
+ "activatable",
+ AVAILABLE_COLUMN,
+ "sensitive",
+ AVAILABLE_COLUMN,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (pm->priv->tree), column);
+
+ /* second column */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, PLUGIN_MANAGER_NAME_TITLE);
+ gtk_tree_view_column_set_resizable (column, TRUE);
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ g_object_set (cell, "stock-size", GTK_ICON_SIZE_SMALL_TOOLBAR, NULL);
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ plugin_manager_view_icon_cell_cb,
+ pm, NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ plugin_manager_view_info_cell_cb,
+ pm, NULL);
+
+
+ gtk_tree_view_column_set_spacing (column, 6);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (pm->priv->tree), column);
+
+ /* Sort on the plugin names */
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model),
+ model_name_sort_func,
+ NULL,
+ NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+
+ /* Enable search for our non-string column */
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (pm->priv->tree),
+ INFO_COLUMN);
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (pm->priv->tree),
+ name_search_cb,
+ NULL,
+ NULL);
+
+ g_signal_connect (pm->priv->tree,
+ "cursor_changed",
+ G_CALLBACK (cursor_changed_cb),
+ pm);
+ g_signal_connect (pm->priv->tree,
+ "row_activated",
+ G_CALLBACK (row_activated_cb),
+ pm);
+
+ g_signal_connect (pm->priv->tree,
+ "button-press-event",
+ G_CALLBACK (button_press_event_cb),
+ pm);
+ g_signal_connect (pm->priv->tree,
+ "popup-menu",
+ G_CALLBACK (popup_menu_cb),
+ pm);
+ gtk_widget_show (pm->priv->tree);
+}
+
+static void
+plugin_toggled_cb (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info,
+ PlumaPluginManager *pm)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean info_found = FALSE;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (pm->priv->tree));
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ /* There is an item selected: it's probably the one we want! */
+ PlumaPluginInfo *tinfo;
+ gtk_tree_model_get (model, &iter, INFO_COLUMN, &tinfo, -1);
+ info_found = info == tinfo;
+ }
+
+ if (!info_found)
+ {
+ gtk_tree_model_get_iter_first (model, &iter);
+
+ do
+ {
+ PlumaPluginInfo *tinfo;
+ gtk_tree_model_get (model, &iter, INFO_COLUMN, &tinfo, -1);
+ info_found = info == tinfo;
+ }
+ while (!info_found && gtk_tree_model_iter_next (model, &iter));
+ }
+
+ if (!info_found)
+ {
+ g_warning ("PlumaPluginManager: plugin '%s' not found in the tree model",
+ pluma_plugin_info_get_name (info));
+ return;
+ }
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, ACTIVE_COLUMN, pluma_plugin_info_is_active (info), -1);
+}
+
+static void
+pluma_plugin_manager_init (PlumaPluginManager *pm)
+{
+ GtkWidget *label;
+ GtkWidget *viewport;
+ GtkWidget *hbuttonbox;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ pm->priv = PLUMA_PLUGIN_MANAGER_GET_PRIVATE (pm);
+
+ /*
+ * Always we create the manager, firstly we rescan the plugins directory
+ */
+ pluma_plugins_engine_rescan_plugins (pluma_plugins_engine_get_default ());
+
+ gtk_box_set_spacing (GTK_BOX (pm), 6);
+
+ label = gtk_label_new_with_mnemonic (_("Active _Plugins:"));
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ gtk_box_pack_start (GTK_BOX (pm), label, FALSE, TRUE, 0);
+
+ viewport = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (viewport),
+ GTK_SHADOW_IN);
+
+ gtk_box_pack_start (GTK_BOX (pm), viewport, TRUE, TRUE, 0);
+
+ pm->priv->tree = gtk_tree_view_new ();
+ gtk_container_add (GTK_CONTAINER (viewport), pm->priv->tree);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), pm->priv->tree);
+
+ hbuttonbox = gtk_hbutton_box_new ();
+ gtk_box_pack_start (GTK_BOX (pm), hbuttonbox, FALSE, FALSE, 0);
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox), GTK_BUTTONBOX_END);
+ gtk_box_set_spacing (GTK_BOX (hbuttonbox), 8);
+
+ pm->priv->about_button = pluma_gtk_button_new_with_stock_icon (_("_About Plugin"),
+ GTK_STOCK_ABOUT);
+ gtk_container_add (GTK_CONTAINER (hbuttonbox), pm->priv->about_button);
+
+ pm->priv->configure_button = pluma_gtk_button_new_with_stock_icon (_("C_onfigure Plugin"),
+ GTK_STOCK_PREFERENCES);
+ gtk_container_add (GTK_CONTAINER (hbuttonbox), pm->priv->configure_button);
+
+ /* setup a window of a sane size. */
+ gtk_widget_set_size_request (GTK_WIDGET (viewport), 270, 100);
+
+ g_signal_connect (pm->priv->about_button,
+ "clicked",
+ G_CALLBACK (about_button_cb),
+ pm);
+ g_signal_connect (pm->priv->configure_button,
+ "clicked",
+ G_CALLBACK (configure_button_cb),
+ pm);
+
+ plugin_manager_construct_tree (pm);
+
+ /* get the plugin engine and populate the treeview */
+ pm->priv->engine = pluma_plugins_engine_get_default ();
+
+ g_signal_connect_after (pm->priv->engine,
+ "activate-plugin",
+ G_CALLBACK (plugin_toggled_cb),
+ pm);
+ g_signal_connect_after (pm->priv->engine,
+ "deactivate-plugin",
+ G_CALLBACK (plugin_toggled_cb),
+ pm);
+
+ if (pluma_plugins_engine_get_plugin_list (pm->priv->engine) != NULL)
+ {
+ plugin_manager_populate_lists (pm);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (pm->priv->about_button, FALSE);
+ gtk_widget_set_sensitive (pm->priv->configure_button, FALSE);
+ }
+}
+
+static void
+pluma_plugin_manager_finalize (GObject *object)
+{
+ PlumaPluginManager *pm = PLUMA_PLUGIN_MANAGER (object);
+
+ g_signal_handlers_disconnect_by_func (pm->priv->engine,
+ plugin_toggled_cb,
+ pm);
+
+ if (pm->priv->popup_menu)
+ gtk_widget_destroy (pm->priv->popup_menu);
+
+ G_OBJECT_CLASS (pluma_plugin_manager_parent_class)->finalize (object);
+
+}
+
+GtkWidget *pluma_plugin_manager_new (void)
+{
+ return g_object_new (PLUMA_TYPE_PLUGIN_MANAGER,0);
+}
diff --git a/pluma/pluma-plugin-manager.h b/pluma/pluma-plugin-manager.h
new file mode 100755
index 00000000..e70a525d
--- /dev/null
+++ b/pluma/pluma-plugin-manager.h
@@ -0,0 +1,83 @@
+/*
+ * pluma-plugin-manager.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PLUGIN_MANAGER_H__
+#define __PLUMA_PLUGIN_MANAGER_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_PLUGIN_MANAGER (pluma_plugin_manager_get_type())
+#define PLUMA_PLUGIN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PLUGIN_MANAGER, PlumaPluginManager))
+#define PLUMA_PLUGIN_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_PLUGIN_MANAGER, PlumaPluginManagerClass))
+#define PLUMA_IS_PLUGIN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_PLUGIN_MANAGER))
+#define PLUMA_IS_PLUGIN_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PLUGIN_MANAGER))
+#define PLUMA_PLUGIN_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_PLUGIN_MANAGER, PlumaPluginManagerClass))
+
+/* Private structure type */
+typedef struct _PlumaPluginManagerPrivate PlumaPluginManagerPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaPluginManager PlumaPluginManager;
+
+struct _PlumaPluginManager
+{
+ GtkVBox vbox;
+
+ /*< private > */
+ PlumaPluginManagerPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaPluginManagerClass PlumaPluginManagerClass;
+
+struct _PlumaPluginManagerClass
+{
+ GtkVBoxClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType pluma_plugin_manager_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_plugin_manager_new (void);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PLUGIN_MANAGER_H__ */
diff --git a/pluma/pluma-plugin.c b/pluma/pluma-plugin.c
new file mode 100755
index 00000000..b89ea11d
--- /dev/null
+++ b/pluma/pluma-plugin.c
@@ -0,0 +1,334 @@
+/*
+ * pluma-plugin.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pluma-plugin.h"
+#include "pluma-dirs.h"
+
+/* properties */
+enum {
+ PROP_0,
+ PROP_INSTALL_DIR,
+ PROP_DATA_DIR_NAME,
+ PROP_DATA_DIR
+};
+
+typedef struct _PlumaPluginPrivate PlumaPluginPrivate;
+
+struct _PlumaPluginPrivate
+{
+ gchar *install_dir;
+ gchar *data_dir_name;
+};
+
+#define PLUMA_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_PLUGIN, PlumaPluginPrivate))
+
+G_DEFINE_TYPE(PlumaPlugin, pluma_plugin, G_TYPE_OBJECT)
+
+static void
+dummy (PlumaPlugin *plugin, PlumaWindow *window)
+{
+ /* Empty */
+}
+
+static GtkWidget *
+create_configure_dialog (PlumaPlugin *plugin)
+{
+ return NULL;
+}
+
+static gboolean
+is_configurable (PlumaPlugin *plugin)
+{
+ return (PLUMA_PLUGIN_GET_CLASS (plugin)->create_configure_dialog !=
+ create_configure_dialog);
+}
+
+static void
+pluma_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_INSTALL_DIR:
+ g_value_take_string (value, pluma_plugin_get_install_dir (PLUMA_PLUGIN (object)));
+ break;
+ case PROP_DATA_DIR:
+ g_value_take_string (value, pluma_plugin_get_data_dir (PLUMA_PLUGIN (object)));
+ break;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+pluma_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaPluginPrivate *priv = PLUMA_PLUGIN_GET_PRIVATE (object);
+
+ switch (prop_id)
+ {
+ case PROP_INSTALL_DIR:
+ priv->install_dir = g_value_dup_string (value);
+ break;
+ case PROP_DATA_DIR_NAME:
+ priv->data_dir_name = g_value_dup_string (value);
+ break;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+pluma_plugin_finalize (GObject *object)
+{
+ PlumaPluginPrivate *priv = PLUMA_PLUGIN_GET_PRIVATE (object);
+
+ g_free (priv->install_dir);
+ g_free (priv->data_dir_name);
+
+ G_OBJECT_CLASS (pluma_plugin_parent_class)->finalize (object);
+}
+
+static void
+pluma_plugin_class_init (PlumaPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ klass->activate = dummy;
+ klass->deactivate = dummy;
+ klass->update_ui = dummy;
+
+ klass->create_configure_dialog = create_configure_dialog;
+ klass->is_configurable = is_configurable;
+
+ object_class->get_property = pluma_plugin_get_property;
+ object_class->set_property = pluma_plugin_set_property;
+ object_class->finalize = pluma_plugin_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_INSTALL_DIR,
+ g_param_spec_string ("install-dir",
+ "Install Directory",
+ "The directory where the plugin is installed",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /* the basename of the data dir is set at construction time by the plugin loader
+ * while the full path is constructed on the fly to take into account relocability
+ * that's why we have a writeonly prop and a readonly prop */
+ g_object_class_install_property (object_class,
+ PROP_DATA_DIR_NAME,
+ g_param_spec_string ("data-dir-name",
+ "Basename of the data directory",
+ "The basename of the directory where the plugin should look for its data files",
+ NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_DATA_DIR,
+ g_param_spec_string ("data-dir",
+ "Data Directory",
+ "The full path of the directory where the plugin should look for its data files",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_type_class_add_private (klass, sizeof (PlumaPluginPrivate));
+}
+
+static void
+pluma_plugin_init (PlumaPlugin *plugin)
+{
+ /* Empty */
+}
+
+/**
+ * pluma_plugin_get_install_dir:
+ * @plugin: a #PlumaPlugin
+ *
+ * Get the path of the directory where the plugin is installed.
+ *
+ * Return value: a newly allocated string with the path of the
+ * directory where the plugin is installed
+ */
+gchar *
+pluma_plugin_get_install_dir (PlumaPlugin *plugin)
+{
+ g_return_val_if_fail (PLUMA_IS_PLUGIN (plugin), NULL);
+
+ return g_strdup (PLUMA_PLUGIN_GET_PRIVATE (plugin)->install_dir);
+}
+
+/**
+ * pluma_plugin_get_data_dir:
+ * @plugin: a #PlumaPlugin
+ *
+ * Get the path of the directory where the plugin should look for
+ * its data files.
+ *
+ * Return value: a newly allocated string with the path of the
+ * directory where the plugin should look for its data files
+ */
+gchar *
+pluma_plugin_get_data_dir (PlumaPlugin *plugin)
+{
+ PlumaPluginPrivate *priv;
+ gchar *pluma_lib_dir;
+ gchar *data_dir;
+
+ g_return_val_if_fail (PLUMA_IS_PLUGIN (plugin), NULL);
+
+ priv = PLUMA_PLUGIN_GET_PRIVATE (plugin);
+
+ /* If it's a "user" plugin the data dir is
+ * install_dir/data_dir_name if instead it's a
+ * "system" plugin the data dir is under pluma_data_dir,
+ * so it's under $prefix/share/pluma-2/plugins/data_dir_name
+ * where data_dir_name usually it's the name of the plugin
+ */
+ pluma_lib_dir = pluma_dirs_get_pluma_lib_dir ();
+
+ /* CHECK: is checking the prefix enough or should we be more
+ * careful about normalizing paths etc? */
+ if (g_str_has_prefix (priv->install_dir, pluma_lib_dir))
+ {
+ gchar *pluma_data_dir;
+
+ pluma_data_dir = pluma_dirs_get_pluma_data_dir ();
+
+ data_dir = g_build_filename (pluma_data_dir,
+ "plugins",
+ priv->data_dir_name,
+ NULL);
+
+ g_free (pluma_data_dir);
+ }
+ else
+ {
+ data_dir = g_build_filename (priv->install_dir,
+ priv->data_dir_name,
+ NULL);
+ }
+
+ g_free (pluma_lib_dir);
+
+ return data_dir;
+}
+
+/**
+ * pluma_plugin_activate:
+ * @plugin: a #PlumaPlugin
+ * @window: a #PlumaWindow
+ *
+ * Activates the plugin.
+ */
+void
+pluma_plugin_activate (PlumaPlugin *plugin,
+ PlumaWindow *window)
+{
+ g_return_if_fail (PLUMA_IS_PLUGIN (plugin));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ PLUMA_PLUGIN_GET_CLASS (plugin)->activate (plugin, window);
+}
+
+/**
+ * pluma_plugin_deactivate:
+ * @plugin: a #PlumaPlugin
+ * @window: a #PlumaWindow
+ *
+ * Deactivates the plugin.
+ */
+void
+pluma_plugin_deactivate (PlumaPlugin *plugin,
+ PlumaWindow *window)
+{
+ g_return_if_fail (PLUMA_IS_PLUGIN (plugin));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ PLUMA_PLUGIN_GET_CLASS (plugin)->deactivate (plugin, window);
+}
+
+/**
+ * pluma_plugin_update_ui:
+ * @plugin: a #PlumaPlugin
+ * @window: a #PlumaWindow
+ *
+ * Triggers an update of the user interface to take into account state changes
+ * caused by the plugin.
+ */
+void
+pluma_plugin_update_ui (PlumaPlugin *plugin,
+ PlumaWindow *window)
+{
+ g_return_if_fail (PLUMA_IS_PLUGIN (plugin));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ PLUMA_PLUGIN_GET_CLASS (plugin)->update_ui (plugin, window);
+}
+
+/**
+ * pluma_plugin_is_configurable:
+ * @plugin: a #PlumaPlugin
+ *
+ * Whether the plugin is configurable.
+ *
+ * Returns: TRUE if the plugin is configurable:
+ */
+gboolean
+pluma_plugin_is_configurable (PlumaPlugin *plugin)
+{
+ g_return_val_if_fail (PLUMA_IS_PLUGIN (plugin), FALSE);
+
+ return PLUMA_PLUGIN_GET_CLASS (plugin)->is_configurable (plugin);
+}
+
+/**
+ * pluma_plugin_create_configure_dialog:
+ * @plugin: a #PlumaPlugin
+ *
+ * Creates the configure dialog widget for the plugin.
+ *
+ * Returns: the configure dialog widget for the plugin.
+ */
+GtkWidget *
+pluma_plugin_create_configure_dialog (PlumaPlugin *plugin)
+{
+ g_return_val_if_fail (PLUMA_IS_PLUGIN (plugin), NULL);
+
+ return PLUMA_PLUGIN_GET_CLASS (plugin)->create_configure_dialog (plugin);
+}
diff --git a/pluma/pluma-plugin.h b/pluma/pluma-plugin.h
new file mode 100755
index 00000000..6d20391d
--- /dev/null
+++ b/pluma/pluma-plugin.h
@@ -0,0 +1,240 @@
+/*
+ * pluma-plugin.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PLUGIN_H__
+#define __PLUMA_PLUGIN_H__
+
+#include <glib-object.h>
+
+#include <pluma/pluma-window.h>
+#include <pluma/pluma-debug.h>
+
+/* TODO: add a .h file that includes all the .h files normally needed to
+ * develop a plugin */
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_PLUGIN (pluma_plugin_get_type())
+#define PLUMA_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PLUGIN, PlumaPlugin))
+#define PLUMA_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_PLUGIN, PlumaPluginClass))
+#define PLUMA_IS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_PLUGIN))
+#define PLUMA_IS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PLUGIN))
+#define PLUMA_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_PLUGIN, PlumaPluginClass))
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaPlugin PlumaPlugin;
+
+struct _PlumaPlugin
+{
+ GObject parent;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaPluginClass PlumaPluginClass;
+
+struct _PlumaPluginClass
+{
+ GObjectClass parent_class;
+
+ /* Virtual public methods */
+
+ void (*activate) (PlumaPlugin *plugin,
+ PlumaWindow *window);
+ void (*deactivate) (PlumaPlugin *plugin,
+ PlumaWindow *window);
+
+ void (*update_ui) (PlumaPlugin *plugin,
+ PlumaWindow *window);
+
+ GtkWidget *(*create_configure_dialog)
+ (PlumaPlugin *plugin);
+
+ /* Plugins should not override this, it's handled automatically by
+ the PlumaPluginClass */
+ gboolean (*is_configurable)
+ (PlumaPlugin *plugin);
+
+ /* Padding for future expansion */
+ void (*_pluma_reserved1) (void);
+ void (*_pluma_reserved2) (void);
+ void (*_pluma_reserved3) (void);
+ void (*_pluma_reserved4) (void);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_plugin_get_type (void) G_GNUC_CONST;
+
+gchar *pluma_plugin_get_install_dir (PlumaPlugin *plugin);
+gchar *pluma_plugin_get_data_dir (PlumaPlugin *plugin);
+
+void pluma_plugin_activate (PlumaPlugin *plugin,
+ PlumaWindow *window);
+void pluma_plugin_deactivate (PlumaPlugin *plugin,
+ PlumaWindow *window);
+
+void pluma_plugin_update_ui (PlumaPlugin *plugin,
+ PlumaWindow *window);
+
+gboolean pluma_plugin_is_configurable (PlumaPlugin *plugin);
+GtkWidget *pluma_plugin_create_configure_dialog
+ (PlumaPlugin *plugin);
+
+/**
+ * PLUMA_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, CODE):
+ *
+ * Utility macro used to register plugins with additional code.
+ */
+#define PLUMA_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, CODE) \
+ G_DEFINE_DYNAMIC_TYPE_EXTENDED (PluginName, \
+ plugin_name, \
+ PLUMA_TYPE_PLUGIN, \
+ 0, \
+ GTypeModule *module G_GNUC_UNUSED = type_module; /* back compat */ \
+ CODE) \
+ \
+/* This is not very nice, but G_DEFINE_DYNAMIC wants it and our old macro \
+ * did not support it */ \
+static void \
+plugin_name##_class_finalize (PluginName##Class *klass) \
+{ \
+} \
+ \
+ \
+G_MODULE_EXPORT GType \
+register_pluma_plugin (GTypeModule *type_module) \
+{ \
+ plugin_name##_register_type (type_module); \
+ \
+ return plugin_name##_get_type(); \
+}
+
+/**
+ * PLUMA_PLUGIN_REGISTER_TYPE(PluginName, plugin_name):
+ *
+ * Utility macro used to register plugins.
+ */
+#define PLUMA_PLUGIN_REGISTER_TYPE(PluginName, plugin_name) \
+ PLUMA_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, ;)
+
+/**
+ * PLUMA_PLUGIN_DEFINE_TYPE_WITH_CODE(ObjectName, object_name, PARENT_TYPE, CODE):
+ *
+ * Utility macro used to register gobject types in plugins with additional code.
+ *
+ * Deprecated: use G_DEFINE_DYNAMIC_TYPE_EXTENDED instead
+ */
+#define PLUMA_PLUGIN_DEFINE_TYPE_WITH_CODE(ObjectName, object_name, PARENT_TYPE, CODE) \
+ \
+static GType g_define_type_id = 0; \
+ \
+GType \
+object_name##_get_type (void) \
+{ \
+ return g_define_type_id; \
+} \
+ \
+static void object_name##_init (ObjectName *self); \
+static void object_name##_class_init (ObjectName##Class *klass); \
+static gpointer object_name##_parent_class = NULL; \
+static void object_name##_class_intern_init (gpointer klass) \
+{ \
+ object_name##_parent_class = g_type_class_peek_parent (klass); \
+ object_name##_class_init ((ObjectName##Class *) klass); \
+} \
+ \
+GType \
+object_name##_register_type (GTypeModule *type_module) \
+{ \
+ GTypeModule *module G_GNUC_UNUSED = type_module; /* back compat */ \
+ static const GTypeInfo our_info = \
+ { \
+ sizeof (ObjectName##Class), \
+ NULL, /* base_init */ \
+ NULL, /* base_finalize */ \
+ (GClassInitFunc) object_name##_class_intern_init, \
+ NULL, \
+ NULL, /* class_data */ \
+ sizeof (ObjectName), \
+ 0, /* n_preallocs */ \
+ (GInstanceInitFunc) object_name##_init \
+ }; \
+ \
+ g_define_type_id = g_type_module_register_type (type_module, \
+ PARENT_TYPE, \
+ #ObjectName, \
+ &our_info, \
+ 0); \
+ \
+ CODE \
+ \
+ return g_define_type_id; \
+}
+
+
+/**
+ * PLUMA_PLUGIN_DEFINE_TYPE(ObjectName, object_name, PARENT_TYPE):
+ *
+ * Utility macro used to register gobject types in plugins.
+ *
+ * Deprecated: use G_DEFINE_DYNAMIC instead
+ */
+#define PLUMA_PLUGIN_DEFINE_TYPE(ObjectName, object_name, PARENT_TYPE) \
+ PLUMA_PLUGIN_DEFINE_TYPE_WITH_CODE(ObjectName, object_name, PARENT_TYPE, ;)
+
+/**
+ * PLUMA_PLUGIN_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init):
+ *
+ * Utility macro used to register interfaces for gobject types in plugins.
+ */
+#define PLUMA_PLUGIN_IMPLEMENT_INTERFACE(object_name, TYPE_IFACE, iface_init) \
+ const GInterfaceInfo object_name##_interface_info = \
+ { \
+ (GInterfaceInitFunc) iface_init, \
+ NULL, \
+ NULL \
+ }; \
+ \
+ g_type_module_add_interface (type_module, \
+ g_define_type_id, \
+ TYPE_IFACE, \
+ &object_name##_interface_info);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PLUGIN_H__ */
diff --git a/pluma/pluma-plugins-engine.c b/pluma/pluma-plugins-engine.c
new file mode 100755
index 00000000..4e27d718
--- /dev/null
+++ b/pluma/pluma-plugins-engine.c
@@ -0,0 +1,861 @@
+/*
+ * pluma-plugins-engine.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "pluma-plugins-engine.h"
+#include "pluma-plugin-info-priv.h"
+#include "pluma-plugin.h"
+#include "pluma-debug.h"
+#include "pluma-app.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-plugin-loader.h"
+#include "pluma-object-module.h"
+#include "pluma-dirs.h"
+
+#define PLUMA_PLUGINS_ENGINE_BASE_KEY "/apps/pluma-2/plugins"
+#define PLUMA_PLUGINS_ENGINE_KEY PLUMA_PLUGINS_ENGINE_BASE_KEY "/active-plugins"
+
+#define PLUGIN_EXT ".pluma-plugin"
+#define LOADER_EXT G_MODULE_SUFFIX
+
+typedef struct
+{
+ PlumaPluginLoader *loader;
+ PlumaObjectModule *module;
+} LoaderInfo;
+
+/* Signals */
+enum
+{
+ ACTIVATE_PLUGIN,
+ DEACTIVATE_PLUGIN,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE(PlumaPluginsEngine, pluma_plugins_engine, G_TYPE_OBJECT)
+
+struct _PlumaPluginsEnginePrivate
+{
+ GList *plugin_list;
+ GHashTable *loaders;
+
+ gboolean activate_from_prefs;
+};
+
+PlumaPluginsEngine *default_engine = NULL;
+
+static void pluma_plugins_engine_activate_plugin_real (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info);
+static void pluma_plugins_engine_deactivate_plugin_real (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info);
+
+typedef gboolean (*LoadDirCallback)(PlumaPluginsEngine *engine, const gchar *filename, gpointer userdata);
+
+static gboolean
+load_dir_real (PlumaPluginsEngine *engine,
+ const gchar *dir,
+ const gchar *suffix,
+ LoadDirCallback callback,
+ gpointer userdata)
+{
+ GError *error = NULL;
+ GDir *d;
+ const gchar *dirent;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (dir != NULL, TRUE);
+
+ pluma_debug_message (DEBUG_PLUGINS, "DIR: %s", dir);
+
+ d = g_dir_open (dir, 0, &error);
+ if (!d)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return TRUE;
+ }
+
+ while ((dirent = g_dir_read_name (d)))
+ {
+ gchar *filename;
+
+ if (!g_str_has_suffix (dirent, suffix))
+ continue;
+
+ filename = g_build_filename (dir, dirent, NULL);
+
+ ret = callback (engine, filename, userdata);
+
+ g_free (filename);
+
+ if (!ret)
+ break;
+ }
+
+ g_dir_close (d);
+ return ret;
+}
+
+static gboolean
+load_plugin_info (PlumaPluginsEngine *engine,
+ const gchar *filename,
+ gpointer userdata)
+{
+ PlumaPluginInfo *info;
+
+ info = _pluma_plugin_info_new (filename);
+
+ if (info == NULL)
+ return TRUE;
+
+ /* If a plugin with this name has already been loaded
+ * drop this one (user plugins override system plugins) */
+ if (pluma_plugins_engine_get_plugin_info (engine, pluma_plugin_info_get_module_name (info)) != NULL)
+ {
+ pluma_debug_message (DEBUG_PLUGINS, "Two or more plugins named '%s'. "
+ "Only the first will be considered.\n",
+ pluma_plugin_info_get_module_name (info));
+
+ _pluma_plugin_info_unref (info);
+
+ return TRUE;
+ }
+
+ engine->priv->plugin_list = g_list_prepend (engine->priv->plugin_list, info);
+
+ pluma_debug_message (DEBUG_PLUGINS, "Plugin %s loaded", info->name);
+ return TRUE;
+}
+
+static void
+load_all_plugins (PlumaPluginsEngine *engine)
+{
+ gchar *plugin_dir;
+ const gchar *pdirs_env = NULL;
+
+ /* load user plugins */
+ plugin_dir = pluma_dirs_get_user_plugins_dir ();
+ if (g_file_test (plugin_dir, G_FILE_TEST_IS_DIR))
+ {
+ load_dir_real (engine,
+ plugin_dir,
+ PLUGIN_EXT,
+ load_plugin_info,
+ NULL);
+
+ }
+ g_free (plugin_dir);
+
+ /* load system plugins */
+ pdirs_env = g_getenv ("PLUMA_PLUGINS_PATH");
+
+ pluma_debug_message (DEBUG_PLUGINS, "PLUMA_PLUGINS_PATH=%s", pdirs_env);
+
+ if (pdirs_env != NULL)
+ {
+ gchar **pdirs;
+ gint i;
+
+ pdirs = g_strsplit (pdirs_env, G_SEARCHPATH_SEPARATOR_S, 0);
+
+ for (i = 0; pdirs[i] != NULL; i++)
+ {
+ if (!load_dir_real (engine,
+ pdirs[i],
+ PLUGIN_EXT,
+ load_plugin_info,
+ NULL))
+ {
+ break;
+ }
+ }
+
+ g_strfreev (pdirs);
+ }
+ else
+ {
+ plugin_dir = pluma_dirs_get_pluma_plugins_dir ();
+
+ load_dir_real (engine,
+ plugin_dir,
+ PLUGIN_EXT,
+ load_plugin_info,
+ NULL);
+
+ g_free (plugin_dir);
+ }
+}
+
+static guint
+hash_lowercase (gconstpointer data)
+{
+ gchar *lowercase;
+ guint ret;
+
+ lowercase = g_ascii_strdown ((const gchar *)data, -1);
+ ret = g_str_hash (lowercase);
+ g_free (lowercase);
+
+ return ret;
+}
+
+static gboolean
+equal_lowercase (gconstpointer a, gconstpointer b)
+{
+ return g_ascii_strcasecmp ((const gchar *)a, (const gchar *)b) == 0;
+}
+
+static void
+loader_destroy (LoaderInfo *info)
+{
+ if (!info)
+ return;
+
+ if (info->loader)
+ g_object_unref (info->loader);
+
+ g_free (info);
+}
+
+static void
+add_loader (PlumaPluginsEngine *engine,
+ const gchar *loader_id,
+ PlumaObjectModule *module)
+{
+ LoaderInfo *info;
+
+ info = g_new (LoaderInfo, 1);
+ info->loader = NULL;
+ info->module = module;
+
+ g_hash_table_insert (engine->priv->loaders, g_strdup (loader_id), info);
+}
+
+static void
+pluma_plugins_engine_init (PlumaPluginsEngine *engine)
+{
+ pluma_debug (DEBUG_PLUGINS);
+
+ if (!g_module_supported ())
+ {
+ g_warning ("pluma is not able to initialize the plugins engine.");
+ return;
+ }
+
+ engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine,
+ PLUMA_TYPE_PLUGINS_ENGINE,
+ PlumaPluginsEnginePrivate);
+
+ load_all_plugins (engine);
+
+ /* make sure that the first reactivation will read active plugins
+ from the prefs */
+ engine->priv->activate_from_prefs = TRUE;
+
+ /* mapping from loadername -> loader object */
+ engine->priv->loaders = g_hash_table_new_full (hash_lowercase,
+ equal_lowercase,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)loader_destroy);
+}
+
+static void
+loader_garbage_collect (const char *id, LoaderInfo *info)
+{
+ if (info->loader)
+ pluma_plugin_loader_garbage_collect (info->loader);
+}
+
+void
+pluma_plugins_engine_garbage_collect (PlumaPluginsEngine *engine)
+{
+ g_hash_table_foreach (engine->priv->loaders,
+ (GHFunc) loader_garbage_collect,
+ NULL);
+}
+
+static void
+pluma_plugins_engine_finalize (GObject *object)
+{
+ PlumaPluginsEngine *engine = PLUMA_PLUGINS_ENGINE (object);
+ GList *item;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ /* Firs deactivate all plugins */
+ for (item = engine->priv->plugin_list; item; item = item->next)
+ {
+ PlumaPluginInfo *info = PLUMA_PLUGIN_INFO (item->data);
+
+ if (pluma_plugin_info_is_active (info))
+ pluma_plugins_engine_deactivate_plugin_real (engine, info);
+ }
+
+ /* unref the loaders */
+ g_hash_table_destroy (engine->priv->loaders);
+
+ /* and finally free the infos */
+ for (item = engine->priv->plugin_list; item; item = item->next)
+ {
+ PlumaPluginInfo *info = PLUMA_PLUGIN_INFO (item->data);
+
+ _pluma_plugin_info_unref (info);
+ }
+
+ g_list_free (engine->priv->plugin_list);
+
+ G_OBJECT_CLASS (pluma_plugins_engine_parent_class)->finalize (object);
+}
+
+static void
+pluma_plugins_engine_class_init (PlumaPluginsEngineClass *klass)
+{
+ GType the_type = G_TYPE_FROM_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_plugins_engine_finalize;
+ klass->activate_plugin = pluma_plugins_engine_activate_plugin_real;
+ klass->deactivate_plugin = pluma_plugins_engine_deactivate_plugin_real;
+
+ signals[ACTIVATE_PLUGIN] =
+ g_signal_new ("activate-plugin",
+ the_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaPluginsEngineClass, activate_plugin),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_PLUGIN_INFO | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ signals[DEACTIVATE_PLUGIN] =
+ g_signal_new ("deactivate-plugin",
+ the_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaPluginsEngineClass, deactivate_plugin),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_PLUGIN_INFO | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ g_type_class_add_private (klass, sizeof (PlumaPluginsEnginePrivate));
+}
+
+static gboolean
+load_loader (PlumaPluginsEngine *engine,
+ const gchar *filename,
+ gpointer data)
+{
+ PlumaObjectModule *module;
+ gchar *base;
+ gchar *path;
+ const gchar *id;
+ GType type;
+
+ /* try to load in the module */
+ path = g_path_get_dirname (filename);
+ base = g_path_get_basename (filename);
+
+ /* for now they are all resident */
+ module = pluma_object_module_new (base,
+ path,
+ "register_pluma_plugin_loader",
+ TRUE);
+
+ g_free (base);
+ g_free (path);
+
+ /* make sure to load the type definition */
+ if (!g_type_module_use (G_TYPE_MODULE (module)))
+ {
+ g_object_unref (module);
+ g_warning ("Plugin loader module `%s' could not be loaded", filename);
+
+ return TRUE;
+ }
+
+ /* get the exported type and check the name as exported by the
+ * loader interface */
+ type = pluma_object_module_get_object_type (module);
+ id = pluma_plugin_loader_type_get_id (type);
+
+ add_loader (engine, id, module);
+ g_type_module_unuse (G_TYPE_MODULE (module));
+
+ return TRUE;
+}
+
+static void
+ensure_loader (LoaderInfo *info)
+{
+ if (info->loader == NULL && info->module != NULL)
+ {
+ /* create a new loader object */
+ PlumaPluginLoader *loader;
+ loader = (PlumaPluginLoader *)pluma_object_module_new_object (info->module, NULL);
+
+ if (loader == NULL || !PLUMA_IS_PLUGIN_LOADER (loader))
+ {
+ g_warning ("Loader object is not a valid PlumaPluginLoader instance");
+
+ if (loader != NULL && G_IS_OBJECT (loader))
+ g_object_unref (loader);
+ }
+ else
+ {
+ info->loader = loader;
+ }
+ }
+}
+
+static PlumaPluginLoader *
+get_plugin_loader (PlumaPluginsEngine *engine, PlumaPluginInfo *info)
+{
+ const gchar *loader_id;
+ LoaderInfo *loader_info;
+
+ loader_id = info->loader;
+
+ loader_info = (LoaderInfo *)g_hash_table_lookup (
+ engine->priv->loaders,
+ loader_id);
+
+ if (loader_info == NULL)
+ {
+ gchar *loader_dir;
+
+ loader_dir = pluma_dirs_get_pluma_plugin_loaders_dir ();
+
+ /* loader could not be found in the hash, try to find it by
+ scanning */
+ load_dir_real (engine,
+ loader_dir,
+ LOADER_EXT,
+ (LoadDirCallback)load_loader,
+ NULL);
+ g_free (loader_dir);
+
+ loader_info = (LoaderInfo *)g_hash_table_lookup (
+ engine->priv->loaders,
+ loader_id);
+ }
+
+ if (loader_info == NULL)
+ {
+ /* cache non-existent so we don't scan again */
+ add_loader (engine, loader_id, NULL);
+ return NULL;
+ }
+
+ ensure_loader (loader_info);
+ return loader_info->loader;
+}
+
+PlumaPluginsEngine *
+pluma_plugins_engine_get_default (void)
+{
+ if (default_engine != NULL)
+ return default_engine;
+
+ default_engine = PLUMA_PLUGINS_ENGINE (g_object_new (PLUMA_TYPE_PLUGINS_ENGINE, NULL));
+ g_object_add_weak_pointer (G_OBJECT (default_engine),
+ (gpointer) &default_engine);
+ return default_engine;
+}
+
+const GList *
+pluma_plugins_engine_get_plugin_list (PlumaPluginsEngine *engine)
+{
+ pluma_debug (DEBUG_PLUGINS);
+
+ return engine->priv->plugin_list;
+}
+
+static gint
+compare_plugin_info_and_name (PlumaPluginInfo *info,
+ const gchar *module_name)
+{
+ return strcmp (pluma_plugin_info_get_module_name (info), module_name);
+}
+
+PlumaPluginInfo *
+pluma_plugins_engine_get_plugin_info (PlumaPluginsEngine *engine,
+ const gchar *name)
+{
+ GList *l = g_list_find_custom (engine->priv->plugin_list,
+ name,
+ (GCompareFunc) compare_plugin_info_and_name);
+
+ return l == NULL ? NULL : (PlumaPluginInfo *) l->data;
+}
+
+static void
+save_active_plugin_list (PlumaPluginsEngine *engine)
+{
+ GSList *active_plugins = NULL;
+ GList *l;
+
+ for (l = engine->priv->plugin_list; l != NULL; l = l->next)
+ {
+ PlumaPluginInfo *info = (PlumaPluginInfo *) l->data;
+
+ if (pluma_plugin_info_is_active (info))
+ {
+ active_plugins = g_slist_prepend (active_plugins,
+ (gpointer)pluma_plugin_info_get_module_name (info));
+ }
+ }
+
+ pluma_prefs_manager_set_active_plugins (active_plugins);
+
+ g_slist_free (active_plugins);
+}
+
+static gboolean
+load_plugin (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info)
+{
+ PlumaPluginLoader *loader;
+ gchar *path;
+
+ if (pluma_plugin_info_is_active (info))
+ return TRUE;
+
+ if (!pluma_plugin_info_is_available (info))
+ return FALSE;
+
+ loader = get_plugin_loader (engine, info);
+
+ if (loader == NULL)
+ {
+ g_warning ("Could not find loader `%s' for plugin `%s'", info->loader, info->name);
+ info->available = FALSE;
+ return FALSE;
+ }
+
+ path = g_path_get_dirname (info->file);
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ info->plugin = pluma_plugin_loader_load (loader, info, path);
+
+ g_free (path);
+
+ if (info->plugin == NULL)
+ {
+ g_warning ("Error loading plugin '%s'", info->name);
+ info->available = FALSE;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+pluma_plugins_engine_activate_plugin_real (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info)
+{
+ const GList *wins;
+
+ if (!load_plugin (engine, info))
+ return;
+
+ for (wins = pluma_app_get_windows (pluma_app_get_default ());
+ wins != NULL;
+ wins = wins->next)
+ {
+ pluma_plugin_activate (info->plugin, PLUMA_WINDOW (wins->data));
+ }
+}
+
+gboolean
+pluma_plugins_engine_activate_plugin (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info)
+{
+ pluma_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ if (!pluma_plugin_info_is_available (info))
+ return FALSE;
+
+ if (pluma_plugin_info_is_active (info))
+ return TRUE;
+
+ g_signal_emit (engine, signals[ACTIVATE_PLUGIN], 0, info);
+
+ if (pluma_plugin_info_is_active (info))
+ save_active_plugin_list (engine);
+
+ return pluma_plugin_info_is_active (info);
+}
+
+static void
+call_plugin_deactivate (PlumaPlugin *plugin,
+ PlumaWindow *window)
+{
+ pluma_plugin_deactivate (plugin, window);
+
+ /* ensure update of ui manager, because we suspect it does something
+ with expected static strings in the type module (when unloaded the
+ strings don't exist anymore, and ui manager updates in an idle
+ func) */
+ gtk_ui_manager_ensure_update (pluma_window_get_ui_manager (window));
+}
+
+static void
+pluma_plugins_engine_deactivate_plugin_real (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info)
+{
+ const GList *wins;
+ PlumaPluginLoader *loader;
+
+ if (!pluma_plugin_info_is_active (info) ||
+ !pluma_plugin_info_is_available (info))
+ return;
+
+ for (wins = pluma_app_get_windows (pluma_app_get_default ());
+ wins != NULL;
+ wins = wins->next)
+ {
+ call_plugin_deactivate (info->plugin, PLUMA_WINDOW (wins->data));
+ }
+
+ /* first unref the plugin (the loader still has one) */
+ g_object_unref (info->plugin);
+
+ /* find the loader and tell it to gc and unload the plugin */
+ loader = get_plugin_loader (engine, info);
+
+ pluma_plugin_loader_garbage_collect (loader);
+ pluma_plugin_loader_unload (loader, info);
+
+ info->plugin = NULL;
+}
+
+gboolean
+pluma_plugins_engine_deactivate_plugin (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info)
+{
+ pluma_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ if (!pluma_plugin_info_is_active (info))
+ return TRUE;
+
+ g_signal_emit (engine, signals[DEACTIVATE_PLUGIN], 0, info);
+ if (!pluma_plugin_info_is_active (info))
+ save_active_plugin_list (engine);
+
+ return !pluma_plugin_info_is_active (info);
+}
+
+void
+pluma_plugins_engine_activate_plugins (PlumaPluginsEngine *engine,
+ PlumaWindow *window)
+{
+ GSList *active_plugins = NULL;
+ GList *pl;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (PLUMA_IS_PLUGINS_ENGINE (engine));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ /* the first time, we get the 'active' plugins from mateconf */
+ if (engine->priv->activate_from_prefs)
+ {
+ active_plugins = pluma_prefs_manager_get_active_plugins ();
+ }
+
+ for (pl = engine->priv->plugin_list; pl; pl = pl->next)
+ {
+ PlumaPluginInfo *info = (PlumaPluginInfo*)pl->data;
+
+ if (engine->priv->activate_from_prefs &&
+ g_slist_find_custom (active_plugins,
+ pluma_plugin_info_get_module_name (info),
+ (GCompareFunc)strcmp) == NULL)
+ continue;
+
+ /* If plugin is not active, don't try to activate/load it */
+ if (!engine->priv->activate_from_prefs &&
+ !pluma_plugin_info_is_active (info))
+ continue;
+
+ if (load_plugin (engine, info))
+ pluma_plugin_activate (info->plugin,
+ window);
+ }
+
+ if (engine->priv->activate_from_prefs)
+ {
+ g_slist_foreach (active_plugins, (GFunc) g_free, NULL);
+ g_slist_free (active_plugins);
+ engine->priv->activate_from_prefs = FALSE;
+ }
+
+ pluma_debug_message (DEBUG_PLUGINS, "End");
+
+ /* also call update_ui after activation */
+ pluma_plugins_engine_update_plugins_ui (engine, window);
+}
+
+void
+pluma_plugins_engine_deactivate_plugins (PlumaPluginsEngine *engine,
+ PlumaWindow *window)
+{
+ GList *pl;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (PLUMA_IS_PLUGINS_ENGINE (engine));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ for (pl = engine->priv->plugin_list; pl; pl = pl->next)
+ {
+ PlumaPluginInfo *info = (PlumaPluginInfo*)pl->data;
+
+ /* check if the plugin is actually active */
+ if (!pluma_plugin_info_is_active (info))
+ continue;
+
+ /* call deactivate for the plugin for this window */
+ pluma_plugin_deactivate (info->plugin, window);
+ }
+
+ pluma_debug_message (DEBUG_PLUGINS, "End");
+}
+
+void
+pluma_plugins_engine_update_plugins_ui (PlumaPluginsEngine *engine,
+ PlumaWindow *window)
+{
+ GList *pl;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (PLUMA_IS_PLUGINS_ENGINE (engine));
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ /* call update_ui for all active plugins */
+ for (pl = engine->priv->plugin_list; pl; pl = pl->next)
+ {
+ PlumaPluginInfo *info = (PlumaPluginInfo*)pl->data;
+
+ if (!pluma_plugin_info_is_active (info))
+ continue;
+
+ pluma_debug_message (DEBUG_PLUGINS, "Updating UI of %s", info->name);
+ pluma_plugin_update_ui (info->plugin, window);
+ }
+}
+
+void
+pluma_plugins_engine_configure_plugin (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info,
+ GtkWindow *parent)
+{
+ GtkWidget *conf_dlg;
+
+ GtkWindowGroup *wg;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (info != NULL);
+
+ conf_dlg = pluma_plugin_create_configure_dialog (info->plugin);
+ g_return_if_fail (conf_dlg != NULL);
+ gtk_window_set_transient_for (GTK_WINDOW (conf_dlg),
+ parent);
+
+ wg = gtk_window_get_group (parent);
+ if (wg == NULL)
+ {
+ wg = gtk_window_group_new ();
+ gtk_window_group_add_window (wg, parent);
+ }
+
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (conf_dlg));
+
+ gtk_window_set_modal (GTK_WINDOW (conf_dlg), TRUE);
+ gtk_widget_show (conf_dlg);
+}
+
+void
+pluma_plugins_engine_active_plugins_changed (PlumaPluginsEngine *engine)
+{
+ gboolean to_activate;
+ GSList *active_plugins;
+ GList *pl;
+
+ pluma_debug (DEBUG_PLUGINS);
+
+ active_plugins = pluma_prefs_manager_get_active_plugins ();
+
+ for (pl = engine->priv->plugin_list; pl; pl = pl->next)
+ {
+ PlumaPluginInfo *info = (PlumaPluginInfo*)pl->data;
+
+ if (!pluma_plugin_info_is_available (info))
+ continue;
+
+ to_activate = (g_slist_find_custom (active_plugins,
+ pluma_plugin_info_get_module_name (info),
+ (GCompareFunc)strcmp) != NULL);
+
+ if (!pluma_plugin_info_is_active (info) && to_activate)
+ g_signal_emit (engine, signals[ACTIVATE_PLUGIN], 0, info);
+ else if (pluma_plugin_info_is_active (info) && !to_activate)
+ g_signal_emit (engine, signals[DEACTIVATE_PLUGIN], 0, info);
+ }
+
+ g_slist_foreach (active_plugins, (GFunc) g_free, NULL);
+ g_slist_free (active_plugins);
+}
+
+void
+pluma_plugins_engine_rescan_plugins (PlumaPluginsEngine *engine)
+{
+ pluma_debug (DEBUG_PLUGINS);
+
+ load_all_plugins (engine);
+}
diff --git a/pluma/pluma-plugins-engine.h b/pluma/pluma-plugins-engine.h
new file mode 100755
index 00000000..2febf012
--- /dev/null
+++ b/pluma/pluma-plugins-engine.h
@@ -0,0 +1,107 @@
+/*
+ * pluma-plugins-engine.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PLUGINS_ENGINE_H__
+#define __PLUMA_PLUGINS_ENGINE_H__
+
+#include <glib.h>
+#include "pluma-window.h"
+#include "pluma-plugin-info.h"
+#include "pluma-plugin.h"
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_PLUGINS_ENGINE (pluma_plugins_engine_get_type ())
+#define PLUMA_PLUGINS_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PLUGINS_ENGINE, PlumaPluginsEngine))
+#define PLUMA_PLUGINS_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_PLUGINS_ENGINE, PlumaPluginsEngineClass))
+#define PLUMA_IS_PLUGINS_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_PLUGINS_ENGINE))
+#define PLUMA_IS_PLUGINS_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PLUGINS_ENGINE))
+#define PLUMA_PLUGINS_ENGINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_PLUGINS_ENGINE, PlumaPluginsEngineClass))
+
+typedef struct _PlumaPluginsEngine PlumaPluginsEngine;
+typedef struct _PlumaPluginsEnginePrivate PlumaPluginsEnginePrivate;
+
+struct _PlumaPluginsEngine
+{
+ GObject parent;
+ PlumaPluginsEnginePrivate *priv;
+};
+
+typedef struct _PlumaPluginsEngineClass PlumaPluginsEngineClass;
+
+struct _PlumaPluginsEngineClass
+{
+ GObjectClass parent_class;
+
+ void (* activate_plugin) (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info);
+
+ void (* deactivate_plugin) (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info);
+};
+
+GType pluma_plugins_engine_get_type (void) G_GNUC_CONST;
+
+PlumaPluginsEngine *pluma_plugins_engine_get_default (void);
+
+void pluma_plugins_engine_garbage_collect (PlumaPluginsEngine *engine);
+
+const GList *pluma_plugins_engine_get_plugin_list (PlumaPluginsEngine *engine);
+
+PlumaPluginInfo *pluma_plugins_engine_get_plugin_info (PlumaPluginsEngine *engine,
+ const gchar *name);
+
+/* plugin load and unloading (overall, for all windows) */
+gboolean pluma_plugins_engine_activate_plugin (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info);
+gboolean pluma_plugins_engine_deactivate_plugin (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info);
+
+void pluma_plugins_engine_configure_plugin (PlumaPluginsEngine *engine,
+ PlumaPluginInfo *info,
+ GtkWindow *parent);
+
+/* plugin activation/deactivation per window, private to PlumaWindow */
+void pluma_plugins_engine_activate_plugins (PlumaPluginsEngine *engine,
+ PlumaWindow *window);
+void pluma_plugins_engine_deactivate_plugins (PlumaPluginsEngine *engine,
+ PlumaWindow *window);
+void pluma_plugins_engine_update_plugins_ui (PlumaPluginsEngine *engine,
+ PlumaWindow *window);
+
+/* private for mateconf notification */
+void pluma_plugins_engine_active_plugins_changed
+ (PlumaPluginsEngine *engine);
+
+void pluma_plugins_engine_rescan_plugins (PlumaPluginsEngine *engine);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PLUGINS_ENGINE_H__ */
diff --git a/pluma/pluma-prefs-manager-app.c b/pluma/pluma-prefs-manager-app.c
new file mode 100755
index 00000000..091bc9de
--- /dev/null
+++ b/pluma/pluma-prefs-manager-app.c
@@ -0,0 +1,1620 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-prefs-manager.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2003. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "pluma-prefs-manager.h"
+#include "pluma-prefs-manager-private.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-app.h"
+#include "pluma-debug.h"
+#include "pluma-view.h"
+#include "pluma-window.h"
+#include "pluma-window-private.h"
+#include "pluma-plugins-engine.h"
+#include "pluma-style-scheme-manager.h"
+#include "pluma-dirs.h"
+
+static void pluma_prefs_manager_editor_font_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_system_font_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_tabs_size_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_wrap_mode_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_line_numbers_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_auto_indent_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_undo_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_right_margin_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_smart_home_end_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_hl_current_line_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_bracket_matching_changed(MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_syntax_hl_enable_changed(MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_search_hl_enable_changed(MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_source_style_scheme_changed
+ (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_max_recents_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_auto_save_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_active_plugins_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static void pluma_prefs_manager_lockdown_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+/* GUI state is serialized to a .desktop file, not in mateconf */
+
+#define PLUMA_STATE_DEFAULT_WINDOW_STATE 0
+#define PLUMA_STATE_DEFAULT_WINDOW_WIDTH 650
+#define PLUMA_STATE_DEFAULT_WINDOW_HEIGHT 500
+#define PLUMA_STATE_DEFAULT_SIDE_PANEL_SIZE 200
+#define PLUMA_STATE_DEFAULT_BOTTOM_PANEL_SIZE 140
+
+#define PLUMA_STATE_FILE_LOCATION "pluma-2"
+
+#define PLUMA_STATE_WINDOW_GROUP "window"
+#define PLUMA_STATE_WINDOW_STATE "state"
+#define PLUMA_STATE_WINDOW_HEIGHT "height"
+#define PLUMA_STATE_WINDOW_WIDTH "width"
+#define PLUMA_STATE_SIDE_PANEL_SIZE "side_panel_size"
+#define PLUMA_STATE_BOTTOM_PANEL_SIZE "bottom_panel_size"
+#define PLUMA_STATE_SIDE_PANEL_ACTIVE_PAGE "side_panel_active_page"
+#define PLUMA_STATE_BOTTOM_PANEL_ACTIVE_PAGE "bottom_panel_active_page"
+
+#define PLUMA_STATE_FILEFILTER_GROUP "filefilter"
+#define PLUMA_STATE_FILEFILTER_ID "id"
+
+static gint window_state = -1;
+static gint window_height = -1;
+static gint window_width = -1;
+static gint side_panel_size = -1;
+static gint bottom_panel_size = -1;
+static gint side_panel_active_page = -1;
+static gint bottom_panel_active_page = -1;
+static gint active_file_filter = -1;
+
+
+static gchar *
+get_state_filename (void)
+{
+ gchar *config_dir;
+ gchar *filename = NULL;
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+
+ if (config_dir != NULL)
+ {
+ filename = g_build_filename (config_dir,
+ PLUMA_STATE_FILE_LOCATION,
+ NULL);
+ g_free (config_dir);
+ }
+
+ return filename;
+}
+
+static GKeyFile *
+get_pluma_state_file (void)
+{
+ static GKeyFile *state_file = NULL;
+
+ if (state_file == NULL)
+ {
+ gchar *filename;
+ GError *err = NULL;
+
+ state_file = g_key_file_new ();
+
+ filename = get_state_filename ();
+
+ if (!g_key_file_load_from_file (state_file,
+ filename,
+ G_KEY_FILE_NONE,
+ &err))
+ {
+ if (err->domain != G_FILE_ERROR ||
+ err->code != G_FILE_ERROR_NOENT)
+ {
+ g_warning ("Could not load pluma state file: %s\n",
+ err->message);
+ }
+
+ g_error_free (err);
+ }
+
+ g_free (filename);
+ }
+
+ return state_file;
+}
+
+static void
+pluma_state_get_int (const gchar *group,
+ const gchar *key,
+ gint defval,
+ gint *result)
+{
+ GKeyFile *state_file;
+ gint res;
+ GError *err = NULL;
+
+ state_file = get_pluma_state_file ();
+ res = g_key_file_get_integer (state_file,
+ group,
+ key,
+ &err);
+
+ if (err != NULL)
+ {
+ if ((err->domain != G_KEY_FILE_ERROR) ||
+ ((err->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND &&
+ err->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND)))
+ {
+ g_warning ("Could not get state value %s::%s : %s\n",
+ group,
+ key,
+ err->message);
+ }
+
+ *result = defval;
+ g_error_free (err);
+ }
+ else
+ {
+ *result = res;
+ }
+}
+
+static void
+pluma_state_set_int (const gchar *group,
+ const gchar *key,
+ gint value)
+{
+ GKeyFile *state_file;
+
+ state_file = get_pluma_state_file ();
+ g_key_file_set_integer (state_file,
+ group,
+ key,
+ value);
+}
+
+static gboolean
+pluma_state_file_sync (void)
+{
+ GKeyFile *state_file;
+ gchar *config_dir;
+ gchar *filename = NULL;
+ gchar *content = NULL;
+ gsize length;
+ gint res;
+ GError *err = NULL;
+ gboolean ret = FALSE;
+
+ state_file = get_pluma_state_file ();
+ g_return_val_if_fail (state_file != NULL, FALSE);
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+ if (config_dir == NULL)
+ {
+ g_warning ("Could not get config directory\n");
+ return ret;
+ }
+
+ res = g_mkdir_with_parents (config_dir, 0755);
+ if (res < 0)
+ {
+ g_warning ("Could not create config directory\n");
+ goto out;
+ }
+
+ content = g_key_file_to_data (state_file,
+ &length,
+ &err);
+
+ if (err != NULL)
+ {
+ g_warning ("Could not get data from state file: %s\n",
+ err->message);
+ goto out;
+ }
+
+ if (content != NULL)
+ {
+ filename = get_state_filename ();
+ if (!g_file_set_contents (filename,
+ content,
+ length,
+ &err))
+ {
+ g_warning ("Could not write pluma state file: %s\n",
+ err->message);
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+
+ out:
+ if (err != NULL)
+ g_error_free (err);
+
+ g_free (config_dir);
+ g_free (filename);
+ g_free (content);
+
+ return ret;
+}
+
+/* Window state */
+gint
+pluma_prefs_manager_get_window_state (void)
+{
+ if (window_state == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_WINDOW_STATE,
+ PLUMA_STATE_DEFAULT_WINDOW_STATE,
+ &window_state);
+ }
+
+ return window_state;
+}
+
+void
+pluma_prefs_manager_set_window_state (gint ws)
+{
+ g_return_if_fail (ws > -1);
+
+ window_state = ws;
+
+ pluma_state_set_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_WINDOW_STATE,
+ ws);
+}
+
+gboolean
+pluma_prefs_manager_window_state_can_set (void)
+{
+ return TRUE;
+}
+
+/* Window size */
+void
+pluma_prefs_manager_get_window_size (gint *width, gint *height)
+{
+ g_return_if_fail (width != NULL && height != NULL);
+
+ if (window_width == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_WINDOW_WIDTH,
+ PLUMA_STATE_DEFAULT_WINDOW_WIDTH,
+ &window_width);
+ }
+
+ if (window_height == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_WINDOW_HEIGHT,
+ PLUMA_STATE_DEFAULT_WINDOW_HEIGHT,
+ &window_height);
+ }
+
+ *width = window_width;
+ *height = window_height;
+}
+
+void
+pluma_prefs_manager_get_default_window_size (gint *width, gint *height)
+{
+ g_return_if_fail (width != NULL && height != NULL);
+
+ *width = PLUMA_STATE_DEFAULT_WINDOW_WIDTH;
+ *height = PLUMA_STATE_DEFAULT_WINDOW_HEIGHT;
+}
+
+void
+pluma_prefs_manager_set_window_size (gint width, gint height)
+{
+ g_return_if_fail (width > -1 && height > -1);
+
+ window_width = width;
+ window_height = height;
+
+ pluma_state_set_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_WINDOW_WIDTH,
+ width);
+ pluma_state_set_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_WINDOW_HEIGHT,
+ height);
+}
+
+gboolean
+pluma_prefs_manager_window_size_can_set (void)
+{
+ return TRUE;
+}
+
+/* Side panel */
+gint
+pluma_prefs_manager_get_side_panel_size (void)
+{
+ if (side_panel_size == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_SIDE_PANEL_SIZE,
+ PLUMA_STATE_DEFAULT_SIDE_PANEL_SIZE,
+ &side_panel_size);
+ }
+
+ return side_panel_size;
+}
+
+gint
+pluma_prefs_manager_get_default_side_panel_size (void)
+{
+ return PLUMA_STATE_DEFAULT_SIDE_PANEL_SIZE;
+}
+
+void
+pluma_prefs_manager_set_side_panel_size (gint ps)
+{
+ g_return_if_fail (ps > -1);
+
+ if (side_panel_size == ps)
+ return;
+
+ side_panel_size = ps;
+ pluma_state_set_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_SIDE_PANEL_SIZE,
+ ps);
+}
+
+gboolean
+pluma_prefs_manager_side_panel_size_can_set (void)
+{
+ return TRUE;
+}
+
+gint
+pluma_prefs_manager_get_side_panel_active_page (void)
+{
+ if (side_panel_active_page == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_SIDE_PANEL_ACTIVE_PAGE,
+ 0,
+ &side_panel_active_page);
+ }
+
+ return side_panel_active_page;
+}
+
+void
+pluma_prefs_manager_set_side_panel_active_page (gint id)
+{
+ if (side_panel_active_page == id)
+ return;
+
+ side_panel_active_page = id;
+ pluma_state_set_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_SIDE_PANEL_ACTIVE_PAGE,
+ id);
+}
+
+gboolean
+pluma_prefs_manager_side_panel_active_page_can_set (void)
+{
+ return TRUE;
+}
+
+/* Bottom panel */
+gint
+pluma_prefs_manager_get_bottom_panel_size (void)
+{
+ if (bottom_panel_size == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_BOTTOM_PANEL_SIZE,
+ PLUMA_STATE_DEFAULT_BOTTOM_PANEL_SIZE,
+ &bottom_panel_size);
+ }
+
+ return bottom_panel_size;
+}
+
+gint
+pluma_prefs_manager_get_default_bottom_panel_size (void)
+{
+ return PLUMA_STATE_DEFAULT_BOTTOM_PANEL_SIZE;
+}
+
+void
+pluma_prefs_manager_set_bottom_panel_size (gint ps)
+{
+ g_return_if_fail (ps > -1);
+
+ if (bottom_panel_size == ps)
+ return;
+
+ bottom_panel_size = ps;
+ pluma_state_set_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_BOTTOM_PANEL_SIZE,
+ ps);
+}
+
+gboolean
+pluma_prefs_manager_bottom_panel_size_can_set (void)
+{
+ return TRUE;
+}
+
+gint
+pluma_prefs_manager_get_bottom_panel_active_page (void)
+{
+ if (bottom_panel_active_page == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_BOTTOM_PANEL_ACTIVE_PAGE,
+ 0,
+ &bottom_panel_active_page);
+ }
+
+ return bottom_panel_active_page;
+}
+
+void
+pluma_prefs_manager_set_bottom_panel_active_page (gint id)
+{
+ if (bottom_panel_active_page == id)
+ return;
+
+ bottom_panel_active_page = id;
+ pluma_state_set_int (PLUMA_STATE_WINDOW_GROUP,
+ PLUMA_STATE_BOTTOM_PANEL_ACTIVE_PAGE,
+ id);
+}
+
+gboolean
+pluma_prefs_manager_bottom_panel_active_page_can_set (void)
+{
+ return TRUE;
+}
+
+/* File filter */
+gint
+pluma_prefs_manager_get_active_file_filter (void)
+{
+ if (active_file_filter == -1)
+ {
+ pluma_state_get_int (PLUMA_STATE_FILEFILTER_GROUP,
+ PLUMA_STATE_FILEFILTER_ID,
+ 0,
+ &active_file_filter);
+ }
+
+ return active_file_filter;
+}
+
+void
+pluma_prefs_manager_set_active_file_filter (gint id)
+{
+ g_return_if_fail (id >= 0);
+
+ if (active_file_filter == id)
+ return;
+
+ active_file_filter = id;
+ pluma_state_set_int (PLUMA_STATE_FILEFILTER_GROUP,
+ PLUMA_STATE_FILEFILTER_ID,
+ id);
+}
+
+gboolean
+pluma_prefs_manager_active_file_filter_can_set (void)
+{
+ return TRUE;
+}
+
+/* Normal prefs are stored in MateConf */
+
+gboolean
+pluma_prefs_manager_app_init (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager == NULL, FALSE);
+
+ pluma_prefs_manager_init ();
+
+ if (pluma_prefs_manager != NULL)
+ {
+ /* TODO: notify, add dirs */
+ mateconf_client_add_dir (pluma_prefs_manager->mateconf_client,
+ GPM_PREFS_DIR,
+ MATECONF_CLIENT_PRELOAD_RECURSIVE,
+ NULL);
+
+ mateconf_client_add_dir (pluma_prefs_manager->mateconf_client,
+ GPM_PLUGINS_DIR,
+ MATECONF_CLIENT_PRELOAD_RECURSIVE,
+ NULL);
+
+ mateconf_client_add_dir (pluma_prefs_manager->mateconf_client,
+ GPM_LOCKDOWN_DIR,
+ MATECONF_CLIENT_PRELOAD_RECURSIVE,
+ NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_FONT_DIR,
+ pluma_prefs_manager_editor_font_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_SYSTEM_FONT,
+ pluma_prefs_manager_system_font_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_TABS_DIR,
+ pluma_prefs_manager_tabs_size_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_WRAP_MODE_DIR,
+ pluma_prefs_manager_wrap_mode_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_LINE_NUMBERS_DIR,
+ pluma_prefs_manager_line_numbers_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_AUTO_INDENT_DIR,
+ pluma_prefs_manager_auto_indent_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_UNDO_DIR,
+ pluma_prefs_manager_undo_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_RIGHT_MARGIN_DIR,
+ pluma_prefs_manager_right_margin_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_SMART_HOME_END_DIR,
+ pluma_prefs_manager_smart_home_end_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_CURRENT_LINE_DIR,
+ pluma_prefs_manager_hl_current_line_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_BRACKET_MATCHING_DIR,
+ pluma_prefs_manager_bracket_matching_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_SYNTAX_HL_ENABLE,
+ pluma_prefs_manager_syntax_hl_enable_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_SEARCH_HIGHLIGHTING_ENABLE,
+ pluma_prefs_manager_search_hl_enable_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_SOURCE_STYLE_DIR,
+ pluma_prefs_manager_source_style_scheme_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_MAX_RECENTS,
+ pluma_prefs_manager_max_recents_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_SAVE_DIR,
+ pluma_prefs_manager_auto_save_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_ACTIVE_PLUGINS,
+ pluma_prefs_manager_active_plugins_changed,
+ NULL, NULL, NULL);
+
+ mateconf_client_notify_add (pluma_prefs_manager->mateconf_client,
+ GPM_LOCKDOWN_DIR,
+ pluma_prefs_manager_lockdown_changed,
+ NULL, NULL, NULL);
+ }
+
+ return pluma_prefs_manager != NULL;
+}
+
+/* This function must be called before exiting pluma */
+void
+pluma_prefs_manager_app_shutdown (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ pluma_prefs_manager_shutdown ();
+
+ pluma_state_file_sync ();
+}
+
+
+static void
+pluma_prefs_manager_editor_font_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ GList *views;
+ GList *l;
+ gchar *font = NULL;
+ gboolean def = TRUE;
+ gint ts;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_USE_DEFAULT_FONT) == 0)
+ {
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ def = mateconf_value_get_bool (entry->value);
+ else
+ def = GPM_DEFAULT_USE_DEFAULT_FONT;
+
+ if (def)
+ font = pluma_prefs_manager_get_system_font ();
+ else
+ font = pluma_prefs_manager_get_editor_font ();
+ }
+ else if (strcmp (entry->key, GPM_EDITOR_FONT) == 0)
+ {
+ if (entry->value->type == MATECONF_VALUE_STRING)
+ font = g_strdup (mateconf_value_get_string (entry->value));
+ else
+ font = g_strdup (GPM_DEFAULT_EDITOR_FONT);
+
+ def = pluma_prefs_manager_get_use_default_font ();
+ }
+ else
+ return;
+
+ g_return_if_fail (font != NULL);
+
+ ts = pluma_prefs_manager_get_tabs_size ();
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ /* Note: we use def=FALSE to avoid PlumaView to query mateconf */
+ pluma_view_set_font (PLUMA_VIEW (l->data), FALSE, font);
+ gtk_source_view_set_tab_width (GTK_SOURCE_VIEW (l->data), ts);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ g_free (font);
+}
+
+static void
+pluma_prefs_manager_system_font_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ GList *views;
+ GList *l;
+ gchar *font;
+ gint ts;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_SYSTEM_FONT) != 0)
+ return;
+
+ if (!pluma_prefs_manager_get_use_default_font ())
+ return;
+
+ if (entry->value->type == MATECONF_VALUE_STRING)
+ font = g_strdup (mateconf_value_get_string (entry->value));
+ else
+ font = g_strdup (GPM_DEFAULT_SYSTEM_FONT);
+
+ ts = pluma_prefs_manager_get_tabs_size ();
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ /* Note: we use def=FALSE to avoid PlumaView to query mateconf */
+ pluma_view_set_font (PLUMA_VIEW (l->data), FALSE, font);
+
+ gtk_source_view_set_tab_width (GTK_SOURCE_VIEW (l->data), ts);
+ l = l->next;
+ }
+
+ g_list_free (views);
+ g_free (font);
+}
+
+static void
+pluma_prefs_manager_tabs_size_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_TABS_SIZE) == 0)
+ {
+ gint tab_width;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_INT)
+ tab_width = mateconf_value_get_int (entry->value);
+ else
+ tab_width = GPM_DEFAULT_TABS_SIZE;
+
+ tab_width = CLAMP (tab_width, 1, 24);
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_tab_width (GTK_SOURCE_VIEW (l->data),
+ tab_width);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+ else if (strcmp (entry->key, GPM_INSERT_SPACES) == 0)
+ {
+ gboolean enable;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ enable = mateconf_value_get_bool (entry->value);
+ else
+ enable = GPM_DEFAULT_INSERT_SPACES;
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_insert_spaces_instead_of_tabs (
+ GTK_SOURCE_VIEW (l->data),
+ enable);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+}
+
+static GtkWrapMode
+get_wrap_mode_from_string (const gchar* str)
+{
+ GtkWrapMode res;
+
+ g_return_val_if_fail (str != NULL, GTK_WRAP_WORD);
+
+ if (strcmp (str, "GTK_WRAP_NONE") == 0)
+ res = GTK_WRAP_NONE;
+ else
+ {
+ if (strcmp (str, "GTK_WRAP_CHAR") == 0)
+ res = GTK_WRAP_CHAR;
+ else
+ res = GTK_WRAP_WORD;
+ }
+
+ return res;
+}
+
+static void
+pluma_prefs_manager_wrap_mode_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_WRAP_MODE) == 0)
+ {
+ GtkWrapMode wrap_mode;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_STRING)
+ wrap_mode =
+ get_wrap_mode_from_string (mateconf_value_get_string (entry->value));
+ else
+ wrap_mode = get_wrap_mode_from_string (GPM_DEFAULT_WRAP_MODE);
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (l->data),
+ wrap_mode);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+}
+
+static void
+pluma_prefs_manager_line_numbers_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_DISPLAY_LINE_NUMBERS) == 0)
+ {
+ gboolean dln;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ dln = mateconf_value_get_bool (entry->value);
+ else
+ dln = GPM_DEFAULT_DISPLAY_LINE_NUMBERS;
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_show_line_numbers (GTK_SOURCE_VIEW (l->data),
+ dln);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+}
+
+static void
+pluma_prefs_manager_hl_current_line_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_HIGHLIGHT_CURRENT_LINE) == 0)
+ {
+ gboolean hl;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ hl = mateconf_value_get_bool (entry->value);
+ else
+ hl = GPM_DEFAULT_HIGHLIGHT_CURRENT_LINE;
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (l->data),
+ hl);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+}
+
+static void
+pluma_prefs_manager_bracket_matching_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_BRACKET_MATCHING) == 0)
+ {
+ gboolean enable;
+ GList *docs;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ enable = mateconf_value_get_bool (entry->value);
+ else
+ enable = GPM_DEFAULT_BRACKET_MATCHING;
+
+ docs = pluma_app_get_documents (pluma_app_get_default ());
+ l = docs;
+
+ while (l != NULL)
+ {
+ gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (l->data),
+ enable);
+
+ l = l->next;
+ }
+
+ g_list_free (docs);
+ }
+}
+
+static void
+pluma_prefs_manager_auto_indent_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_AUTO_INDENT) == 0)
+ {
+ gboolean enable;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ enable = mateconf_value_get_bool (entry->value);
+ else
+ enable = GPM_DEFAULT_AUTO_INDENT;
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_auto_indent (GTK_SOURCE_VIEW (l->data),
+ enable);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+}
+
+static void
+pluma_prefs_manager_undo_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_UNDO_ACTIONS_LIMIT) == 0)
+ {
+ gint ul;
+ GList *docs;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_INT)
+ ul = mateconf_value_get_int (entry->value);
+ else
+ ul = GPM_DEFAULT_UNDO_ACTIONS_LIMIT;
+
+ ul = CLAMP (ul, -1, 250);
+
+ docs = pluma_app_get_documents (pluma_app_get_default ());
+ l = docs;
+
+ while (l != NULL)
+ {
+ gtk_source_buffer_set_max_undo_levels (GTK_SOURCE_BUFFER (l->data),
+ ul);
+
+ l = l->next;
+ }
+
+ g_list_free (docs);
+ }
+}
+
+static void
+pluma_prefs_manager_right_margin_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_RIGHT_MARGIN_POSITION) == 0)
+ {
+ gint pos;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_INT)
+ pos = mateconf_value_get_int (entry->value);
+ else
+ pos = GPM_DEFAULT_RIGHT_MARGIN_POSITION;
+
+ pos = CLAMP (pos, 1, 160);
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_right_margin_position (GTK_SOURCE_VIEW (l->data),
+ pos);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+ else if (strcmp (entry->key, GPM_DISPLAY_RIGHT_MARGIN) == 0)
+ {
+ gboolean display;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ display = mateconf_value_get_bool (entry->value);
+ else
+ display = GPM_DEFAULT_DISPLAY_RIGHT_MARGIN;
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_show_right_margin (GTK_SOURCE_VIEW (l->data),
+ display);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+}
+
+static GtkSourceSmartHomeEndType
+get_smart_home_end_from_string (const gchar *str)
+{
+ GtkSourceSmartHomeEndType res;
+
+ g_return_val_if_fail (str != NULL, GTK_SOURCE_SMART_HOME_END_AFTER);
+
+ if (strcmp (str, "DISABLED") == 0)
+ res = GTK_SOURCE_SMART_HOME_END_DISABLED;
+ else if (strcmp (str, "BEFORE") == 0)
+ res = GTK_SOURCE_SMART_HOME_END_BEFORE;
+ else if (strcmp (str, "ALWAYS") == 0)
+ res = GTK_SOURCE_SMART_HOME_END_ALWAYS;
+ else
+ res = GTK_SOURCE_SMART_HOME_END_AFTER;
+
+ return res;
+}
+
+static void
+pluma_prefs_manager_smart_home_end_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_SMART_HOME_END) == 0)
+ {
+ GtkSourceSmartHomeEndType smart_he;
+ GList *views;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_STRING)
+ smart_he =
+ get_smart_home_end_from_string (mateconf_value_get_string (entry->value));
+ else
+ smart_he = get_smart_home_end_from_string (GPM_DEFAULT_SMART_HOME_END);
+
+ views = pluma_app_get_views (pluma_app_get_default ());
+ l = views;
+
+ while (l != NULL)
+ {
+ gtk_source_view_set_smart_home_end (GTK_SOURCE_VIEW (l->data),
+ smart_he);
+
+ l = l->next;
+ }
+
+ g_list_free (views);
+ }
+}
+
+static void
+pluma_prefs_manager_syntax_hl_enable_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_SYNTAX_HL_ENABLE) == 0)
+ {
+ gboolean enable;
+ GList *docs;
+ GList *l;
+ const GList *windows;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ enable = mateconf_value_get_bool (entry->value);
+ else
+ enable = GPM_DEFAULT_SYNTAX_HL_ENABLE;
+
+ docs = pluma_app_get_documents (pluma_app_get_default ());
+ l = docs;
+
+ while (l != NULL)
+ {
+ g_return_if_fail (GTK_IS_SOURCE_BUFFER (l->data));
+
+ gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (l->data),
+ enable);
+
+ l = l->next;
+ }
+
+ g_list_free (docs);
+
+ /* update the sensitivity of the Higlight Mode menu item */
+ windows = pluma_app_get_windows (pluma_app_get_default ());
+ while (windows != NULL)
+ {
+ GtkUIManager *ui;
+ GtkAction *a;
+
+ ui = pluma_window_get_ui_manager (PLUMA_WINDOW (windows->data));
+
+ a = gtk_ui_manager_get_action (ui,
+ "/MenuBar/ViewMenu/ViewHighlightModeMenu");
+
+ gtk_action_set_sensitive (a, enable);
+
+ windows = g_list_next (windows);
+ }
+ }
+}
+
+static void
+pluma_prefs_manager_search_hl_enable_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_SEARCH_HIGHLIGHTING_ENABLE) == 0)
+ {
+ gboolean enable;
+ GList *docs;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ enable = mateconf_value_get_bool (entry->value);
+ else
+ enable = GPM_DEFAULT_SEARCH_HIGHLIGHTING_ENABLE;
+
+ docs = pluma_app_get_documents (pluma_app_get_default ());
+ l = docs;
+
+ while (l != NULL)
+ {
+ g_return_if_fail (PLUMA_IS_DOCUMENT (l->data));
+
+ pluma_document_set_enable_search_highlighting (PLUMA_DOCUMENT (l->data),
+ enable);
+
+ l = l->next;
+ }
+
+ g_list_free (docs);
+ }
+}
+
+static void
+pluma_prefs_manager_source_style_scheme_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_SOURCE_STYLE_SCHEME) == 0)
+ {
+ static gchar *old_scheme = NULL;
+ const gchar *scheme;
+ GtkSourceStyleScheme *style;
+ GList *docs;
+ GList *l;
+
+ if (entry->value->type == MATECONF_VALUE_STRING)
+ scheme = mateconf_value_get_string (entry->value);
+ else
+ scheme = GPM_DEFAULT_SOURCE_STYLE_SCHEME;
+
+ if (old_scheme != NULL && (strcmp (scheme, old_scheme) == 0))
+ return;
+
+ g_free (old_scheme);
+ old_scheme = g_strdup (scheme);
+
+ style = gtk_source_style_scheme_manager_get_scheme (
+ pluma_get_style_scheme_manager (),
+ scheme);
+
+ if (style == NULL)
+ {
+ g_warning ("Default style scheme '%s' not found, falling back to 'classic'", scheme);
+
+ style = gtk_source_style_scheme_manager_get_scheme (
+ pluma_get_style_scheme_manager (),
+ "classic");
+
+ if (style == NULL)
+ {
+ g_warning ("Style scheme 'classic' cannot be found, check your GtkSourceView installation.");
+ return;
+ }
+ }
+
+ docs = pluma_app_get_documents (pluma_app_get_default ());
+ for (l = docs; l != NULL; l = l->next)
+ {
+ g_return_if_fail (GTK_IS_SOURCE_BUFFER (l->data));
+
+ gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (l->data),
+ style);
+ }
+
+ g_list_free (docs);
+ }
+}
+
+static void
+pluma_prefs_manager_max_recents_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_MAX_RECENTS) == 0)
+ {
+ const GList *windows;
+ gint max;
+
+ if (entry->value->type == MATECONF_VALUE_INT)
+ {
+ max = mateconf_value_get_int (entry->value);
+
+ if (max < 0)
+ max = GPM_DEFAULT_MAX_RECENTS;
+ }
+ else
+ max = GPM_DEFAULT_MAX_RECENTS;
+
+ windows = pluma_app_get_windows (pluma_app_get_default ());
+ while (windows != NULL)
+ {
+ PlumaWindow *w = windows->data;
+
+ gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER (w->priv->toolbar_recent_menu),
+ max);
+
+ windows = g_list_next (windows);
+ }
+
+ /* FIXME: we have no way at the moment to trigger the
+ * update of the inline recents in the File menu */
+ }
+}
+
+static void
+pluma_prefs_manager_auto_save_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ GList *docs;
+ GList *l;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_AUTO_SAVE) == 0)
+ {
+ gboolean auto_save;
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ auto_save = mateconf_value_get_bool (entry->value);
+ else
+ auto_save = GPM_DEFAULT_AUTO_SAVE;
+
+ docs = pluma_app_get_documents (pluma_app_get_default ());
+ l = docs;
+
+ while (l != NULL)
+ {
+ PlumaDocument *doc = PLUMA_DOCUMENT (l->data);
+ PlumaTab *tab = pluma_tab_get_from_document (doc);
+
+ pluma_tab_set_auto_save_enabled (tab, auto_save);
+
+ l = l->next;
+ }
+
+ g_list_free (docs);
+ }
+ else if (strcmp (entry->key, GPM_AUTO_SAVE_INTERVAL) == 0)
+ {
+ gint auto_save_interval;
+
+ if (entry->value->type == MATECONF_VALUE_INT)
+ {
+ auto_save_interval = mateconf_value_get_int (entry->value);
+
+ if (auto_save_interval <= 0)
+ auto_save_interval = GPM_DEFAULT_AUTO_SAVE_INTERVAL;
+ }
+ else
+ auto_save_interval = GPM_DEFAULT_AUTO_SAVE_INTERVAL;
+
+ docs = pluma_app_get_documents (pluma_app_get_default ());
+ l = docs;
+
+ while (l != NULL)
+ {
+ PlumaDocument *doc = PLUMA_DOCUMENT (l->data);
+ PlumaTab *tab = pluma_tab_get_from_document (doc);
+
+ pluma_tab_set_auto_save_interval (tab, auto_save_interval);
+
+ l = l->next;
+ }
+
+ g_list_free (docs);
+ }
+}
+
+static void
+pluma_prefs_manager_active_plugins_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (strcmp (entry->key, GPM_ACTIVE_PLUGINS) == 0)
+ {
+ if ((entry->value->type == MATECONF_VALUE_LIST) &&
+ (mateconf_value_get_list_type (entry->value) == MATECONF_VALUE_STRING))
+ {
+ PlumaPluginsEngine *engine;
+
+ engine = pluma_plugins_engine_get_default ();
+
+ pluma_plugins_engine_active_plugins_changed (engine);
+ }
+ }
+}
+
+static void
+pluma_prefs_manager_lockdown_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ PlumaApp *app;
+ gboolean locked;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (entry->key != NULL);
+ g_return_if_fail (entry->value != NULL);
+
+ if (entry->value->type == MATECONF_VALUE_BOOL)
+ locked = mateconf_value_get_bool (entry->value);
+ else
+ locked = FALSE;
+
+ app = pluma_app_get_default ();
+
+ if (strcmp (entry->key, GPM_LOCKDOWN_COMMAND_LINE) == 0)
+ _pluma_app_set_lockdown_bit (app,
+ PLUMA_LOCKDOWN_COMMAND_LINE,
+ locked);
+
+ else if (strcmp (entry->key, GPM_LOCKDOWN_PRINTING) == 0)
+ _pluma_app_set_lockdown_bit (app,
+ PLUMA_LOCKDOWN_PRINTING,
+ locked);
+
+ else if (strcmp (entry->key, GPM_LOCKDOWN_PRINT_SETUP) == 0)
+ _pluma_app_set_lockdown_bit (app,
+ PLUMA_LOCKDOWN_PRINT_SETUP,
+ locked);
+
+ else if (strcmp (entry->key, GPM_LOCKDOWN_SAVE_TO_DISK) == 0)
+ _pluma_app_set_lockdown_bit (app,
+ PLUMA_LOCKDOWN_SAVE_TO_DISK,
+ locked);
+}
diff --git a/pluma/pluma-prefs-manager-app.h b/pluma/pluma-prefs-manager-app.h
new file mode 100755
index 00000000..059580e2
--- /dev/null
+++ b/pluma/pluma-prefs-manager-app.h
@@ -0,0 +1,84 @@
+/*
+ * pluma-prefs-manager-app.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ *
+ */
+
+#ifndef __PLUMA_PREFS_MANAGER_APP_H__
+#define __PLUMA_PREFS_MANAGER_APP_H__
+
+#include <glib.h>
+#include <pluma/pluma-prefs-manager.h>
+
+/** LIFE CYCLE MANAGEMENT FUNCTIONS **/
+
+gboolean pluma_prefs_manager_app_init (void);
+
+/* This function must be called before exiting pluma */
+void pluma_prefs_manager_app_shutdown (void);
+
+
+/* Window state */
+gint pluma_prefs_manager_get_window_state (void);
+void pluma_prefs_manager_set_window_state (gint ws);
+gboolean pluma_prefs_manager_window_state_can_set (void);
+
+/* Window size */
+void pluma_prefs_manager_get_window_size (gint *width,
+ gint *height);
+void pluma_prefs_manager_get_default_window_size (gint *width,
+ gint *height);
+void pluma_prefs_manager_set_window_size (gint width,
+ gint height);
+gboolean pluma_prefs_manager_window_size_can_set (void);
+
+/* Side panel */
+gint pluma_prefs_manager_get_side_panel_size (void);
+gint pluma_prefs_manager_get_default_side_panel_size(void);
+void pluma_prefs_manager_set_side_panel_size (gint ps);
+gboolean pluma_prefs_manager_side_panel_size_can_set (void);
+gint pluma_prefs_manager_get_side_panel_active_page (void);
+void pluma_prefs_manager_set_side_panel_active_page (gint id);
+gboolean pluma_prefs_manager_side_panel_active_page_can_set (void);
+
+/* Bottom panel */
+gint pluma_prefs_manager_get_bottom_panel_size (void);
+gint pluma_prefs_manager_get_default_bottom_panel_size(void);
+void pluma_prefs_manager_set_bottom_panel_size (gint ps);
+gboolean pluma_prefs_manager_bottom_panel_size_can_set (void);
+gint pluma_prefs_manager_get_bottom_panel_active_page (void);
+void pluma_prefs_manager_set_bottom_panel_active_page (gint id);
+gboolean pluma_prefs_manager_bottom_panel_active_page_can_set (void);
+
+/* File filter */
+gint pluma_prefs_manager_get_active_file_filter (void);
+void pluma_prefs_manager_set_active_file_filter (gint id);
+gboolean pluma_prefs_manager_active_file_filter_can_set (void);
+
+
+#endif /* __PLUMA_PREFS_MANAGER_APP_H__ */
diff --git a/pluma/pluma-prefs-manager-private.h b/pluma/pluma-prefs-manager-private.h
new file mode 100755
index 00000000..3b1cd89c
--- /dev/null
+++ b/pluma/pluma-prefs-manager-private.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-prefs-manager-private.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __PLUMA_PREFS_MANAGER_PRIVATE_H__
+#define __PLUMA_PREFS_MANAGER_PRIVATE_H__
+
+#include <mateconf/mateconf-client.h>
+
+typedef struct _PlumaPrefsManager PlumaPrefsManager;
+
+struct _PlumaPrefsManager {
+ MateConfClient *mateconf_client;
+};
+
+extern PlumaPrefsManager *pluma_prefs_manager;
+
+#endif /* __PLUMA_PREFS_MANAGER_PRIVATE_H__ */
+
+
diff --git a/pluma/pluma-prefs-manager.c b/pluma/pluma-prefs-manager.c
new file mode 100755
index 00000000..1f15e3a2
--- /dev/null
+++ b/pluma/pluma-prefs-manager.c
@@ -0,0 +1,1241 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-prefs-manager.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <mateconf/mateconf-value.h>
+
+#include "pluma-prefs-manager.h"
+#include "pluma-prefs-manager-private.h"
+#include "pluma-debug.h"
+#include "pluma-encodings.h"
+#include "pluma-utils.h"
+
+#define DEFINE_BOOL_PREF(name, key, def) gboolean \
+pluma_prefs_manager_get_ ## name (void) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ return pluma_prefs_manager_get_bool (key, \
+ (def)); \
+} \
+ \
+void \
+pluma_prefs_manager_set_ ## name (gboolean v) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ pluma_prefs_manager_set_bool (key, \
+ v); \
+} \
+ \
+gboolean \
+pluma_prefs_manager_ ## name ## _can_set (void) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ return pluma_prefs_manager_key_is_writable (key); \
+}
+
+
+
+#define DEFINE_INT_PREF(name, key, def) gint \
+pluma_prefs_manager_get_ ## name (void) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ return pluma_prefs_manager_get_int (key, \
+ (def)); \
+} \
+ \
+void \
+pluma_prefs_manager_set_ ## name (gint v) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ pluma_prefs_manager_set_int (key, \
+ v); \
+} \
+ \
+gboolean \
+pluma_prefs_manager_ ## name ## _can_set (void) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ return pluma_prefs_manager_key_is_writable (key); \
+}
+
+
+
+#define DEFINE_STRING_PREF(name, key, def) gchar* \
+pluma_prefs_manager_get_ ## name (void) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ return pluma_prefs_manager_get_string (key, \
+ def); \
+} \
+ \
+void \
+pluma_prefs_manager_set_ ## name (const gchar* v) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ pluma_prefs_manager_set_string (key, \
+ v); \
+} \
+ \
+gboolean \
+pluma_prefs_manager_ ## name ## _can_set (void) \
+{ \
+ pluma_debug (DEBUG_PREFS); \
+ \
+ return pluma_prefs_manager_key_is_writable (key); \
+}
+
+
+PlumaPrefsManager *pluma_prefs_manager = NULL;
+
+
+static GtkWrapMode get_wrap_mode_from_string (const gchar* str);
+
+static gboolean mateconf_client_get_bool_with_default (MateConfClient* client,
+ const gchar* key,
+ gboolean def,
+ GError** err);
+
+static gchar *mateconf_client_get_string_with_default (MateConfClient* client,
+ const gchar* key,
+ const gchar* def,
+ GError** err);
+
+static gint mateconf_client_get_int_with_default (MateConfClient* client,
+ const gchar* key,
+ gint def,
+ GError** err);
+
+static gboolean pluma_prefs_manager_get_bool (const gchar* key,
+ gboolean def);
+
+static gint pluma_prefs_manager_get_int (const gchar* key,
+ gint def);
+
+static gchar *pluma_prefs_manager_get_string (const gchar* key,
+ const gchar* def);
+
+
+gboolean
+pluma_prefs_manager_init (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ if (pluma_prefs_manager == NULL)
+ {
+ MateConfClient *mateconf_client;
+
+ mateconf_client = mateconf_client_get_default ();
+ if (mateconf_client == NULL)
+ {
+ g_warning (_("Cannot initialize preferences manager."));
+ return FALSE;
+ }
+
+ pluma_prefs_manager = g_new0 (PlumaPrefsManager, 1);
+
+ pluma_prefs_manager->mateconf_client = mateconf_client;
+ }
+
+ if (pluma_prefs_manager->mateconf_client == NULL)
+ {
+ g_free (pluma_prefs_manager);
+ pluma_prefs_manager = NULL;
+ }
+
+ return pluma_prefs_manager != NULL;
+
+}
+
+void
+pluma_prefs_manager_shutdown (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (pluma_prefs_manager != NULL);
+
+ g_object_unref (pluma_prefs_manager->mateconf_client);
+ pluma_prefs_manager->mateconf_client = NULL;
+}
+
+static gboolean
+pluma_prefs_manager_get_bool (const gchar* key, gboolean def)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL, def);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL, def);
+
+ return mateconf_client_get_bool_with_default (pluma_prefs_manager->mateconf_client,
+ key,
+ def,
+ NULL);
+}
+
+static gint
+pluma_prefs_manager_get_int (const gchar* key, gint def)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL, def);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL, def);
+
+ return mateconf_client_get_int_with_default (pluma_prefs_manager->mateconf_client,
+ key,
+ def,
+ NULL);
+}
+
+static gchar *
+pluma_prefs_manager_get_string (const gchar* key, const gchar* def)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL,
+ def ? g_strdup (def) : NULL);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL,
+ def ? g_strdup (def) : NULL);
+
+ return mateconf_client_get_string_with_default (pluma_prefs_manager->mateconf_client,
+ key,
+ def,
+ NULL);
+}
+
+static void
+pluma_prefs_manager_set_bool (const gchar* key, gboolean value)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (pluma_prefs_manager != NULL);
+ g_return_if_fail (pluma_prefs_manager->mateconf_client != NULL);
+ g_return_if_fail (mateconf_client_key_is_writable (
+ pluma_prefs_manager->mateconf_client, key, NULL));
+
+ mateconf_client_set_bool (pluma_prefs_manager->mateconf_client, key, value, NULL);
+}
+
+static void
+pluma_prefs_manager_set_int (const gchar* key, gint value)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (pluma_prefs_manager != NULL);
+ g_return_if_fail (pluma_prefs_manager->mateconf_client != NULL);
+ g_return_if_fail (mateconf_client_key_is_writable (
+ pluma_prefs_manager->mateconf_client, key, NULL));
+
+ mateconf_client_set_int (pluma_prefs_manager->mateconf_client, key, value, NULL);
+}
+
+static void
+pluma_prefs_manager_set_string (const gchar* key, const gchar* value)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_if_fail (value != NULL);
+
+ g_return_if_fail (pluma_prefs_manager != NULL);
+ g_return_if_fail (pluma_prefs_manager->mateconf_client != NULL);
+ g_return_if_fail (mateconf_client_key_is_writable (
+ pluma_prefs_manager->mateconf_client, key, NULL));
+
+ mateconf_client_set_string (pluma_prefs_manager->mateconf_client, key, value, NULL);
+}
+
+static gboolean
+pluma_prefs_manager_key_is_writable (const gchar* key)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL, FALSE);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL, FALSE);
+
+ return mateconf_client_key_is_writable (pluma_prefs_manager->mateconf_client, key, NULL);
+}
+
+/* Use default font */
+DEFINE_BOOL_PREF (use_default_font,
+ GPM_USE_DEFAULT_FONT,
+ GPM_DEFAULT_USE_DEFAULT_FONT)
+
+/* Editor font */
+DEFINE_STRING_PREF (editor_font,
+ GPM_EDITOR_FONT,
+ GPM_DEFAULT_EDITOR_FONT)
+
+/* System font */
+gchar *
+pluma_prefs_manager_get_system_font (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_get_string (GPM_SYSTEM_FONT,
+ GPM_DEFAULT_SYSTEM_FONT);
+}
+
+/* Create backup copy */
+DEFINE_BOOL_PREF (create_backup_copy,
+ GPM_CREATE_BACKUP_COPY,
+ GPM_DEFAULT_CREATE_BACKUP_COPY)
+
+/* Auto save */
+DEFINE_BOOL_PREF (auto_save,
+ GPM_AUTO_SAVE,
+ GPM_DEFAULT_AUTO_SAVE)
+
+/* Auto save interval */
+DEFINE_INT_PREF (auto_save_interval,
+ GPM_AUTO_SAVE_INTERVAL,
+ GPM_DEFAULT_AUTO_SAVE_INTERVAL)
+
+
+/* Undo actions limit: if < 1 then no limits */
+DEFINE_INT_PREF (undo_actions_limit,
+ GPM_UNDO_ACTIONS_LIMIT,
+ GPM_DEFAULT_UNDO_ACTIONS_LIMIT)
+
+static GtkWrapMode
+get_wrap_mode_from_string (const gchar* str)
+{
+ GtkWrapMode res;
+
+ g_return_val_if_fail (str != NULL, GTK_WRAP_WORD);
+
+ if (strcmp (str, "GTK_WRAP_NONE") == 0)
+ res = GTK_WRAP_NONE;
+ else
+ {
+ if (strcmp (str, "GTK_WRAP_CHAR") == 0)
+ res = GTK_WRAP_CHAR;
+ else
+ res = GTK_WRAP_WORD;
+ }
+
+ return res;
+}
+
+/* Wrap mode */
+GtkWrapMode
+pluma_prefs_manager_get_wrap_mode (void)
+{
+ gchar *str;
+ GtkWrapMode res;
+
+ pluma_debug (DEBUG_PREFS);
+
+ str = pluma_prefs_manager_get_string (GPM_WRAP_MODE,
+ GPM_DEFAULT_WRAP_MODE);
+
+ res = get_wrap_mode_from_string (str);
+
+ g_free (str);
+
+ return res;
+}
+
+void
+pluma_prefs_manager_set_wrap_mode (GtkWrapMode wp)
+{
+ const gchar * str;
+
+ pluma_debug (DEBUG_PREFS);
+
+ switch (wp)
+ {
+ case GTK_WRAP_NONE:
+ str = "GTK_WRAP_NONE";
+ break;
+
+ case GTK_WRAP_CHAR:
+ str = "GTK_WRAP_CHAR";
+ break;
+
+ default: /* GTK_WRAP_WORD */
+ str = "GTK_WRAP_WORD";
+ }
+
+ pluma_prefs_manager_set_string (GPM_WRAP_MODE,
+ str);
+}
+
+gboolean
+pluma_prefs_manager_wrap_mode_can_set (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_key_is_writable (GPM_WRAP_MODE);
+}
+
+
+/* Tabs size */
+DEFINE_INT_PREF (tabs_size,
+ GPM_TABS_SIZE,
+ GPM_DEFAULT_TABS_SIZE)
+
+/* Insert spaces */
+DEFINE_BOOL_PREF (insert_spaces,
+ GPM_INSERT_SPACES,
+ GPM_DEFAULT_INSERT_SPACES)
+
+/* Auto indent */
+DEFINE_BOOL_PREF (auto_indent,
+ GPM_AUTO_INDENT,
+ GPM_DEFAULT_AUTO_INDENT)
+
+/* Display line numbers */
+DEFINE_BOOL_PREF (display_line_numbers,
+ GPM_DISPLAY_LINE_NUMBERS,
+ GPM_DEFAULT_DISPLAY_LINE_NUMBERS)
+
+/* Toolbar visibility */
+DEFINE_BOOL_PREF (toolbar_visible,
+ GPM_TOOLBAR_VISIBLE,
+ GPM_DEFAULT_TOOLBAR_VISIBLE)
+
+
+/* Toolbar suttons style */
+PlumaToolbarSetting
+pluma_prefs_manager_get_toolbar_buttons_style (void)
+{
+ gchar *str;
+ PlumaToolbarSetting res;
+
+ pluma_debug (DEBUG_PREFS);
+
+ str = pluma_prefs_manager_get_string (GPM_TOOLBAR_BUTTONS_STYLE,
+ GPM_DEFAULT_TOOLBAR_BUTTONS_STYLE);
+
+ if (strcmp (str, "PLUMA_TOOLBAR_ICONS") == 0)
+ res = PLUMA_TOOLBAR_ICONS;
+ else
+ {
+ if (strcmp (str, "PLUMA_TOOLBAR_ICONS_AND_TEXT") == 0)
+ res = PLUMA_TOOLBAR_ICONS_AND_TEXT;
+ else
+ {
+ if (strcmp (str, "PLUMA_TOOLBAR_ICONS_BOTH_HORIZ") == 0)
+ res = PLUMA_TOOLBAR_ICONS_BOTH_HORIZ;
+ else
+ res = PLUMA_TOOLBAR_SYSTEM;
+ }
+ }
+
+ g_free (str);
+
+ return res;
+}
+
+void
+pluma_prefs_manager_set_toolbar_buttons_style (PlumaToolbarSetting tbs)
+{
+ const gchar * str;
+
+ pluma_debug (DEBUG_PREFS);
+
+ switch (tbs)
+ {
+ case PLUMA_TOOLBAR_ICONS:
+ str = "PLUMA_TOOLBAR_ICONS";
+ break;
+
+ case PLUMA_TOOLBAR_ICONS_AND_TEXT:
+ str = "PLUMA_TOOLBAR_ICONS_AND_TEXT";
+ break;
+
+ case PLUMA_TOOLBAR_ICONS_BOTH_HORIZ:
+ str = "PLUMA_TOOLBAR_ICONS_BOTH_HORIZ";
+ break;
+ default: /* PLUMA_TOOLBAR_SYSTEM */
+ str = "PLUMA_TOOLBAR_SYSTEM";
+ }
+
+ pluma_prefs_manager_set_string (GPM_TOOLBAR_BUTTONS_STYLE,
+ str);
+
+}
+
+gboolean
+pluma_prefs_manager_toolbar_buttons_style_can_set (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_key_is_writable (GPM_TOOLBAR_BUTTONS_STYLE);
+
+}
+
+/* Statusbar visiblity */
+DEFINE_BOOL_PREF (statusbar_visible,
+ GPM_STATUSBAR_VISIBLE,
+ GPM_DEFAULT_STATUSBAR_VISIBLE)
+
+/* Side Pane visiblity */
+DEFINE_BOOL_PREF (side_pane_visible,
+ GPM_SIDE_PANE_VISIBLE,
+ GPM_DEFAULT_SIDE_PANE_VISIBLE)
+
+/* Bottom Panel visiblity */
+DEFINE_BOOL_PREF (bottom_panel_visible,
+ GPM_BOTTOM_PANEL_VISIBLE,
+ GPM_DEFAULT_BOTTOM_PANEL_VISIBLE)
+
+/* Print syntax highlighting */
+DEFINE_BOOL_PREF (print_syntax_hl,
+ GPM_PRINT_SYNTAX,
+ GPM_DEFAULT_PRINT_SYNTAX)
+
+/* Print header */
+DEFINE_BOOL_PREF (print_header,
+ GPM_PRINT_HEADER,
+ GPM_DEFAULT_PRINT_HEADER)
+
+
+/* Print Wrap mode */
+GtkWrapMode
+pluma_prefs_manager_get_print_wrap_mode (void)
+{
+ gchar *str;
+ GtkWrapMode res;
+
+ pluma_debug (DEBUG_PREFS);
+
+ str = pluma_prefs_manager_get_string (GPM_PRINT_WRAP_MODE,
+ GPM_DEFAULT_PRINT_WRAP_MODE);
+
+ if (strcmp (str, "GTK_WRAP_NONE") == 0)
+ res = GTK_WRAP_NONE;
+ else
+ {
+ if (strcmp (str, "GTK_WRAP_WORD") == 0)
+ res = GTK_WRAP_WORD;
+ else
+ res = GTK_WRAP_CHAR;
+ }
+
+ g_free (str);
+
+ return res;
+}
+
+void
+pluma_prefs_manager_set_print_wrap_mode (GtkWrapMode pwp)
+{
+ const gchar *str;
+
+ pluma_debug (DEBUG_PREFS);
+
+ switch (pwp)
+ {
+ case GTK_WRAP_NONE:
+ str = "GTK_WRAP_NONE";
+ break;
+
+ case GTK_WRAP_WORD:
+ str = "GTK_WRAP_WORD";
+ break;
+
+ default: /* GTK_WRAP_CHAR */
+ str = "GTK_WRAP_CHAR";
+ }
+
+ pluma_prefs_manager_set_string (GPM_PRINT_WRAP_MODE, str);
+}
+
+gboolean
+pluma_prefs_manager_print_wrap_mode_can_set (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_key_is_writable (GPM_PRINT_WRAP_MODE);
+}
+
+/* Print line numbers */
+DEFINE_INT_PREF (print_line_numbers,
+ GPM_PRINT_LINE_NUMBERS,
+ GPM_DEFAULT_PRINT_LINE_NUMBERS)
+
+/* Printing fonts */
+DEFINE_STRING_PREF (print_font_body,
+ GPM_PRINT_FONT_BODY,
+ GPM_DEFAULT_PRINT_FONT_BODY)
+
+const gchar *
+pluma_prefs_manager_get_default_print_font_body (void)
+{
+ return GPM_DEFAULT_PRINT_FONT_BODY;
+}
+
+DEFINE_STRING_PREF (print_font_header,
+ GPM_PRINT_FONT_HEADER,
+ GPM_DEFAULT_PRINT_FONT_HEADER)
+
+const gchar *
+pluma_prefs_manager_get_default_print_font_header (void)
+{
+ return GPM_DEFAULT_PRINT_FONT_HEADER;
+}
+
+DEFINE_STRING_PREF (print_font_numbers,
+ GPM_PRINT_FONT_NUMBERS,
+ GPM_DEFAULT_PRINT_FONT_NUMBERS)
+
+const gchar *
+pluma_prefs_manager_get_default_print_font_numbers (void)
+{
+ return GPM_DEFAULT_PRINT_FONT_NUMBERS;
+}
+
+/* Max number of files in "Recent Files" menu.
+ * This is configurable only using mateconftool or mateconf-editor
+ */
+gint
+pluma_prefs_manager_get_max_recents (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_get_int (GPM_MAX_RECENTS,
+ GPM_DEFAULT_MAX_RECENTS);
+
+}
+
+/* Encodings */
+
+static gboolean
+data_exists (GSList *list,
+ const gpointer data)
+{
+ while (list != NULL)
+ {
+ if (list->data == data)
+ return TRUE;
+
+ list = g_slist_next (list);
+ }
+
+ return FALSE;
+}
+
+GSList *
+pluma_prefs_manager_get_auto_detected_encodings (void)
+{
+ GSList *strings;
+ GSList *res = NULL;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL, NULL);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL, NULL);
+
+ strings = mateconf_client_get_list (pluma_prefs_manager->mateconf_client,
+ GPM_AUTO_DETECTED_ENCODINGS,
+ MATECONF_VALUE_STRING,
+ NULL);
+
+ if (strings == NULL)
+ {
+ gint i = 0;
+ const gchar* s[] = GPM_DEFAULT_AUTO_DETECTED_ENCODINGS;
+
+ while (s[i] != NULL)
+ {
+ strings = g_slist_prepend (strings, g_strdup (s[i]));
+
+ ++i;
+ }
+
+
+ strings = g_slist_reverse (strings);
+ }
+
+ if (strings != NULL)
+ {
+ GSList *tmp;
+ const PlumaEncoding *enc;
+
+ tmp = strings;
+
+ while (tmp)
+ {
+ const char *charset = tmp->data;
+
+ if (strcmp (charset, "CURRENT") == 0)
+ g_get_charset (&charset);
+
+ g_return_val_if_fail (charset != NULL, NULL);
+ enc = pluma_encoding_get_from_charset (charset);
+
+ if (enc != NULL)
+ {
+ if (!data_exists (res, (gpointer)enc))
+ res = g_slist_prepend (res, (gpointer)enc);
+
+ }
+
+ tmp = g_slist_next (tmp);
+ }
+
+ g_slist_foreach (strings, (GFunc) g_free, NULL);
+ g_slist_free (strings);
+
+ res = g_slist_reverse (res);
+ }
+
+ pluma_debug_message (DEBUG_PREFS, "Done");
+
+ return res;
+}
+
+GSList *
+pluma_prefs_manager_get_shown_in_menu_encodings (void)
+{
+ GSList *strings;
+ GSList *res = NULL;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL, NULL);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL, NULL);
+
+ strings = mateconf_client_get_list (pluma_prefs_manager->mateconf_client,
+ GPM_SHOWN_IN_MENU_ENCODINGS,
+ MATECONF_VALUE_STRING,
+ NULL);
+
+ if (strings != NULL)
+ {
+ GSList *tmp;
+ const PlumaEncoding *enc;
+
+ tmp = strings;
+
+ while (tmp)
+ {
+ const char *charset = tmp->data;
+
+ if (strcmp (charset, "CURRENT") == 0)
+ g_get_charset (&charset);
+
+ g_return_val_if_fail (charset != NULL, NULL);
+ enc = pluma_encoding_get_from_charset (charset);
+
+ if (enc != NULL)
+ {
+ if (!data_exists (res, (gpointer)enc))
+ res = g_slist_prepend (res, (gpointer)enc);
+ }
+
+ tmp = g_slist_next (tmp);
+ }
+
+ g_slist_foreach (strings, (GFunc) g_free, NULL);
+ g_slist_free (strings);
+
+ res = g_slist_reverse (res);
+ }
+
+ return res;
+}
+
+void
+pluma_prefs_manager_set_shown_in_menu_encodings (const GSList *encs)
+{
+ GSList *list = NULL;
+
+ g_return_if_fail (pluma_prefs_manager != NULL);
+ g_return_if_fail (pluma_prefs_manager->mateconf_client != NULL);
+ g_return_if_fail (pluma_prefs_manager_shown_in_menu_encodings_can_set ());
+
+ while (encs != NULL)
+ {
+ const PlumaEncoding *enc;
+ const gchar *charset;
+
+ enc = (const PlumaEncoding *)encs->data;
+
+ charset = pluma_encoding_get_charset (enc);
+ g_return_if_fail (charset != NULL);
+
+ list = g_slist_prepend (list, (gpointer)charset);
+
+ encs = g_slist_next (encs);
+ }
+
+ list = g_slist_reverse (list);
+
+ mateconf_client_set_list (pluma_prefs_manager->mateconf_client,
+ GPM_SHOWN_IN_MENU_ENCODINGS,
+ MATECONF_VALUE_STRING,
+ list,
+ NULL);
+
+ g_slist_free (list);
+}
+
+gboolean
+pluma_prefs_manager_shown_in_menu_encodings_can_set (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_key_is_writable (GPM_SHOWN_IN_MENU_ENCODINGS);
+
+}
+
+/* Highlight current line */
+DEFINE_BOOL_PREF (highlight_current_line,
+ GPM_HIGHLIGHT_CURRENT_LINE,
+ GPM_DEFAULT_HIGHLIGHT_CURRENT_LINE)
+
+/* Highlight matching bracket */
+DEFINE_BOOL_PREF (bracket_matching,
+ GPM_BRACKET_MATCHING,
+ GPM_DEFAULT_BRACKET_MATCHING)
+
+/* Display Right Margin */
+DEFINE_BOOL_PREF (display_right_margin,
+ GPM_DISPLAY_RIGHT_MARGIN,
+ GPM_DEFAULT_DISPLAY_RIGHT_MARGIN)
+
+/* Right Margin Position */
+DEFINE_INT_PREF (right_margin_position,
+ GPM_RIGHT_MARGIN_POSITION,
+ GPM_DEFAULT_RIGHT_MARGIN_POSITION)
+
+static GtkSourceSmartHomeEndType
+get_smart_home_end_from_string (const gchar *str)
+{
+ GtkSourceSmartHomeEndType res;
+
+ g_return_val_if_fail (str != NULL, GTK_SOURCE_SMART_HOME_END_AFTER);
+
+ if (strcmp (str, "DISABLED") == 0)
+ res = GTK_SOURCE_SMART_HOME_END_DISABLED;
+ else if (strcmp (str, "BEFORE") == 0)
+ res = GTK_SOURCE_SMART_HOME_END_BEFORE;
+ else if (strcmp (str, "ALWAYS") == 0)
+ res = GTK_SOURCE_SMART_HOME_END_ALWAYS;
+ else
+ res = GTK_SOURCE_SMART_HOME_END_AFTER;
+
+ return res;
+}
+
+GtkSourceSmartHomeEndType
+pluma_prefs_manager_get_smart_home_end (void)
+{
+ gchar *str;
+ GtkSourceSmartHomeEndType res;
+
+ pluma_debug (DEBUG_PREFS);
+
+ str = pluma_prefs_manager_get_string (GPM_SMART_HOME_END,
+ GPM_DEFAULT_SMART_HOME_END);
+
+ res = get_smart_home_end_from_string (str);
+
+ g_free (str);
+
+ return res;
+}
+
+void
+pluma_prefs_manager_set_smart_home_end (GtkSourceSmartHomeEndType smart_he)
+{
+ const gchar *str;
+
+ pluma_debug (DEBUG_PREFS);
+
+ switch (smart_he)
+ {
+ case GTK_SOURCE_SMART_HOME_END_DISABLED:
+ str = "DISABLED";
+ break;
+
+ case GTK_SOURCE_SMART_HOME_END_BEFORE:
+ str = "BEFORE";
+ break;
+
+ case GTK_SOURCE_SMART_HOME_END_ALWAYS:
+ str = "ALWAYS";
+ break;
+
+ default: /* GTK_SOURCE_SMART_HOME_END_AFTER */
+ str = "AFTER";
+ }
+
+ pluma_prefs_manager_set_string (GPM_WRAP_MODE, str);
+}
+
+gboolean
+pluma_prefs_manager_smart_home_end_can_set (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_key_is_writable (GPM_SMART_HOME_END);
+}
+
+/* Enable syntax highlighting */
+DEFINE_BOOL_PREF (enable_syntax_highlighting,
+ GPM_SYNTAX_HL_ENABLE,
+ GPM_DEFAULT_SYNTAX_HL_ENABLE)
+
+/* Enable search highlighting */
+DEFINE_BOOL_PREF (enable_search_highlighting,
+ GPM_SEARCH_HIGHLIGHTING_ENABLE,
+ GPM_DEFAULT_SEARCH_HIGHLIGHTING_ENABLE)
+
+/* Source style scheme */
+DEFINE_STRING_PREF (source_style_scheme,
+ GPM_SOURCE_STYLE_SCHEME,
+ GPM_DEFAULT_SOURCE_STYLE_SCHEME)
+
+GSList *
+pluma_prefs_manager_get_writable_vfs_schemes (void)
+{
+ GSList *strings;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL, NULL);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL, NULL);
+
+ strings = mateconf_client_get_list (pluma_prefs_manager->mateconf_client,
+ GPM_WRITABLE_VFS_SCHEMES,
+ MATECONF_VALUE_STRING,
+ NULL);
+
+ if (strings == NULL)
+ {
+ gint i = 0;
+ const gchar* s[] = GPM_DEFAULT_WRITABLE_VFS_SCHEMES;
+
+ while (s[i] != NULL)
+ {
+ strings = g_slist_prepend (strings, g_strdup (s[i]));
+
+ ++i;
+ }
+
+ strings = g_slist_reverse (strings);
+ }
+
+ /* The 'file' scheme is writable by default. */
+ strings = g_slist_prepend (strings, g_strdup ("file"));
+
+ pluma_debug_message (DEBUG_PREFS, "Done");
+
+ return strings;
+}
+
+gboolean
+pluma_prefs_manager_get_restore_cursor_position (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_get_bool (GPM_RESTORE_CURSOR_POSITION,
+ GPM_DEFAULT_RESTORE_CURSOR_POSITION);
+}
+
+/* Plugins: we just store/return a list of strings, all the magic has to
+ * happen in the plugin engine */
+
+GSList *
+pluma_prefs_manager_get_active_plugins (void)
+{
+ GSList *plugins;
+
+ pluma_debug (DEBUG_PREFS);
+
+ g_return_val_if_fail (pluma_prefs_manager != NULL, NULL);
+ g_return_val_if_fail (pluma_prefs_manager->mateconf_client != NULL, NULL);
+
+ plugins = mateconf_client_get_list (pluma_prefs_manager->mateconf_client,
+ GPM_ACTIVE_PLUGINS,
+ MATECONF_VALUE_STRING,
+ NULL);
+
+ return plugins;
+}
+
+void
+pluma_prefs_manager_set_active_plugins (const GSList *plugins)
+{
+ g_return_if_fail (pluma_prefs_manager != NULL);
+ g_return_if_fail (pluma_prefs_manager->mateconf_client != NULL);
+ g_return_if_fail (pluma_prefs_manager_active_plugins_can_set ());
+
+ mateconf_client_set_list (pluma_prefs_manager->mateconf_client,
+ GPM_ACTIVE_PLUGINS,
+ MATECONF_VALUE_STRING,
+ (GSList *) plugins,
+ NULL);
+}
+
+gboolean
+pluma_prefs_manager_active_plugins_can_set (void)
+{
+ pluma_debug (DEBUG_PREFS);
+
+ return pluma_prefs_manager_key_is_writable (GPM_ACTIVE_PLUGINS);
+}
+
+/* Global Lockdown */
+
+PlumaLockdownMask
+pluma_prefs_manager_get_lockdown (void)
+{
+ guint lockdown = 0;
+
+ if (pluma_prefs_manager_get_bool (GPM_LOCKDOWN_COMMAND_LINE, FALSE))
+ lockdown |= PLUMA_LOCKDOWN_COMMAND_LINE;
+
+ if (pluma_prefs_manager_get_bool (GPM_LOCKDOWN_PRINTING, FALSE))
+ lockdown |= PLUMA_LOCKDOWN_PRINTING;
+
+ if (pluma_prefs_manager_get_bool (GPM_LOCKDOWN_PRINT_SETUP, FALSE))
+ lockdown |= PLUMA_LOCKDOWN_PRINT_SETUP;
+
+ if (pluma_prefs_manager_get_bool (GPM_LOCKDOWN_SAVE_TO_DISK, FALSE))
+ lockdown |= PLUMA_LOCKDOWN_SAVE_TO_DISK;
+
+ return lockdown;
+}
+
+/* The following functions are taken from mateconf-client.c
+ * and partially modified.
+ * The licensing terms on these is:
+ *
+ *
+ * MateConf
+ * Copyright (C) 1999, 2000, 2000 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+static const gchar*
+mateconf_value_type_to_string(MateConfValueType type)
+{
+ switch (type)
+ {
+ case MATECONF_VALUE_INT:
+ return "int";
+ break;
+ case MATECONF_VALUE_STRING:
+ return "string";
+ break;
+ case MATECONF_VALUE_FLOAT:
+ return "float";
+ break;
+ case MATECONF_VALUE_BOOL:
+ return "bool";
+ break;
+ case MATECONF_VALUE_SCHEMA:
+ return "schema";
+ break;
+ case MATECONF_VALUE_LIST:
+ return "list";
+ break;
+ case MATECONF_VALUE_PAIR:
+ return "pair";
+ break;
+ case MATECONF_VALUE_INVALID:
+ return "*invalid*";
+ break;
+ default:
+ g_return_val_if_reached (NULL);
+ break;
+ }
+}
+
+/* Emit the proper signals for the error, and fill in err */
+static gboolean
+handle_error (MateConfClient* client, GError* error, GError** err)
+{
+ if (error != NULL)
+ {
+ mateconf_client_error(client, error);
+
+ if (err == NULL)
+ {
+ mateconf_client_unreturned_error(client, error);
+
+ g_error_free(error);
+ }
+ else
+ *err = error;
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static gboolean
+check_type (const gchar* key, MateConfValue* val, MateConfValueType t, GError** err)
+{
+ if (val->type != t)
+ {
+ /*
+ mateconf_set_error(err, MATECONF_ERROR_TYPE_MISMATCH,
+ _("Expected `%s' got, `%s' for key %s"),
+ mateconf_value_type_to_string(t),
+ mateconf_value_type_to_string(val->type),
+ key);
+ */
+ g_set_error (err, MATECONF_ERROR, MATECONF_ERROR_TYPE_MISMATCH,
+ _("Expected `%s', got `%s' for key %s"),
+ mateconf_value_type_to_string(t),
+ mateconf_value_type_to_string(val->type),
+ key);
+
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+static gboolean
+mateconf_client_get_bool_with_default (MateConfClient* client, const gchar* key,
+ gboolean def, GError** err)
+{
+ GError* error = NULL;
+ MateConfValue* val;
+
+ g_return_val_if_fail (err == NULL || *err == NULL, def);
+
+ val = mateconf_client_get (client, key, &error);
+
+ if (val != NULL)
+ {
+ gboolean retval = def;
+
+ g_return_val_if_fail (error == NULL, retval);
+
+ if (check_type (key, val, MATECONF_VALUE_BOOL, &error))
+ retval = mateconf_value_get_bool (val);
+ else
+ handle_error (client, error, err);
+
+ mateconf_value_free (val);
+
+ return retval;
+ }
+ else
+ {
+ if (error != NULL)
+ handle_error (client, error, err);
+ return def;
+ }
+}
+
+static gchar*
+mateconf_client_get_string_with_default (MateConfClient* client, const gchar* key,
+ const gchar* def, GError** err)
+{
+ GError* error = NULL;
+ gchar* val;
+
+ g_return_val_if_fail (err == NULL || *err == NULL, def ? g_strdup (def) : NULL);
+
+ val = mateconf_client_get_string (client, key, &error);
+
+ if (val != NULL)
+ {
+ g_return_val_if_fail (error == NULL, def ? g_strdup (def) : NULL);
+
+ return val;
+ }
+ else
+ {
+ if (error != NULL)
+ handle_error (client, error, err);
+ return def ? g_strdup (def) : NULL;
+ }
+}
+
+static gint
+mateconf_client_get_int_with_default (MateConfClient* client, const gchar* key,
+ gint def, GError** err)
+{
+ GError* error = NULL;
+ MateConfValue* val;
+
+ g_return_val_if_fail (err == NULL || *err == NULL, def);
+
+ val = mateconf_client_get (client, key, &error);
+
+ if (val != NULL)
+ {
+ gint retval = def;
+
+ g_return_val_if_fail (error == NULL, def);
+
+ if (check_type (key, val, MATECONF_VALUE_INT, &error))
+ retval = mateconf_value_get_int(val);
+ else
+ handle_error (client, error, err);
+
+ mateconf_value_free (val);
+
+ return retval;
+ }
+ else
+ {
+ if (error != NULL)
+ handle_error (client, error, err);
+ return def;
+ }
+}
+
diff --git a/pluma/pluma-prefs-manager.h b/pluma/pluma-prefs-manager.h
new file mode 100755
index 00000000..f14fa431
--- /dev/null
+++ b/pluma/pluma-prefs-manager.h
@@ -0,0 +1,423 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-prefs-manager.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __PLUMA_PREFS_MANAGER_H__
+#define __PLUMA_PREFS_MANAGER_H__
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <gtksourceview/gtksourceview.h>
+#include "pluma-app.h"
+
+#define PLUMA_BASE_KEY "/apps/pluma-2"
+
+#define GPM_PREFS_DIR PLUMA_BASE_KEY "/preferences"
+
+/* Editor */
+#define GPM_FONT_DIR GPM_PREFS_DIR "/editor/font"
+#define GPM_USE_DEFAULT_FONT GPM_FONT_DIR "/use_default_font"
+#define GPM_EDITOR_FONT GPM_FONT_DIR "/editor_font"
+#define GPM_SYSTEM_FONT "/desktop/mate/interface/monospace_font_name"
+
+#define GPM_SAVE_DIR GPM_PREFS_DIR "/editor/save"
+#define GPM_CREATE_BACKUP_COPY GPM_SAVE_DIR "/create_backup_copy"
+
+#define GPM_AUTO_SAVE GPM_SAVE_DIR "/auto_save"
+#define GPM_AUTO_SAVE_INTERVAL GPM_SAVE_DIR "/auto_save_interval"
+
+#define GPM_UNDO_DIR GPM_PREFS_DIR "/editor/undo"
+#define GPM_UNDO_ACTIONS_LIMIT GPM_UNDO_DIR "/max_undo_actions"
+
+#define GPM_WRAP_MODE_DIR GPM_PREFS_DIR "/editor/wrap_mode"
+#define GPM_WRAP_MODE GPM_WRAP_MODE_DIR "/wrap_mode"
+
+#define GPM_TABS_DIR GPM_PREFS_DIR "/editor/tabs"
+#define GPM_TABS_SIZE GPM_TABS_DIR "/tabs_size"
+#define GPM_INSERT_SPACES GPM_TABS_DIR "/insert_spaces"
+
+#define GPM_AUTO_INDENT_DIR GPM_PREFS_DIR "/editor/auto_indent"
+#define GPM_AUTO_INDENT GPM_AUTO_INDENT_DIR "/auto_indent"
+
+#define GPM_LINE_NUMBERS_DIR GPM_PREFS_DIR "/editor/line_numbers"
+#define GPM_DISPLAY_LINE_NUMBERS GPM_LINE_NUMBERS_DIR "/display_line_numbers"
+
+#define GPM_CURRENT_LINE_DIR GPM_PREFS_DIR "/editor/current_line"
+#define GPM_HIGHLIGHT_CURRENT_LINE GPM_CURRENT_LINE_DIR "/highlight_current_line"
+
+#define GPM_BRACKET_MATCHING_DIR GPM_PREFS_DIR "/editor/bracket_matching"
+#define GPM_BRACKET_MATCHING GPM_BRACKET_MATCHING_DIR "/bracket_matching"
+
+#define GPM_RIGHT_MARGIN_DIR GPM_PREFS_DIR "/editor/right_margin"
+#define GPM_DISPLAY_RIGHT_MARGIN GPM_RIGHT_MARGIN_DIR "/display_right_margin"
+#define GPM_RIGHT_MARGIN_POSITION GPM_RIGHT_MARGIN_DIR "/right_margin_position"
+
+#define GPM_SMART_HOME_END_DIR GPM_PREFS_DIR "/editor/smart_home_end"
+#define GPM_SMART_HOME_END GPM_SMART_HOME_END_DIR "/smart_home_end"
+
+#define GPM_CURSOR_POSITION_DIR GPM_PREFS_DIR "/editor/cursor_position"
+#define GPM_RESTORE_CURSOR_POSITION GPM_CURSOR_POSITION_DIR "/restore_cursor_position"
+
+#define GPM_SEARCH_HIGHLIGHTING_DIR GPM_PREFS_DIR "/editor/search_highlighting"
+#define GPM_SEARCH_HIGHLIGHTING_ENABLE GPM_SEARCH_HIGHLIGHTING_DIR "/enable"
+
+#define GPM_SOURCE_STYLE_DIR GPM_PREFS_DIR "/editor/colors"
+#define GPM_SOURCE_STYLE_SCHEME GPM_SOURCE_STYLE_DIR "/scheme"
+
+/* UI */
+#define GPM_TOOLBAR_DIR GPM_PREFS_DIR "/ui/toolbar"
+#define GPM_TOOLBAR_VISIBLE GPM_TOOLBAR_DIR "/toolbar_visible"
+#define GPM_TOOLBAR_BUTTONS_STYLE GPM_TOOLBAR_DIR "/toolbar_buttons_style"
+
+#define GPM_STATUSBAR_DIR GPM_PREFS_DIR "/ui/statusbar"
+#define GPM_STATUSBAR_VISIBLE GPM_STATUSBAR_DIR "/statusbar_visible"
+
+#define GPM_SIDE_PANE_DIR GPM_PREFS_DIR "/ui/side_pane"
+#define GPM_SIDE_PANE_VISIBLE GPM_SIDE_PANE_DIR "/side_pane_visible"
+
+#define GPM_BOTTOM_PANEL_DIR GPM_PREFS_DIR "/ui/bottom_panel"
+#define GPM_BOTTOM_PANEL_VISIBLE GPM_BOTTOM_PANEL_DIR "/bottom_panel_visible"
+
+#define GPM_RECENTS_DIR GPM_PREFS_DIR "/ui/recents"
+#define GPM_MAX_RECENTS GPM_RECENTS_DIR "/max_recents"
+
+/* Print */
+#define GPM_PRINT_PAGE_DIR GPM_PREFS_DIR "/print/page"
+#define GPM_PRINT_SYNTAX GPM_PRINT_PAGE_DIR "/print_syntax_highlighting"
+#define GPM_PRINT_HEADER GPM_PRINT_PAGE_DIR "/print_header"
+#define GPM_PRINT_WRAP_MODE GPM_PRINT_PAGE_DIR "/print_wrap_mode"
+#define GPM_PRINT_LINE_NUMBERS GPM_PRINT_PAGE_DIR "/print_line_numbers"
+
+#define GPM_PRINT_FONT_DIR GPM_PREFS_DIR "/print/fonts"
+#define GPM_PRINT_FONT_BODY GPM_PRINT_FONT_DIR "/print_font_body_pango"
+#define GPM_PRINT_FONT_HEADER GPM_PRINT_FONT_DIR "/print_font_header_pango"
+#define GPM_PRINT_FONT_NUMBERS GPM_PRINT_FONT_DIR "/print_font_numbers_pango"
+
+/* Encodings */
+#define GPM_ENCODINGS_DIR GPM_PREFS_DIR "/encodings"
+#define GPM_AUTO_DETECTED_ENCODINGS GPM_ENCODINGS_DIR "/auto_detected"
+#define GPM_SHOWN_IN_MENU_ENCODINGS GPM_ENCODINGS_DIR "/shown_in_menu"
+
+/* Syntax highlighting */
+#define GPM_SYNTAX_HL_DIR GPM_PREFS_DIR "/syntax_highlighting"
+#define GPM_SYNTAX_HL_ENABLE GPM_SYNTAX_HL_DIR "/enable"
+
+/* White list of writable mate-vfs methods */
+#define GPM_WRITABLE_VFS_SCHEMES GPM_SAVE_DIR "/writable_vfs_schemes"
+
+/* Plugins */
+#define GPM_PLUGINS_DIR PLUMA_BASE_KEY "/plugins"
+#define GPM_ACTIVE_PLUGINS GPM_PLUGINS_DIR "/active-plugins"
+
+/* Global Lockdown keys */
+#define GPM_LOCKDOWN_DIR "/desktop/mate/lockdown"
+#define GPM_LOCKDOWN_COMMAND_LINE GPM_LOCKDOWN_DIR "/disable_command_line"
+#define GPM_LOCKDOWN_PRINTING GPM_LOCKDOWN_DIR "/disable_printing"
+#define GPM_LOCKDOWN_PRINT_SETUP GPM_LOCKDOWN_DIR "/disable_print_setup"
+#define GPM_LOCKDOWN_SAVE_TO_DISK GPM_LOCKDOWN_DIR "/disable_save_to_disk"
+
+/* Fallback default values. Keep in sync with pluma.schemas */
+
+#define GPM_DEFAULT_USE_DEFAULT_FONT 1 /* TRUE */
+
+#ifndef PLATFORM_OSX
+#define GPM_DEFAULT_EDITOR_FONT (const gchar*) "Monospace 12"
+#define GPM_DEFAULT_SYSTEM_FONT (const gchar*) "Monospace 10"
+#else
+#define GPM_DEFAULT_EDITOR_FONT (const gchar*) "Monaco 12"
+#define GPM_DEFAULT_SYSTEM_FONT (const gchar*) "Monaco 12"
+#endif
+
+#define GPM_DEFAULT_CREATE_BACKUP_COPY 0 /* FALSE */
+
+#define GPM_DEFAULT_AUTO_SAVE 0 /* FALSE */
+#define GPM_DEFAULT_AUTO_SAVE_INTERVAL 10 /* minutes */
+
+#define GPM_DEFAULT_UNDO_ACTIONS_LIMIT 2000 /* actions */
+
+#define GPM_DEFAULT_WRAP_MODE "GTK_WRAP_WORD"
+
+#define GPM_DEFAULT_TABS_SIZE 8
+#define GPM_DEFAULT_INSERT_SPACES 0 /* FALSE */
+
+#define GPM_DEFAULT_AUTO_INDENT 0 /* FALSE */
+
+#define GPM_DEFAULT_DISPLAY_LINE_NUMBERS 0 /* FALSE */
+
+#define GPM_DEFAULT_AUTO_DETECTED_ENCODINGS {"UTF-8", "CURRENT", "ISO-8859-15", NULL}
+
+#define GPM_DEFAULT_TOOLBAR_VISIBLE 1 /* TRUE */
+#define GPM_DEFAULT_TOOLBAR_BUTTONS_STYLE "PLUMA_TOOLBAR_SYSTEM"
+#define GPM_DEFAULT_TOOLBAR_SHOW_TOOLTIPS 1 /* TRUE */
+
+#define GPM_DEFAULT_STATUSBAR_VISIBLE 1 /* TRUE */
+#define GPM_DEFAULT_SIDE_PANE_VISIBLE 0 /* FALSE */
+#define GPM_DEFAULT_BOTTOM_PANEL_VISIBLE 0 /* FALSE */
+
+#define GPM_DEFAULT_PRINT_SYNTAX 1 /* TRUE */
+#define GPM_DEFAULT_PRINT_HEADER 1 /* TRUE */
+#define GPM_DEFAULT_PRINT_WRAP_MODE "GTK_WRAP_WORD"
+#define GPM_DEFAULT_PRINT_LINE_NUMBERS 0 /* No numbers */
+
+#ifndef PLATFORM_OSX
+#define GPM_DEFAULT_PRINT_FONT_BODY (const gchar*) "Monospace 9"
+#else
+#define GPM_DEFAULT_PRINT_FONT_BODY (const gchar*) "Monaco 10"
+#endif
+
+#define GPM_DEFAULT_PRINT_FONT_HEADER (const gchar*) "Sans 11"
+#define GPM_DEFAULT_PRINT_FONT_NUMBERS (const gchar*) "Sans 8"
+
+#define GPM_DEFAULT_MAX_RECENTS 5
+
+#define GPM_DEFAULT_HIGHLIGHT_CURRENT_LINE 1 /* TRUE */
+
+#define GPM_DEFAULT_BRACKET_MATCHING 0 /* FALSE */
+
+#define GPM_DEFAULT_DISPLAY_RIGHT_MARGIN 0 /* FALSE */
+#define GPM_DEFAULT_RIGHT_MARGIN_POSITION 80
+
+#define GPM_DEFAULT_SMART_HOME_END "AFTER"
+
+#define GPM_DEFAULT_SYNTAX_HL_ENABLE 1 /* TRUE */
+
+#define GPM_DEFAULT_WRITABLE_VFS_SCHEMES {"ssh", "sftp", "smb", "dav", "davs", NULL}
+
+#define GPM_DEFAULT_RESTORE_CURSOR_POSITION 1 /* TRUE */
+
+#define GPM_DEFAULT_SEARCH_HIGHLIGHTING_ENABLE 1 /* TRUE */
+
+#define GPM_DEFAULT_SOURCE_STYLE_SCHEME "classic"
+
+typedef enum {
+ PLUMA_TOOLBAR_SYSTEM = 0,
+ PLUMA_TOOLBAR_ICONS,
+ PLUMA_TOOLBAR_ICONS_AND_TEXT,
+ PLUMA_TOOLBAR_ICONS_BOTH_HORIZ
+} PlumaToolbarSetting;
+
+/** LIFE CYCLE MANAGEMENT FUNCTIONS **/
+
+gboolean pluma_prefs_manager_init (void);
+
+/* This function must be called before exiting pluma */
+void pluma_prefs_manager_shutdown (void);
+
+
+/** PREFS MANAGEMENT FUNCTIONS **/
+
+/* Use default font */
+gboolean pluma_prefs_manager_get_use_default_font (void);
+void pluma_prefs_manager_set_use_default_font (gboolean udf);
+gboolean pluma_prefs_manager_use_default_font_can_set (void);
+
+/* Editor font */
+gchar *pluma_prefs_manager_get_editor_font (void);
+void pluma_prefs_manager_set_editor_font (const gchar *font);
+gboolean pluma_prefs_manager_editor_font_can_set (void);
+
+/* System font */
+gchar *pluma_prefs_manager_get_system_font (void);
+
+/* Create backup copy */
+gboolean pluma_prefs_manager_get_create_backup_copy (void);
+void pluma_prefs_manager_set_create_backup_copy (gboolean cbc);
+gboolean pluma_prefs_manager_create_backup_copy_can_set (void);
+
+/* Auto save */
+gboolean pluma_prefs_manager_get_auto_save (void);
+void pluma_prefs_manager_set_auto_save (gboolean as);
+gboolean pluma_prefs_manager_auto_save_can_set (void);
+
+/* Auto save interval */
+gint pluma_prefs_manager_get_auto_save_interval (void);
+void pluma_prefs_manager_set_auto_save_interval (gint asi);
+gboolean pluma_prefs_manager_auto_save_interval_can_set (void);
+
+/* Undo actions limit: if < 1 then no limits */
+gint pluma_prefs_manager_get_undo_actions_limit (void);
+void pluma_prefs_manager_set_undo_actions_limit (gint ual);
+gboolean pluma_prefs_manager_undo_actions_limit_can_set (void);
+
+/* Wrap mode */
+GtkWrapMode pluma_prefs_manager_get_wrap_mode (void);
+void pluma_prefs_manager_set_wrap_mode (GtkWrapMode wp);
+gboolean pluma_prefs_manager_wrap_mode_can_set (void);
+
+/* Tabs size */
+gint pluma_prefs_manager_get_tabs_size (void);
+void pluma_prefs_manager_set_tabs_size (gint ts);
+gboolean pluma_prefs_manager_tabs_size_can_set (void);
+
+/* Insert spaces */
+gboolean pluma_prefs_manager_get_insert_spaces (void);
+void pluma_prefs_manager_set_insert_spaces (gboolean ai);
+gboolean pluma_prefs_manager_insert_spaces_can_set (void);
+
+/* Auto indent */
+gboolean pluma_prefs_manager_get_auto_indent (void);
+void pluma_prefs_manager_set_auto_indent (gboolean ai);
+gboolean pluma_prefs_manager_auto_indent_can_set (void);
+
+/* Display line numbers */
+gboolean pluma_prefs_manager_get_display_line_numbers (void);
+void pluma_prefs_manager_set_display_line_numbers (gboolean dln);
+gboolean pluma_prefs_manager_display_line_numbers_can_set (void);
+
+/* Toolbar visible */
+gboolean pluma_prefs_manager_get_toolbar_visible (void);
+void pluma_prefs_manager_set_toolbar_visible (gboolean tv);
+gboolean pluma_prefs_manager_toolbar_visible_can_set (void);
+
+/* Toolbar buttons style */
+PlumaToolbarSetting pluma_prefs_manager_get_toolbar_buttons_style (void);
+void pluma_prefs_manager_set_toolbar_buttons_style (PlumaToolbarSetting tbs);
+gboolean pluma_prefs_manager_toolbar_buttons_style_can_set (void);
+
+/* Statusbar visible */
+gboolean pluma_prefs_manager_get_statusbar_visible (void);
+void pluma_prefs_manager_set_statusbar_visible (gboolean sv);
+gboolean pluma_prefs_manager_statusbar_visible_can_set (void);
+
+/* Side pane visible */
+gboolean pluma_prefs_manager_get_side_pane_visible (void);
+void pluma_prefs_manager_set_side_pane_visible (gboolean tv);
+gboolean pluma_prefs_manager_side_pane_visible_can_set (void);
+
+/* Bottom panel visible */
+gboolean pluma_prefs_manager_get_bottom_panel_visible (void);
+void pluma_prefs_manager_set_bottom_panel_visible (gboolean tv);
+gboolean pluma_prefs_manager_bottom_panel_visible_can_set(void);
+/* Print syntax highlighting */
+gboolean pluma_prefs_manager_get_print_syntax_hl (void);
+void pluma_prefs_manager_set_print_syntax_hl (gboolean ps);
+gboolean pluma_prefs_manager_print_syntax_hl_can_set (void);
+
+/* Print header */
+gboolean pluma_prefs_manager_get_print_header (void);
+void pluma_prefs_manager_set_print_header (gboolean ph);
+gboolean pluma_prefs_manager_print_header_can_set (void);
+
+/* Wrap mode while printing */
+GtkWrapMode pluma_prefs_manager_get_print_wrap_mode (void);
+void pluma_prefs_manager_set_print_wrap_mode (GtkWrapMode pwm);
+gboolean pluma_prefs_manager_print_wrap_mode_can_set (void);
+
+/* Print line numbers */
+gint pluma_prefs_manager_get_print_line_numbers (void);
+void pluma_prefs_manager_set_print_line_numbers (gint pln);
+gboolean pluma_prefs_manager_print_line_numbers_can_set (void);
+
+/* Font used to print the body of documents */
+gchar *pluma_prefs_manager_get_print_font_body (void);
+void pluma_prefs_manager_set_print_font_body (const gchar *font);
+gboolean pluma_prefs_manager_print_font_body_can_set (void);
+const gchar *pluma_prefs_manager_get_default_print_font_body (void);
+
+/* Font used to print headers */
+gchar *pluma_prefs_manager_get_print_font_header (void);
+void pluma_prefs_manager_set_print_font_header (const gchar *font);
+gboolean pluma_prefs_manager_print_font_header_can_set (void);
+const gchar *pluma_prefs_manager_get_default_print_font_header (void);
+
+/* Font used to print line numbers */
+gchar *pluma_prefs_manager_get_print_font_numbers (void);
+void pluma_prefs_manager_set_print_font_numbers (const gchar *font);
+gboolean pluma_prefs_manager_print_font_numbers_can_set (void);
+const gchar *pluma_prefs_manager_get_default_print_font_numbers (void);
+
+/* Max number of files in "Recent Files" menu.
+ * This is configurable only using mateconftool or mateconf-editor
+ */
+gint pluma_prefs_manager_get_max_recents (void);
+
+/* Encodings */
+GSList *pluma_prefs_manager_get_auto_detected_encodings (void);
+
+GSList *pluma_prefs_manager_get_shown_in_menu_encodings (void);
+void pluma_prefs_manager_set_shown_in_menu_encodings (const GSList *encs);
+gboolean pluma_prefs_manager_shown_in_menu_encodings_can_set (void);
+
+/* Highlight current line */
+gboolean pluma_prefs_manager_get_highlight_current_line (void);
+void pluma_prefs_manager_set_highlight_current_line (gboolean hl);
+gboolean pluma_prefs_manager_highlight_current_line_can_set (void);
+
+/* Highlight matching bracket */
+gboolean pluma_prefs_manager_get_bracket_matching (void);
+void pluma_prefs_manager_set_bracket_matching (gboolean bm);
+gboolean pluma_prefs_manager_bracket_matching_can_set (void);
+
+/* Display right margin */
+gboolean pluma_prefs_manager_get_display_right_margin (void);
+void pluma_prefs_manager_set_display_right_margin (gboolean drm);
+gboolean pluma_prefs_manager_display_right_margin_can_set (void);
+
+/* Right margin position */
+gint pluma_prefs_manager_get_right_margin_position (void);
+void pluma_prefs_manager_set_right_margin_position (gint rmp);
+gboolean pluma_prefs_manager_right_margin_position_can_set (void);
+
+/* Smart home end */
+GtkSourceSmartHomeEndType
+ pluma_prefs_manager_get_smart_home_end (void);
+void pluma_prefs_manager_set_smart_home_end (GtkSourceSmartHomeEndType smart_he);
+gboolean pluma_prefs_manager_smart_home_end_can_set (void);
+
+/* Enable syntax highlighting */
+gboolean pluma_prefs_manager_get_enable_syntax_highlighting (void);
+void pluma_prefs_manager_set_enable_syntax_highlighting (gboolean esh);
+gboolean pluma_prefs_manager_enable_syntax_highlighting_can_set (void);
+
+/* Writable VFS schemes */
+GSList *pluma_prefs_manager_get_writable_vfs_schemes (void);
+
+/* Restore cursor position */
+gboolean pluma_prefs_manager_get_restore_cursor_position (void);
+
+/* Enable search highlighting */
+gboolean pluma_prefs_manager_get_enable_search_highlighting (void);
+void pluma_prefs_manager_set_enable_search_highlighting (gboolean esh);
+gboolean pluma_prefs_manager_enable_search_highlighting_can_set (void);
+
+/* Style scheme */
+gchar *pluma_prefs_manager_get_source_style_scheme (void);
+void pluma_prefs_manager_set_source_style_scheme (const gchar *scheme);
+gboolean pluma_prefs_manager_source_style_scheme_can_set(void);
+
+/* Plugins */
+GSList *pluma_prefs_manager_get_active_plugins (void);
+void pluma_prefs_manager_set_active_plugins (const GSList *plugins);
+gboolean pluma_prefs_manager_active_plugins_can_set (void);
+
+/* Global lockdown */
+PlumaLockdownMask pluma_prefs_manager_get_lockdown (void);
+
+#endif /* __PLUMA_PREFS_MANAGER_H__ */
+
+
diff --git a/pluma/pluma-print-job.c b/pluma/pluma-print-job.c
new file mode 100755
index 00000000..7016a45d
--- /dev/null
+++ b/pluma/pluma-print-job.c
@@ -0,0 +1,865 @@
+/*
+ * pluma-print.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2000-2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2008 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: pluma-print.c 6022 2007-12-09 14:38:57Z pborelli $
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksourceprintcompositor.h>
+
+#include "pluma-print-job.h"
+#include "pluma-debug.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-print-preview.h"
+#include "pluma-marshal.h"
+#include "pluma-utils.h"
+#include "pluma-dirs.h"
+
+
+#define PLUMA_PRINT_JOB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ PLUMA_TYPE_PRINT_JOB, \
+ PlumaPrintJobPrivate))
+
+struct _PlumaPrintJobPrivate
+{
+ PlumaView *view;
+ PlumaDocument *doc;
+
+ GtkPrintOperation *operation;
+ GtkSourcePrintCompositor *compositor;
+
+ GtkPrintSettings *settings;
+
+ GtkWidget *preview;
+
+ PlumaPrintJobStatus status;
+
+ gchar *status_string;
+
+ gdouble progress;
+
+ gboolean is_preview;
+
+ /* widgets part of the custom print preferences widget.
+ * These pointers are valid just when the dialog is displayed */
+ GtkWidget *syntax_checkbutton;
+ GtkWidget *page_header_checkbutton;
+ GtkWidget *line_numbers_checkbutton;
+ GtkWidget *line_numbers_hbox;
+ GtkWidget *line_numbers_spinbutton;
+ GtkWidget *text_wrapping_checkbutton;
+ GtkWidget *do_not_split_checkbutton;
+ GtkWidget *fonts_table;
+ GtkWidget *body_font_label;
+ GtkWidget *headers_font_label;
+ GtkWidget *numbers_font_label;
+ GtkWidget *body_fontbutton;
+ GtkWidget *headers_fontbutton;
+ GtkWidget *numbers_fontbutton;
+ GtkWidget *restore_button;
+};
+
+enum
+{
+ PROP_0,
+ PROP_VIEW
+};
+
+enum
+{
+ PRINTING,
+ SHOW_PREVIEW,
+ DONE,
+ LAST_SIGNAL
+};
+
+static guint print_job_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (PlumaPrintJob, pluma_print_job, G_TYPE_OBJECT)
+
+static void
+set_view (PlumaPrintJob *job, PlumaView *view)
+{
+ job->priv->view = view;
+ job->priv->doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+}
+
+static void
+pluma_print_job_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaPrintJob *job = PLUMA_PRINT_JOB (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ g_value_set_object (value, job->priv->view);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_print_job_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaPrintJob *job = PLUMA_PRINT_JOB (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ set_view (job, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_print_job_finalize (GObject *object)
+{
+ PlumaPrintJob *job = PLUMA_PRINT_JOB (object);
+
+ g_free (job->priv->status_string);
+
+ if (job->priv->compositor != NULL)
+ g_object_unref (job->priv->compositor);
+
+ if (job->priv->operation != NULL)
+ g_object_unref (job->priv->operation);
+
+ G_OBJECT_CLASS (pluma_print_job_parent_class)->finalize (object);
+}
+
+static void
+pluma_print_job_class_init (PlumaPrintJobClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = pluma_print_job_get_property;
+ object_class->set_property = pluma_print_job_set_property;
+ object_class->finalize = pluma_print_job_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_VIEW,
+ g_param_spec_object ("view",
+ "Pluma View",
+ "Pluma View to print",
+ PLUMA_TYPE_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ print_job_signals[PRINTING] =
+ g_signal_new ("printing",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaPrintJobClass, printing),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_UINT);
+
+ print_job_signals[SHOW_PREVIEW] =
+ g_signal_new ("show-preview",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaPrintJobClass, show_preview),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ GTK_TYPE_WIDGET);
+
+ print_job_signals[DONE] =
+ g_signal_new ("done",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaPrintJobClass, done),
+ NULL, NULL,
+ pluma_marshal_VOID__UINT_POINTER,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_UINT,
+ G_TYPE_POINTER);
+
+ g_type_class_add_private (object_class, sizeof (PlumaPrintJobPrivate));
+}
+
+static void
+line_numbers_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPrintJob *job)
+{
+ if (gtk_toggle_button_get_active (button))
+ {
+ gtk_widget_set_sensitive (job->priv->line_numbers_hbox,
+ pluma_prefs_manager_print_line_numbers_can_set ());
+ }
+ else
+ {
+ gtk_widget_set_sensitive (job->priv->line_numbers_hbox, FALSE);
+ }
+}
+
+static void
+wrap_mode_checkbutton_toggled (GtkToggleButton *button,
+ PlumaPrintJob *job)
+{
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->text_wrapping_checkbutton)))
+ {
+ gtk_widget_set_sensitive (job->priv->do_not_split_checkbutton,
+ FALSE);
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (job->priv->do_not_split_checkbutton),
+ TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (job->priv->do_not_split_checkbutton,
+ TRUE);
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (job->priv->do_not_split_checkbutton),
+ FALSE);
+ }
+}
+
+static void
+restore_button_clicked (GtkButton *button,
+ PlumaPrintJob *job)
+
+{
+ if (pluma_prefs_manager_print_font_body_can_set ())
+ {
+ const gchar *font;
+
+ font = pluma_prefs_manager_get_default_print_font_body ();
+
+ gtk_font_button_set_font_name (
+ GTK_FONT_BUTTON (job->priv->body_fontbutton),
+ font);
+ }
+
+ if (pluma_prefs_manager_print_font_header_can_set ())
+ {
+ const gchar *font;
+
+ font = pluma_prefs_manager_get_default_print_font_header ();
+
+ gtk_font_button_set_font_name (
+ GTK_FONT_BUTTON (job->priv->headers_fontbutton),
+ font);
+ }
+
+ if (pluma_prefs_manager_print_font_numbers_can_set ())
+ {
+ const gchar *font;
+
+ font = pluma_prefs_manager_get_default_print_font_numbers ();
+
+ gtk_font_button_set_font_name (
+ GTK_FONT_BUTTON (job->priv->numbers_fontbutton),
+ font);
+ }
+}
+
+static GObject *
+create_custom_widget_cb (GtkPrintOperation *operation,
+ PlumaPrintJob *job)
+{
+ gboolean ret;
+ GtkWidget *widget;
+ GtkWidget *error_widget;
+ gchar *font;
+ gint line_numbers;
+ gboolean can_set;
+ GtkWrapMode wrap_mode;
+ gchar *file;
+ gchar *root_objects[] = {
+ "adjustment1",
+ "contents",
+ NULL
+ };
+
+ file = pluma_dirs_get_ui_file ("pluma-print-preferences.ui");
+ ret = pluma_utils_get_ui_objects (file,
+ root_objects,
+ &error_widget,
+ "contents", &widget,
+ "syntax_checkbutton", &job->priv->syntax_checkbutton,
+ "line_numbers_checkbutton", &job->priv->line_numbers_checkbutton,
+ "line_numbers_hbox", &job->priv->line_numbers_hbox,
+ "line_numbers_spinbutton", &job->priv->line_numbers_spinbutton,
+ "page_header_checkbutton", &job->priv->page_header_checkbutton,
+ "text_wrapping_checkbutton", &job->priv->text_wrapping_checkbutton,
+ "do_not_split_checkbutton", &job->priv->do_not_split_checkbutton,
+ "fonts_table", &job->priv->fonts_table,
+ "body_font_label", &job->priv->body_font_label,
+ "body_fontbutton", &job->priv->body_fontbutton,
+ "headers_font_label", &job->priv->headers_font_label,
+ "headers_fontbutton", &job->priv->headers_fontbutton,
+ "numbers_font_label", &job->priv->numbers_font_label,
+ "numbers_fontbutton", &job->priv->numbers_fontbutton,
+ "restore_button", &job->priv->restore_button,
+ NULL);
+ g_free (file);
+
+ if (!ret)
+ {
+ return G_OBJECT (error_widget);
+ }
+
+ /* Print syntax */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (job->priv->syntax_checkbutton),
+ pluma_prefs_manager_get_print_syntax_hl ());
+ gtk_widget_set_sensitive (job->priv->syntax_checkbutton,
+ pluma_prefs_manager_print_syntax_hl_can_set ());
+
+ /* Print page headers */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (job->priv->page_header_checkbutton),
+ pluma_prefs_manager_get_print_header ());
+ gtk_widget_set_sensitive (job->priv->page_header_checkbutton,
+ pluma_prefs_manager_print_header_can_set ());
+
+ /* Line numbers */
+ line_numbers = pluma_prefs_manager_get_print_line_numbers ();
+ can_set = pluma_prefs_manager_print_line_numbers_can_set ();
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (job->priv->line_numbers_checkbutton),
+ line_numbers > 0);
+ gtk_widget_set_sensitive (job->priv->line_numbers_checkbutton, can_set);
+
+ if (line_numbers > 0)
+ {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (job->priv->line_numbers_spinbutton),
+ (guint) line_numbers);
+ gtk_widget_set_sensitive (job->priv->line_numbers_hbox, can_set);
+ }
+ else
+ {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (job->priv->line_numbers_spinbutton),
+ 1);
+ gtk_widget_set_sensitive (job->priv->line_numbers_hbox, FALSE);
+ }
+
+ /* Text wrapping */
+ wrap_mode = pluma_prefs_manager_get_print_wrap_mode ();
+
+ switch (wrap_mode)
+ {
+ case GTK_WRAP_WORD:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (job->priv->text_wrapping_checkbutton), TRUE);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (job->priv->do_not_split_checkbutton), TRUE);
+ break;
+ case GTK_WRAP_CHAR:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (job->priv->text_wrapping_checkbutton), TRUE);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (job->priv->do_not_split_checkbutton), FALSE);
+ break;
+ default:
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (job->priv->text_wrapping_checkbutton), FALSE);
+ gtk_toggle_button_set_inconsistent (
+ GTK_TOGGLE_BUTTON (job->priv->do_not_split_checkbutton), TRUE);
+ }
+
+ can_set = pluma_prefs_manager_print_wrap_mode_can_set ();
+
+ gtk_widget_set_sensitive (job->priv->text_wrapping_checkbutton, can_set);
+ gtk_widget_set_sensitive (job->priv->do_not_split_checkbutton,
+ can_set && (wrap_mode != GTK_WRAP_NONE));
+
+ /* Set initial values */
+ font = pluma_prefs_manager_get_print_font_body ();
+ gtk_font_button_set_font_name (GTK_FONT_BUTTON (job->priv->body_fontbutton),
+ font);
+ g_free (font);
+
+ font = pluma_prefs_manager_get_print_font_header ();
+ gtk_font_button_set_font_name (GTK_FONT_BUTTON (job->priv->headers_fontbutton),
+ font);
+ g_free (font);
+
+ font = pluma_prefs_manager_get_print_font_numbers ();
+ gtk_font_button_set_font_name (GTK_FONT_BUTTON (job->priv->numbers_fontbutton),
+ font);
+ g_free (font);
+
+ can_set = pluma_prefs_manager_print_font_body_can_set ();
+ gtk_widget_set_sensitive (job->priv->body_fontbutton, can_set);
+ gtk_widget_set_sensitive (job->priv->body_font_label, can_set);
+
+ can_set = pluma_prefs_manager_print_font_header_can_set ();
+ gtk_widget_set_sensitive (job->priv->headers_fontbutton, can_set);
+ gtk_widget_set_sensitive (job->priv->headers_font_label, can_set);
+
+ can_set = pluma_prefs_manager_print_font_numbers_can_set ();
+ gtk_widget_set_sensitive (job->priv->numbers_fontbutton, can_set);
+ gtk_widget_set_sensitive (job->priv->numbers_font_label, can_set);
+
+ g_signal_connect (job->priv->line_numbers_checkbutton,
+ "toggled",
+ G_CALLBACK (line_numbers_checkbutton_toggled),
+ job);
+ g_signal_connect (job->priv->text_wrapping_checkbutton,
+ "toggled",
+ G_CALLBACK (wrap_mode_checkbutton_toggled),
+ job);
+ g_signal_connect (job->priv->do_not_split_checkbutton,
+ "toggled",
+ G_CALLBACK (wrap_mode_checkbutton_toggled),
+ job);
+ g_signal_connect (job->priv->restore_button,
+ "clicked",
+ G_CALLBACK (restore_button_clicked),
+ job);
+
+ return G_OBJECT (widget);
+}
+
+static void
+custom_widget_apply_cb (GtkPrintOperation *operation,
+ GtkWidget *widget,
+ PlumaPrintJob *job)
+{
+ pluma_prefs_manager_set_print_syntax_hl (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->syntax_checkbutton)));
+
+ pluma_prefs_manager_set_print_header (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->page_header_checkbutton)));
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->line_numbers_checkbutton)))
+ {
+ pluma_prefs_manager_set_print_line_numbers (
+ MAX (1, gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (job->priv->line_numbers_spinbutton))));
+ }
+ else
+ {
+ pluma_prefs_manager_set_print_line_numbers (0);
+ }
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->text_wrapping_checkbutton)))
+ {
+ pluma_prefs_manager_set_print_wrap_mode (GTK_WRAP_NONE);
+ }
+ else
+ {
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->do_not_split_checkbutton)))
+ {
+ pluma_prefs_manager_set_print_wrap_mode (GTK_WRAP_WORD);
+ }
+ else
+ {
+ pluma_prefs_manager_set_print_wrap_mode (GTK_WRAP_CHAR);
+ }
+ }
+
+ pluma_prefs_manager_set_print_font_body (gtk_font_button_get_font_name (GTK_FONT_BUTTON (job->priv->body_fontbutton)));
+ pluma_prefs_manager_set_print_font_header (gtk_font_button_get_font_name (GTK_FONT_BUTTON (job->priv->headers_fontbutton)));
+ pluma_prefs_manager_set_print_font_numbers (gtk_font_button_get_font_name (GTK_FONT_BUTTON (job->priv->numbers_fontbutton)));
+}
+
+static void
+create_compositor (PlumaPrintJob *job)
+{
+ gchar *print_font_body;
+ gchar *print_font_header;
+ gchar *print_font_numbers;
+
+ /* Create and initialize print compositor */
+ print_font_body = pluma_prefs_manager_get_print_font_body ();
+ print_font_header = pluma_prefs_manager_get_print_font_header ();
+ print_font_numbers = pluma_prefs_manager_get_print_font_numbers ();
+
+ job->priv->compositor = GTK_SOURCE_PRINT_COMPOSITOR (
+ g_object_new (GTK_TYPE_SOURCE_PRINT_COMPOSITOR,
+ "buffer", GTK_SOURCE_BUFFER (job->priv->doc),
+ "tab-width", gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (job->priv->view)),
+ "highlight-syntax", gtk_source_buffer_get_highlight_syntax (GTK_SOURCE_BUFFER (job->priv->doc)) &&
+ pluma_prefs_manager_get_print_syntax_hl (),
+ "wrap-mode", pluma_prefs_manager_get_print_wrap_mode (),
+ "print-line-numbers", pluma_prefs_manager_get_print_line_numbers (),
+ "print-header", pluma_prefs_manager_get_print_header (),
+ "print-footer", FALSE,
+ "body-font-name", print_font_body,
+ "line-numbers-font-name", print_font_numbers,
+ "header-font-name", print_font_header,
+ NULL));
+
+ g_free (print_font_body);
+ g_free (print_font_header);
+ g_free (print_font_numbers);
+
+ if (pluma_prefs_manager_get_print_header ())
+ {
+ gchar *doc_name;
+ gchar *name_to_display;
+ gchar *left;
+
+ doc_name = pluma_document_get_uri_for_display (job->priv->doc);
+ name_to_display = pluma_utils_str_middle_truncate (doc_name, 60);
+
+ left = g_strdup_printf (_("File: %s"), name_to_display);
+
+ /* Translators: %N is the current page number, %Q is the total
+ * number of pages (ex. Page 2 of 10)
+ */
+ gtk_source_print_compositor_set_header_format (job->priv->compositor,
+ TRUE,
+ left,
+ NULL,
+ _("Page %N of %Q"));
+
+ g_free (doc_name);
+ g_free (name_to_display);
+ g_free (left);
+ }
+}
+
+static void
+begin_print_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ PlumaPrintJob *job)
+{
+ create_compositor (job);
+
+ job->priv->status = PLUMA_PRINT_JOB_STATUS_PAGINATING;
+
+ job->priv->progress = 0.0;
+
+ g_signal_emit (job, print_job_signals[PRINTING], 0, job->priv->status);
+}
+
+static void
+preview_ready (GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context,
+ PlumaPrintJob *job)
+{
+ job->priv->is_preview = TRUE;
+
+ g_signal_emit (job, print_job_signals[SHOW_PREVIEW], 0, job->priv->preview);
+}
+
+static void
+preview_destroyed (GtkWidget *preview,
+ GtkPrintOperationPreview *gtk_preview)
+{
+ gtk_print_operation_preview_end_preview (gtk_preview);
+}
+
+static gboolean
+preview_cb (GtkPrintOperation *op,
+ GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context,
+ GtkWindow *parent,
+ PlumaPrintJob *job)
+{
+ job->priv->preview = pluma_print_preview_new (op, gtk_preview, context);
+
+ g_signal_connect_after (gtk_preview,
+ "ready",
+ G_CALLBACK (preview_ready),
+ job);
+
+ /* FIXME: should this go in the preview widget itself? */
+ g_signal_connect (job->priv->preview,
+ "destroy",
+ G_CALLBACK (preview_destroyed),
+ gtk_preview);
+
+ return TRUE;
+}
+
+static gboolean
+paginate_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ PlumaPrintJob *job)
+{
+ gboolean res;
+
+ job->priv->status = PLUMA_PRINT_JOB_STATUS_PAGINATING;
+
+ res = gtk_source_print_compositor_paginate (job->priv->compositor, context);
+
+ if (res)
+ {
+ gint n_pages;
+
+ n_pages = gtk_source_print_compositor_get_n_pages (job->priv->compositor);
+ gtk_print_operation_set_n_pages (job->priv->operation, n_pages);
+ }
+
+ job->priv->progress = gtk_source_print_compositor_get_pagination_progress (job->priv->compositor);
+
+ /* When previewing, the progress is just for pagination, when printing
+ * it's split between pagination and rendering */
+ if (!job->priv->is_preview)
+ job->priv->progress /= 2.0;
+
+ g_signal_emit (job, print_job_signals[PRINTING], 0, job->priv->status);
+
+ return res;
+}
+
+static void
+draw_page_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ gint page_nr,
+ PlumaPrintJob *job)
+{
+ gint n_pages;
+
+ /* In preview, pages are drawn on the fly, so rendering is
+ * not part of the progress */
+ if (!job->priv->is_preview)
+ {
+ g_free (job->priv->status_string);
+
+ n_pages = gtk_source_print_compositor_get_n_pages (job->priv->compositor);
+
+ job->priv->status = PLUMA_PRINT_JOB_STATUS_DRAWING;
+
+ job->priv->status_string = g_strdup_printf ("Rendering page %d of %d...",
+ page_nr + 1,
+ n_pages);
+
+ job->priv->progress = page_nr / (2.0 * n_pages) + 0.5;
+
+ g_signal_emit (job, print_job_signals[PRINTING], 0, job->priv->status);
+ }
+
+ gtk_source_print_compositor_draw_page (job->priv->compositor, context, page_nr);
+}
+
+static void
+end_print_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ PlumaPrintJob *job)
+{
+ g_object_unref (job->priv->compositor);
+ job->priv->compositor = NULL;
+}
+
+static void
+done_cb (GtkPrintOperation *operation,
+ GtkPrintOperationResult result,
+ PlumaPrintJob *job)
+{
+ GError *error = NULL;
+ PlumaPrintJobResult print_result;
+
+ switch (result)
+ {
+ case GTK_PRINT_OPERATION_RESULT_CANCEL:
+ print_result = PLUMA_PRINT_JOB_RESULT_CANCEL;
+ break;
+
+ case GTK_PRINT_OPERATION_RESULT_APPLY:
+ print_result = PLUMA_PRINT_JOB_RESULT_OK;
+ break;
+
+ case GTK_PRINT_OPERATION_RESULT_ERROR:
+ print_result = PLUMA_PRINT_JOB_RESULT_ERROR;
+ gtk_print_operation_get_error (operation, &error);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ /* Avoid job is destroyed in the handler of the "done" message */
+ g_object_ref (job);
+
+ g_signal_emit (job, print_job_signals[DONE], 0, print_result, error);
+
+ g_object_unref (operation);
+ job->priv->operation = NULL;
+
+ g_object_unref (job);
+}
+
+/* Note that pluma_print_job_print can can only be called once on a given PlumaPrintJob */
+GtkPrintOperationResult
+pluma_print_job_print (PlumaPrintJob *job,
+ GtkPrintOperationAction action,
+ GtkPageSetup *page_setup,
+ GtkPrintSettings *settings,
+ GtkWindow *parent,
+ GError **error)
+{
+ PlumaPrintJobPrivate *priv;
+ gchar *job_name;
+
+ g_return_val_if_fail (job->priv->compositor == NULL, GTK_PRINT_OPERATION_RESULT_ERROR);
+
+ priv = job->priv;
+
+ /* Check if we are previewing */
+ priv->is_preview = (action == GTK_PRINT_OPERATION_ACTION_PREVIEW);
+
+ /* Create print operation */
+ job->priv->operation = gtk_print_operation_new ();
+
+ if (settings)
+ gtk_print_operation_set_print_settings (priv->operation,
+ settings);
+
+ if (page_setup != NULL)
+ gtk_print_operation_set_default_page_setup (priv->operation,
+ page_setup);
+
+ job_name = pluma_document_get_short_name_for_display (priv->doc);
+ gtk_print_operation_set_job_name (priv->operation, job_name);
+ g_free (job_name);
+
+#if GTK_CHECK_VERSION (2, 17, 4)
+ gtk_print_operation_set_embed_page_setup (priv->operation, TRUE);
+#endif
+
+ gtk_print_operation_set_custom_tab_label (priv->operation,
+ _("Text Editor"));
+
+ gtk_print_operation_set_allow_async (priv->operation, TRUE);
+
+ g_signal_connect (priv->operation,
+ "create-custom-widget",
+ G_CALLBACK (create_custom_widget_cb),
+ job);
+ g_signal_connect (priv->operation,
+ "custom-widget-apply",
+ G_CALLBACK (custom_widget_apply_cb),
+ job);
+ g_signal_connect (priv->operation,
+ "begin-print",
+ G_CALLBACK (begin_print_cb),
+ job);
+ g_signal_connect (priv->operation,
+ "preview",
+ G_CALLBACK (preview_cb),
+ job);
+ g_signal_connect (priv->operation,
+ "paginate",
+ G_CALLBACK (paginate_cb),
+ job);
+ g_signal_connect (priv->operation,
+ "draw-page",
+ G_CALLBACK (draw_page_cb),
+ job);
+ g_signal_connect (priv->operation,
+ "end-print",
+ G_CALLBACK (end_print_cb),
+ job);
+ g_signal_connect (priv->operation,
+ "done",
+ G_CALLBACK (done_cb),
+ job);
+
+ return gtk_print_operation_run (priv->operation,
+ action,
+ parent,
+ error);
+}
+
+static void
+pluma_print_job_init (PlumaPrintJob *job)
+{
+ job->priv = PLUMA_PRINT_JOB_GET_PRIVATE (job);
+
+ job->priv->status = PLUMA_PRINT_JOB_STATUS_INIT;
+
+ job->priv->status_string = g_strdup (_("Preparing..."));
+}
+
+PlumaPrintJob *
+pluma_print_job_new (PlumaView *view)
+{
+ PlumaPrintJob *job;
+
+ g_return_val_if_fail (PLUMA_IS_VIEW (view), NULL);
+
+ job = PLUMA_PRINT_JOB (g_object_new (PLUMA_TYPE_PRINT_JOB,
+ "view", view,
+ NULL));
+
+ return job;
+}
+
+void
+pluma_print_job_cancel (PlumaPrintJob *job)
+{
+ g_return_if_fail (PLUMA_IS_PRINT_JOB (job));
+
+ gtk_print_operation_cancel (job->priv->operation);
+}
+
+const gchar *
+pluma_print_job_get_status_string (PlumaPrintJob *job)
+{
+ g_return_val_if_fail (PLUMA_IS_PRINT_JOB (job), NULL);
+ g_return_val_if_fail (job->priv->status_string != NULL, NULL);
+
+ return job->priv->status_string;
+}
+
+gdouble
+pluma_print_job_get_progress (PlumaPrintJob *job)
+{
+ g_return_val_if_fail (PLUMA_IS_PRINT_JOB (job), 0.0);
+
+ return job->priv->progress;
+}
+
+GtkPrintSettings *
+pluma_print_job_get_print_settings (PlumaPrintJob *job)
+{
+ g_return_val_if_fail (PLUMA_IS_PRINT_JOB (job), NULL);
+
+ return gtk_print_operation_get_print_settings (job->priv->operation);
+}
+
+GtkPageSetup *
+pluma_print_job_get_page_setup (PlumaPrintJob *job)
+{
+ g_return_val_if_fail (PLUMA_IS_PRINT_JOB (job), NULL);
+
+ return gtk_print_operation_get_default_page_setup (job->priv->operation);
+}
diff --git a/pluma/pluma-print-job.h b/pluma/pluma-print-job.h
new file mode 100755
index 00000000..ebda70cc
--- /dev/null
+++ b/pluma/pluma-print-job.h
@@ -0,0 +1,133 @@
+/*
+ * pluma-print-job.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2000-2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2008 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PRINT_JOB_H__
+#define __PLUMA_PRINT_JOB_H__
+
+#include <gtk/gtk.h>
+#include <pluma/pluma-view.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_PRINT_JOB (pluma_print_job_get_type())
+#define PLUMA_PRINT_JOB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PRINT_JOB, PlumaPrintJob))
+#define PLUMA_PRINT_JOB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_PRINT_JOB, PlumaPrintJobClass))
+#define PLUMA_IS_PRINT_JOB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_PRINT_JOB))
+#define PLUMA_IS_PRINT_JOB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PRINT_JOB))
+#define PLUMA_PRINT_JOB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_PRINT_JOB, PlumaPrintJobClass))
+
+
+typedef enum
+{
+ PLUMA_PRINT_JOB_STATUS_INIT,
+ PLUMA_PRINT_JOB_STATUS_PAGINATING,
+ PLUMA_PRINT_JOB_STATUS_DRAWING,
+ PLUMA_PRINT_JOB_STATUS_DONE
+} PlumaPrintJobStatus;
+
+typedef enum
+{
+ PLUMA_PRINT_JOB_RESULT_OK,
+ PLUMA_PRINT_JOB_RESULT_CANCEL,
+ PLUMA_PRINT_JOB_RESULT_ERROR
+} PlumaPrintJobResult;
+
+/* Private structure type */
+typedef struct _PlumaPrintJobPrivate PlumaPrintJobPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaPrintJob PlumaPrintJob;
+
+
+struct _PlumaPrintJob
+{
+ GObject parent;
+
+ /* <private> */
+ PlumaPrintJobPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaPrintJobClass PlumaPrintJobClass;
+
+struct _PlumaPrintJobClass
+{
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (* printing) (PlumaPrintJob *job,
+ PlumaPrintJobStatus status);
+
+ void (* show_preview) (PlumaPrintJob *job,
+ GtkWidget *preview);
+
+ void (*done) (PlumaPrintJob *job,
+ PlumaPrintJobResult result,
+ const GError *error);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_print_job_get_type (void) G_GNUC_CONST;
+
+PlumaPrintJob *pluma_print_job_new (PlumaView *view);
+
+void pluma_print_job_set_export_filename (PlumaPrintJob *job,
+ const gchar *filename);
+
+GtkPrintOperationResult pluma_print_job_print (PlumaPrintJob *job,
+ GtkPrintOperationAction action,
+ GtkPageSetup *page_setup,
+ GtkPrintSettings *settings,
+ GtkWindow *parent,
+ GError **error);
+
+void pluma_print_job_cancel (PlumaPrintJob *job);
+
+const gchar *pluma_print_job_get_status_string (PlumaPrintJob *job);
+
+gdouble pluma_print_job_get_progress (PlumaPrintJob *job);
+
+GtkPrintSettings *pluma_print_job_get_print_settings (PlumaPrintJob *job);
+
+GtkPageSetup *pluma_print_job_get_page_setup (PlumaPrintJob *job);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PRINT_JOB_H__ */
diff --git a/pluma/pluma-print-preferences.ui b/pluma/pluma-print-preferences.ui
new file mode 100755
index 00000000..3917eeba
--- /dev/null
+++ b/pluma/pluma-print-preferences.ui
@@ -0,0 +1,497 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="value">1</property>
+ <property name="lower">1</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkWindow" id="window1">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">window1</property>
+ <child>
+ <object class="GtkHBox" id="contents">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox20">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox26">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Syntax Highlighting</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkCheckButton" id="syntax_checkbutton">
+ <property name="label" translatable="yes">Print synta_x highlighting</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox22">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label27">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Line Numbers</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="line_numbers_checkbutton">
+ <property name="label" translatable="yes">Print line nu_mbers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="line_numbers_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0.47999998927116394</property>
+ <property name="label" translatable="yes" comments="'Number every' from 'Number every 3 lines' in the 'Text Editor' tab of the print preferences.">_Number every</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">line_numbers_spinbutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="line_numbers_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes" comments="'lines' from 'Number every 3 lines' in the 'Text Editor' tab of the print preferences.">lines</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox24">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Text Wrapping</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox25">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="text_wrapping_checkbutton">
+ <property name="label" translatable="yes">Enable text _wrapping</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox20">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkCheckButton" id="do_not_split_checkbutton">
+ <property name="label" translatable="yes">Do not _split words over two lines</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </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="GtkVBox" id="vbox39">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label45">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Page header</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkCheckButton" id="page_header_checkbutton">
+ <property name="label" translatable="yes">Print page _headers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox36">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox37">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label43">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Fonts</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkTable" id="fonts_table">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="body_font_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Body:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">body_fontbutton</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="numbers_font_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Line numbers:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">numbers_fontbutton</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="headers_font_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">He_aders and footers:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">headers_fontbutton</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="body_fontbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_font">True</property>
+ <property name="show_style">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="numbers_fontbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_font">True</property>
+ <property name="show_style">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="headers_fontbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_font">True</property>
+ <property name="show_style">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="restore_button">
+ <property name="label" translatable="yes">_Restore Default Fonts</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">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="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/pluma/pluma-print-preview.c b/pluma/pluma-print-preview.c
new file mode 100755
index 00000000..d1b8d746
--- /dev/null
+++ b/pluma/pluma-print-preview.c
@@ -0,0 +1,1327 @@
+/*
+ * pluma-print-preview.c
+ *
+ * Copyright (C) 2008 Paolo Borelli
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: pluma-commands-search.c 5931 2007-09-25 20:05:40Z pborelli $
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <cairo-pdf.h>
+
+#include "pluma-print-preview.h"
+
+#define PRINTER_DPI (72.)
+
+struct _PlumaPrintPreviewPrivate
+{
+ GtkPrintOperation *operation;
+ GtkPrintContext *context;
+ GtkPrintOperationPreview *gtk_preview;
+
+ GtkWidget *layout;
+ GtkWidget *scrolled_window;
+
+ GtkToolItem *next;
+ GtkToolItem *prev;
+ GtkWidget *page_entry;
+ GtkWidget *last;
+ GtkToolItem *multi;
+ GtkToolItem *zoom_one;
+ GtkToolItem *zoom_fit;
+ GtkToolItem *zoom_in;
+ GtkToolItem *zoom_out;
+
+ /* real size of the page in inches */
+ double paper_w;
+ double paper_h;
+ double dpi;
+
+ double scale;
+
+ /* size of the tile of a page (including padding
+ * and drop shadow) in pixels */
+ gint tile_w;
+ gint tile_h;
+
+ GtkPageOrientation orientation;
+
+ /* multipage support */
+ gint rows;
+ gint cols;
+
+ guint n_pages;
+ guint cur_page;
+};
+
+G_DEFINE_TYPE (PlumaPrintPreview, pluma_print_preview, GTK_TYPE_VBOX)
+
+static void
+pluma_print_preview_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ //PlumaPrintPreview *preview = PLUMA_PRINT_PREVIEW (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_print_preview_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ //PlumaPrintPreview *preview = PLUMA_PRINT_PREVIEW (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_print_preview_finalize (GObject *object)
+{
+ //PlumaPrintPreview *preview = PLUMA_PRINT_PREVIEW (object);
+
+ G_OBJECT_CLASS (pluma_print_preview_parent_class)->finalize (object);
+}
+
+static void
+pluma_print_preview_grab_focus (GtkWidget *widget)
+{
+ PlumaPrintPreview *preview;
+
+ preview = PLUMA_PRINT_PREVIEW (widget);
+
+ gtk_widget_grab_focus (GTK_WIDGET (preview->priv->layout));
+}
+
+static void
+pluma_print_preview_class_init (PlumaPrintPreviewClass *klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = pluma_print_preview_get_property;
+ object_class->set_property = pluma_print_preview_set_property;
+ object_class->finalize = pluma_print_preview_finalize;
+
+ widget_class->grab_focus = pluma_print_preview_grab_focus;
+
+ g_type_class_add_private (object_class, sizeof(PlumaPrintPreviewPrivate));
+}
+
+static void
+update_layout_size (PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+
+ priv = preview->priv;
+
+ /* force size of the drawing area to make the scrolled window work */
+ gtk_layout_set_size (GTK_LAYOUT (priv->layout),
+ priv->tile_w * priv->cols,
+ priv->tile_h * priv->rows);
+
+ gtk_widget_queue_draw (preview->priv->layout);
+}
+
+static void
+set_rows_and_cols (PlumaPrintPreview *preview,
+ gint rows,
+ gint cols)
+{
+ /* TODO: set the zoom appropriately */
+
+ preview->priv->rows = rows;
+ preview->priv->cols = cols;
+ update_layout_size (preview);
+}
+
+/* get the paper size in points: these must be used only
+ * after the widget has been mapped and the dpi is known */
+
+static double
+get_paper_width (PlumaPrintPreview *preview)
+{
+ return preview->priv->paper_w * preview->priv->dpi;
+}
+
+static double
+get_paper_height (PlumaPrintPreview *preview)
+{
+ return preview->priv->paper_h * preview->priv->dpi;
+}
+
+#define PAGE_PAD 12
+#define PAGE_SHADOW_OFFSET 5
+
+/* The tile size is the size of the area where a page
+ * will be drawn including the padding and idependent
+ * of the orientation */
+
+/* updates the tile size to the current zoom and page size */
+static void
+update_tile_size (PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+ gint w, h;
+
+ priv = preview->priv;
+
+ w = 2 * PAGE_PAD + floor (priv->scale * get_paper_width (preview) + 0.5);
+ h = 2 * PAGE_PAD + floor (priv->scale * get_paper_height (preview) + 0.5);
+
+ if ((priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
+ (priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
+ {
+ priv->tile_w = h;
+ priv->tile_h = w;
+ }
+ else
+ {
+ priv->tile_w = w;
+ priv->tile_h = h;
+ }
+}
+
+/* Zoom should always be set with one of these two function
+ * so that the tile size is properly updated */
+
+static void
+set_zoom_factor (PlumaPrintPreview *preview,
+ double zoom)
+{
+ PlumaPrintPreviewPrivate *priv;
+
+ priv = preview->priv;
+
+ priv->scale = zoom;
+
+ update_tile_size (preview);
+ update_layout_size (preview);
+}
+
+static void
+set_zoom_fit_to_size (PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+ double width, height;
+ double p_width, p_height;
+ double zoomx, zoomy;
+
+ priv = preview->priv;
+
+ g_object_get (gtk_layout_get_hadjustment (GTK_LAYOUT (priv->layout)),
+ "page-size", &width,
+ NULL);
+ g_object_get (gtk_layout_get_vadjustment (GTK_LAYOUT (priv->layout)),
+ "page-size", &height,
+ NULL);
+
+ width /= priv->cols;
+ height /= priv->rows;
+
+ if ((priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
+ (priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
+ {
+ p_width = get_paper_height (preview);
+ p_height = get_paper_width (preview);
+ }
+ else
+ {
+ p_width = get_paper_width (preview);
+ p_height = get_paper_height (preview);
+ }
+
+ zoomx = MAX (1, width - 2 * PAGE_PAD) / p_width;
+ zoomy = MAX (1, height - 2 * PAGE_PAD) / p_height;
+
+ if (zoomx <= zoomy)
+ {
+ priv->tile_w = width;
+ priv->tile_h = floor (0.5 + width * (p_height / p_width));
+ priv->scale = zoomx;
+ }
+ else
+ {
+ priv->tile_w = floor (0.5 + height * (p_width / p_height));
+ priv->tile_h = height;
+ priv->scale = zoomy;
+ }
+
+ update_layout_size (preview);
+}
+
+#define ZOOM_IN_FACTOR (1.2)
+#define ZOOM_OUT_FACTOR (1.0 / ZOOM_IN_FACTOR)
+
+static void
+zoom_in (PlumaPrintPreview *preview)
+{
+ set_zoom_factor (preview,
+ preview->priv->scale * ZOOM_IN_FACTOR);
+}
+
+static void
+zoom_out (PlumaPrintPreview *preview)
+{
+ set_zoom_factor (preview,
+ preview->priv->scale * ZOOM_OUT_FACTOR);
+}
+
+static void
+goto_page (PlumaPrintPreview *preview, gint page)
+{
+ gchar c[32];
+
+ g_snprintf (c, 32, "%d", page + 1);
+ gtk_entry_set_text (GTK_ENTRY (preview->priv->page_entry), c);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (preview->priv->prev),
+ (page > 0) && (preview->priv->n_pages > 1));
+ gtk_widget_set_sensitive (GTK_WIDGET (preview->priv->next),
+ (page != (preview->priv->n_pages - 1)) &&
+ (preview->priv->n_pages > 1));
+
+ if (page != preview->priv->cur_page)
+ {
+ preview->priv->cur_page = page;
+ if (preview->priv->n_pages > 0)
+ gtk_widget_queue_draw (preview->priv->layout);
+ }
+}
+
+static void
+prev_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ GdkEvent *event;
+ gint page;
+
+ event = gtk_get_current_event ();
+
+ if (event->button.state & GDK_SHIFT_MASK)
+ page = 0;
+ else
+ page = preview->priv->cur_page - preview->priv->rows * preview->priv->cols;
+
+ goto_page (preview, MAX (page, 0));
+
+ gdk_event_free (event);
+}
+
+static void
+next_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ GdkEvent *event;
+ gint page;
+
+ event = gtk_get_current_event ();
+
+ if (event->button.state & GDK_SHIFT_MASK)
+ page = preview->priv->n_pages - 1;
+ else
+ page = preview->priv->cur_page + preview->priv->rows * preview->priv->cols;
+
+ goto_page (preview, MIN (page, preview->priv->n_pages - 1));
+
+ gdk_event_free (event);
+}
+
+static void
+page_entry_activated (GtkEntry *entry,
+ PlumaPrintPreview *preview)
+{
+ const gchar *text;
+ gint page;
+
+ text = gtk_entry_get_text (entry);
+
+ page = CLAMP (atoi (text), 1, preview->priv->n_pages) - 1;
+ goto_page (preview, page);
+
+ gtk_widget_grab_focus (GTK_WIDGET (preview->priv->layout));
+}
+
+static void
+page_entry_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position)
+{
+ gunichar c;
+ const gchar *p;
+ const gchar *end;
+
+ p = text;
+ end = text + length;
+
+ while (p != end)
+ {
+ const gchar *next;
+ next = g_utf8_next_char (p);
+
+ c = g_utf8_get_char (p);
+
+ if (!g_unichar_isdigit (c))
+ {
+ g_signal_stop_emission_by_name (editable, "insert-text");
+ break;
+ }
+
+ p = next;
+ }
+}
+
+static gboolean
+page_entry_focus_out (GtkWidget *widget,
+ GdkEventFocus *event,
+ PlumaPrintPreview *preview)
+{
+ const gchar *text;
+ gint page;
+
+ text = gtk_entry_get_text (GTK_ENTRY (widget));
+ page = atoi (text) - 1;
+
+ /* Reset the page number only if really needed */
+ if (page != preview->priv->cur_page)
+ {
+ gchar *str;
+
+ str = g_strdup_printf ("%d", preview->priv->cur_page + 1);
+ gtk_entry_set_text (GTK_ENTRY (widget), str);
+ g_free (str);
+ }
+
+ return FALSE;
+}
+
+static void
+on_1x1_clicked (GtkMenuItem *i, PlumaPrintPreview *preview)
+{
+ set_rows_and_cols (preview, 1, 1);
+}
+
+static void
+on_1x2_clicked (GtkMenuItem *i, PlumaPrintPreview *preview)
+{
+ set_rows_and_cols (preview, 1, 2);
+}
+
+static void
+on_2x1_clicked (GtkMenuItem *i, PlumaPrintPreview *preview)
+{
+ set_rows_and_cols (preview, 2, 1);
+}
+
+static void
+on_2x2_clicked (GtkMenuItem *i, PlumaPrintPreview *preview)
+{
+ set_rows_and_cols (preview, 2, 2);
+}
+
+static void
+multi_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ GtkWidget *m, *i;
+
+ m = gtk_menu_new ();
+ gtk_widget_show (m);
+ g_signal_connect (m,
+ "selection_done",
+ G_CALLBACK (gtk_widget_destroy),
+ m);
+
+ i = gtk_menu_item_new_with_label ("1x1");
+ gtk_widget_show (i);
+ gtk_menu_attach (GTK_MENU (m), i, 0, 1, 0, 1);
+ g_signal_connect (i, "activate", G_CALLBACK (on_1x1_clicked), preview);
+
+ i = gtk_menu_item_new_with_label ("2x1");
+ gtk_widget_show (i);
+ gtk_menu_attach (GTK_MENU (m), i, 0, 1, 1, 2);
+ g_signal_connect (i, "activate", G_CALLBACK (on_2x1_clicked), preview);
+
+ i = gtk_menu_item_new_with_label ("1x2");
+ gtk_widget_show (i);
+ gtk_menu_attach (GTK_MENU (m), i, 1, 2, 0, 1);
+ g_signal_connect (i, "activate", G_CALLBACK (on_1x2_clicked), preview);
+
+ i = gtk_menu_item_new_with_label ("2x2");
+ gtk_widget_show (i);
+ gtk_menu_attach (GTK_MENU (m), i, 1, 2, 1, 2);
+ g_signal_connect (i, "activate", G_CALLBACK (on_2x2_clicked), preview);
+
+ gtk_menu_popup (GTK_MENU (m),
+ NULL, NULL, NULL, preview, 0,
+ GDK_CURRENT_TIME);
+}
+
+static void
+zoom_one_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ set_zoom_factor (preview, 1);
+}
+
+static void
+zoom_fit_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ set_zoom_fit_to_size (preview);
+}
+
+static void
+zoom_in_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ zoom_in (preview);
+}
+
+static void
+zoom_out_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ zoom_out (preview);
+}
+
+static void
+close_button_clicked (GtkWidget *button,
+ PlumaPrintPreview *preview)
+{
+ gtk_widget_destroy (GTK_WIDGET (preview));
+}
+
+static void
+create_bar (PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+ GtkWidget *toolbar;
+ GtkToolItem *i;
+ AtkObject *atko;
+ GtkWidget *status;
+
+ priv = preview->priv;
+
+ toolbar = gtk_toolbar_new ();
+ gtk_toolbar_set_style (GTK_TOOLBAR (toolbar),
+ GTK_TOOLBAR_BOTH_HORIZ);
+ gtk_widget_show (toolbar);
+ gtk_box_pack_start (GTK_BOX (preview),
+ toolbar,
+ FALSE, FALSE, 0);
+
+ priv->prev = gtk_tool_button_new_from_stock (GTK_STOCK_GO_BACK);
+ gtk_tool_button_set_label (GTK_TOOL_BUTTON (priv->prev),
+ "P_revious Page");
+ gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (priv->prev), TRUE);
+ gtk_tool_item_set_tooltip_text (priv->prev, _("Show the previous page"));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), priv->prev, -1);
+ g_signal_connect (priv->prev,
+ "clicked",
+ G_CALLBACK (prev_button_clicked),
+ preview);
+ gtk_widget_show (GTK_WIDGET (priv->prev));
+
+ priv->next = gtk_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD);
+ gtk_tool_button_set_label (GTK_TOOL_BUTTON (priv->next),
+ "_Next Page");
+ gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (priv->next), TRUE);
+ gtk_tool_item_set_tooltip_text (priv->next, _("Show the next page"));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), priv->next, -1);
+ g_signal_connect (priv->next,
+ "clicked",
+ G_CALLBACK (next_button_clicked),
+ preview);
+ gtk_widget_show (GTK_WIDGET (priv->next));
+
+ i = gtk_separator_tool_item_new ();
+ gtk_widget_show (GTK_WIDGET (i));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
+
+ status = gtk_hbox_new (FALSE, 4);
+ priv->page_entry = gtk_entry_new ();
+ gtk_entry_set_width_chars (GTK_ENTRY (priv->page_entry), 3);
+ gtk_entry_set_max_length (GTK_ENTRY (priv->page_entry), 6);
+ gtk_widget_set_tooltip_text (priv->page_entry, _("Current page (Alt+P)"));
+
+ g_signal_connect (priv->page_entry,
+ "activate",
+ G_CALLBACK (page_entry_activated),
+ preview);
+ g_signal_connect (priv->page_entry,
+ "insert-text",
+ G_CALLBACK (page_entry_insert_text),
+ NULL);
+ g_signal_connect (priv->page_entry,
+ "focus-out-event",
+ G_CALLBACK (page_entry_focus_out),
+ preview);
+
+ gtk_box_pack_start (GTK_BOX (status),
+ priv->page_entry,
+ FALSE, FALSE, 0);
+ /* gtk_label_set_mnemonic_widget ((GtkLabel *) l, mp->priv->page_entry); */
+
+ /* We are displaying 'XXX of XXX'. */
+ gtk_box_pack_start (GTK_BOX (status),
+ /* Translators: the "of" from "1 of 19" in print preview. */
+ gtk_label_new (_("of")),
+ FALSE, FALSE, 0);
+
+ priv->last = gtk_label_new ("");
+ gtk_box_pack_start (GTK_BOX (status),
+ priv->last,
+ FALSE, FALSE, 0);
+ atko = gtk_widget_get_accessible (priv->last);
+ atk_object_set_name (atko, _("Page total"));
+ atk_object_set_description (atko, _("The total number of pages in the document"));
+
+ gtk_widget_show_all (status);
+
+ i = gtk_tool_item_new ();
+ gtk_container_add (GTK_CONTAINER (i), status);
+ gtk_widget_show (GTK_WIDGET (i));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
+
+ i = gtk_separator_tool_item_new ();
+ gtk_widget_show (GTK_WIDGET (i));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
+
+ priv->multi = gtk_tool_button_new_from_stock (GTK_STOCK_DND_MULTIPLE);
+ gtk_tool_button_set_label (GTK_TOOL_BUTTON (priv->multi),
+ "_Show Multiple Pages");
+ gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (priv->multi), TRUE);
+ gtk_tool_item_set_tooltip_text (priv->multi, _("Show multiple pages"));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), priv->multi, -1);
+ g_signal_connect (priv->multi,
+ "clicked",
+ G_CALLBACK (multi_button_clicked),
+ preview);
+ gtk_widget_show (GTK_WIDGET (priv->multi));
+
+ i = gtk_separator_tool_item_new ();
+ gtk_widget_show (GTK_WIDGET (i));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
+
+ priv->zoom_one = gtk_tool_button_new_from_stock (GTK_STOCK_ZOOM_100);
+ gtk_tool_item_set_tooltip_text (priv->zoom_one, _("Zoom 1:1"));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), priv->zoom_one, -1);
+ g_signal_connect (priv->zoom_one,
+ "clicked",
+ G_CALLBACK (zoom_one_button_clicked),
+ preview);
+ gtk_widget_show (GTK_WIDGET (priv->zoom_one));
+
+ priv->zoom_fit = gtk_tool_button_new_from_stock (GTK_STOCK_ZOOM_FIT);
+ gtk_tool_item_set_tooltip_text (priv->zoom_fit, _("Zoom to fit the whole page"));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), priv->zoom_fit, -1);
+ g_signal_connect (priv->zoom_fit,
+ "clicked",
+ G_CALLBACK (zoom_fit_button_clicked),
+ preview);
+ gtk_widget_show (GTK_WIDGET (priv->zoom_fit));
+
+ priv->zoom_in = gtk_tool_button_new_from_stock (GTK_STOCK_ZOOM_IN);
+ gtk_tool_item_set_tooltip_text (priv->zoom_in, _("Zoom the page in"));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), priv->zoom_in, -1);
+ g_signal_connect (priv->zoom_in,
+ "clicked",
+ G_CALLBACK (zoom_in_button_clicked),
+ preview);
+ gtk_widget_show (GTK_WIDGET (priv->zoom_in));
+
+ priv->zoom_out = gtk_tool_button_new_from_stock (GTK_STOCK_ZOOM_OUT);
+ gtk_tool_item_set_tooltip_text (priv->zoom_out, _("Zoom the page out"));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), priv->zoom_out, -1);
+ g_signal_connect (priv->zoom_out,
+ "clicked",
+ G_CALLBACK (zoom_out_button_clicked),
+ preview);
+ gtk_widget_show (GTK_WIDGET (priv->zoom_out));
+
+ i = gtk_separator_tool_item_new ();
+ gtk_widget_show (GTK_WIDGET (i));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
+
+ i = gtk_tool_button_new (NULL, _("_Close Preview"));
+ gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (i), TRUE);
+ gtk_tool_item_set_is_important (i, TRUE);
+ gtk_tool_item_set_tooltip_text (i, _("Close print preview"));
+ g_signal_connect (i, "clicked",
+ G_CALLBACK (close_button_clicked), preview);
+ gtk_widget_show (GTK_WIDGET (i));
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
+}
+
+static gint
+get_first_page_displayed (PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+
+ priv = preview->priv;
+
+ return priv->cur_page - priv->cur_page % (priv->cols * priv->rows);
+}
+
+/* returns the page number (starting from 0) or -1 if no page */
+static gint
+get_page_at_coords (PlumaPrintPreview *preview,
+ gint x,
+ gint y)
+{
+ PlumaPrintPreviewPrivate *priv;
+ GtkAdjustment *hadj, *vadj;
+ gint r, c, pg;
+
+ priv = preview->priv;
+
+ if (priv->tile_h <= 0 || priv->tile_h <= 0)
+ return -1;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (priv->layout));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (priv->layout));
+
+ x += gtk_adjustment_get_value (hadj);
+ y += gtk_adjustment_get_value (vadj);
+
+ r = 1 + y / (priv->tile_h);
+ c = 1 + x / (priv->tile_w);
+
+ if (c > priv->cols)
+ return -1;
+
+ pg = get_first_page_displayed (preview) - 1;
+ pg += (r - 1) * priv->cols + c;
+
+ if (pg >= priv->n_pages)
+ return -1;
+
+ /* FIXME: we could try to be picky and check
+ * if we actually are inside the page */
+ return pg;
+}
+
+static gboolean
+preview_layout_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip,
+ PlumaPrintPreview *preview)
+{
+ gint pg;
+ gchar *tip;
+
+ pg = get_page_at_coords (preview, x, y);
+ if (pg < 0)
+ return FALSE;
+
+ tip = g_strdup_printf (_("Page %d of %d"), pg + 1, preview->priv->n_pages);
+ gtk_tooltip_set_text (tooltip, tip);
+ g_free (tip);
+
+ return TRUE;
+}
+
+static gint
+preview_layout_key_press (GtkWidget *widget,
+ GdkEventKey *event,
+ PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+ GtkAdjustment *hadj, *vadj;
+ double x, y;
+ guint h, w;
+ double hlower, hupper, vlower, vupper;
+ double hpage, vpage;
+ double hstep, vstep;
+ gboolean domove = FALSE;
+ gboolean ret = TRUE;
+
+ priv = preview->priv;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (priv->layout));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (priv->layout));
+
+ x = gtk_adjustment_get_value (hadj);
+ y = gtk_adjustment_get_value (vadj);
+
+ g_object_get (hadj,
+ "lower", &hlower,
+ "upper", &hupper,
+ "page-size", &hpage,
+ NULL);
+ g_object_get (vadj,
+ "lower", &vlower,
+ "upper", &vupper,
+ "page-size", &vpage,
+ NULL);
+
+ gtk_layout_get_size (GTK_LAYOUT (priv->layout), &w, &h);
+
+ hstep = 10;
+ vstep = 10;
+
+ switch (event->keyval) {
+ case '1':
+ set_zoom_fit_to_size (preview);
+ break;
+ case '+':
+ case '=':
+ case GDK_KP_Add:
+ zoom_in (preview);
+ break;
+ case '-':
+ case '_':
+ case GDK_KP_Subtract:
+ zoom_out (preview);
+ break;
+ case GDK_KP_Right:
+ case GDK_Right:
+ if (event->state & GDK_SHIFT_MASK)
+ x = hupper - hpage;
+ else
+ x = MIN (hupper - hpage, x + hstep);
+ domove = TRUE;
+ break;
+ case GDK_KP_Left:
+ case GDK_Left:
+ if (event->state & GDK_SHIFT_MASK)
+ x = hlower;
+ else
+ x = MAX (hlower, x - hstep);
+ domove = TRUE;
+ break;
+ case GDK_KP_Up:
+ case GDK_Up:
+ if (event->state & GDK_SHIFT_MASK)
+ goto page_up;
+ y = MAX (vlower, y - vstep);
+ domove = TRUE;
+ break;
+ case GDK_KP_Down:
+ case GDK_Down:
+ if (event->state & GDK_SHIFT_MASK)
+ goto page_down;
+ y = MIN (vupper - vpage, y + vstep);
+ domove = TRUE;
+ break;
+ case GDK_KP_Page_Up:
+ case GDK_Page_Up:
+ case GDK_Delete:
+ case GDK_KP_Delete:
+ case GDK_BackSpace:
+ page_up:
+ if (y <= vlower)
+ {
+ if (preview->priv->cur_page > 0)
+ {
+ goto_page (preview, preview->priv->cur_page - 1);
+ y = (vupper - vpage);
+ }
+ }
+ else
+ {
+ y = vlower;
+ }
+ domove = TRUE;
+ break;
+ case GDK_KP_Page_Down:
+ case GDK_Page_Down:
+ case ' ':
+ page_down:
+ if (y >= (vupper - vpage))
+ {
+ if (preview->priv->cur_page < preview->priv->n_pages - 1)
+ {
+ goto_page (preview, preview->priv->cur_page + 1);
+ y = vlower;
+ }
+ }
+ else
+ {
+ y = (vupper - vpage);
+ }
+ domove = TRUE;
+ break;
+ case GDK_KP_Home:
+ case GDK_Home:
+ goto_page (preview, 0);
+ y = 0;
+ domove = TRUE;
+ break;
+ case GDK_KP_End:
+ case GDK_End:
+ goto_page (preview, preview->priv->n_pages - 1);
+ y = 0;
+ domove = TRUE;
+ break;
+ case GDK_Escape:
+ gtk_widget_destroy (GTK_WIDGET (preview));
+ break;
+ case 'c':
+ if (event->state & GDK_MOD1_MASK)
+ {
+ gtk_widget_destroy (GTK_WIDGET (preview));
+ }
+ break;
+ case 'p':
+ if (event->state & GDK_MOD1_MASK)
+ {
+ gtk_widget_grab_focus (preview->priv->page_entry);
+ }
+ break;
+ default:
+ /* by default do not stop the default handler */
+ ret = FALSE;
+ }
+
+ if (domove)
+ {
+ gtk_adjustment_set_value (hadj, x);
+ gtk_adjustment_set_value (vadj, y);
+
+ gtk_adjustment_value_changed (hadj);
+ gtk_adjustment_value_changed (vadj);
+ }
+
+ return ret;
+}
+
+static void
+create_preview_layout (PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+ AtkObject *atko;
+
+ priv = preview->priv;
+
+ priv->layout = gtk_layout_new (NULL, NULL);
+// gtk_widget_set_double_buffered (priv->layout, FALSE);
+
+ atko = gtk_widget_get_accessible (GTK_WIDGET (priv->layout));
+ atk_object_set_name (atko, _("Page Preview"));
+ atk_object_set_description (atko, _("The preview of a page in the document to be printed"));
+
+ gtk_widget_add_events (priv->layout,
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_KEY_PRESS_MASK);
+
+ GTK_WIDGET_SET_FLAGS (priv->layout, GTK_CAN_FOCUS);
+
+ g_signal_connect (priv->layout,
+ "key-press-event",
+ G_CALLBACK (preview_layout_key_press),
+ preview);
+
+ g_object_set (priv->layout, "has-tooltip", TRUE, NULL);
+ g_signal_connect (priv->layout,
+ "query-tooltip",
+ G_CALLBACK (preview_layout_query_tooltip),
+ preview);
+
+ priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->layout);
+ gtk_box_pack_end (GTK_BOX (preview),
+ priv->scrolled_window,
+ TRUE, TRUE, 0);
+
+ gtk_widget_show_all (GTK_WIDGET (priv->scrolled_window));
+ gtk_widget_grab_focus (GTK_WIDGET (priv->layout));
+}
+
+static void
+pluma_print_preview_init (PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+
+ priv = G_TYPE_INSTANCE_GET_PRIVATE (preview,
+ PLUMA_TYPE_PRINT_PREVIEW,
+ PlumaPrintPreviewPrivate);
+
+ preview->priv = priv;
+
+ priv->operation = NULL;
+ priv->context = NULL;
+ priv->gtk_preview = NULL;
+
+ create_bar (preview);
+ create_preview_layout (preview);
+
+ // FIXME
+ priv->cur_page = 0;
+ priv->paper_w = 0;
+ priv->paper_h = 0;
+ priv->dpi = PRINTER_DPI;
+ priv->scale = 1.0;
+ priv->rows = 1;
+ priv->cols = 1;
+}
+
+static void
+draw_page_content (cairo_t *cr,
+ gint page_number,
+ PlumaPrintPreview *preview)
+{
+ /* scale to the desired size */
+ cairo_scale (cr, preview->priv->scale, preview->priv->scale);
+
+ /* rotate acording to page orientation if needed */
+ if ((preview->priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
+ (preview->priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
+ {
+ cairo_matrix_t matrix;
+
+ cairo_matrix_init (&matrix,
+ 0, -1,
+ 1, 0,
+ 0, get_paper_width (preview));
+ cairo_transform (cr, &matrix);
+ }
+
+ gtk_print_context_set_cairo_context (preview->priv->context,
+ cr,
+ preview->priv->dpi,
+ preview->priv->dpi);
+
+ gtk_print_operation_preview_render_page (preview->priv->gtk_preview,
+ page_number);
+}
+
+/* For the frame, we scale and rotate manually, since
+ * the line width should not depend on the zoom and
+ * the drop shadow should be on the bottom right no matter
+ * the orientation */
+static void
+draw_page_frame (cairo_t *cr,
+ PlumaPrintPreview *preview)
+{
+ double w, h;
+
+ w = get_paper_width (preview);
+ h = get_paper_height (preview);
+
+ if ((preview->priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
+ (preview->priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
+ {
+ double tmp;
+
+ tmp = w;
+ w = h;
+ h = tmp;
+ }
+
+ w *= preview->priv->scale;
+ h *= preview->priv->scale;
+
+ /* drop shadow */
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_rectangle (cr,
+ PAGE_SHADOW_OFFSET, PAGE_SHADOW_OFFSET,
+ w, h);
+ cairo_fill (cr);
+
+ /* page frame */
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_rectangle (cr,
+ 0, 0,
+ w, h);
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke (cr);
+}
+
+static void
+draw_page (cairo_t *cr,
+ double x,
+ double y,
+ gint page_number,
+ PlumaPrintPreview *preview)
+{
+ cairo_save (cr);
+
+ /* move to the page top left corner */
+ cairo_translate (cr, x + PAGE_PAD, y + PAGE_PAD);
+
+ draw_page_frame (cr, preview);
+ draw_page_content (cr, page_number, preview);
+
+ cairo_restore (cr);
+}
+
+static gboolean
+preview_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ PlumaPrintPreview *preview)
+{
+ PlumaPrintPreviewPrivate *priv;
+ GdkWindow *bin_window;
+ cairo_t *cr;
+ gint pg;
+ gint i, j;
+
+ priv = preview->priv;
+
+ bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (priv->layout));
+ if (event->window != bin_window)
+ return FALSE;
+
+ cr = gdk_cairo_create (bin_window);
+
+ gdk_cairo_rectangle (cr, &event->area);
+ cairo_clip (cr);
+
+ /* get the first page to display */
+ pg = get_first_page_displayed (preview);
+
+ for (i = 0; i < priv->cols; ++i)
+ {
+ for (j = 0; j < priv->rows; ++j)
+ {
+ if (!gtk_print_operation_preview_is_selected (priv->gtk_preview,
+ pg))
+ {
+ continue;
+ }
+
+ if (pg == priv->n_pages)
+ break;
+
+ draw_page (cr,
+ j * priv->tile_w,
+ i * priv->tile_h,
+ pg,
+ preview);
+
+ ++pg;
+ }
+ }
+ cairo_destroy (cr);
+
+ return TRUE;
+}
+
+static double
+get_screen_dpi (PlumaPrintPreview *preview)
+{
+ GdkScreen *screen;
+ double dpi;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (preview));
+
+ dpi = gdk_screen_get_resolution (screen);
+ if (dpi < 30. || 600. < dpi)
+ {
+ g_warning ("Invalid the x-resolution for the screen, assuming 96dpi");
+ dpi = 96.;
+ }
+
+ return dpi;
+}
+
+static void
+set_n_pages (PlumaPrintPreview *preview,
+ gint n_pages)
+{
+ gchar *str;
+
+ preview->priv->n_pages = n_pages;
+
+ // FIXME: count the visible pages
+
+ str = g_strdup_printf ("%d", n_pages);
+ gtk_label_set_markup (GTK_LABEL (preview->priv->last), str);
+ g_free (str);
+}
+
+static void
+preview_ready (GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context,
+ PlumaPrintPreview *preview)
+{
+ gint n_pages;
+
+ g_object_get (preview->priv->operation, "n-pages", &n_pages, NULL);
+ set_n_pages (preview, n_pages);
+ goto_page (preview, 0);
+
+ /* figure out the dpi */
+ preview->priv->dpi = get_screen_dpi (preview);
+
+ set_zoom_factor (preview, 1.0);
+
+ /* let the default gtklayout handler clear the background */
+ g_signal_connect_after (preview->priv->layout,
+ "expose-event",
+ G_CALLBACK (preview_expose),
+ preview);
+
+ gtk_widget_queue_draw (preview->priv->layout);
+}
+
+static void
+update_paper_size (PlumaPrintPreview *preview,
+ GtkPageSetup *page_setup)
+{
+ GtkPaperSize *paper_size;
+
+ paper_size = gtk_page_setup_get_paper_size (page_setup);
+
+ preview->priv->paper_w = gtk_paper_size_get_width (paper_size, GTK_UNIT_INCH);
+ preview->priv->paper_h = gtk_paper_size_get_height (paper_size, GTK_UNIT_INCH);
+
+ preview->priv->orientation = gtk_page_setup_get_orientation (page_setup);
+}
+
+static void
+preview_got_page_size (GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context,
+ GtkPageSetup *page_setup,
+ PlumaPrintPreview *preview)
+{
+ update_paper_size (preview, page_setup);
+}
+
+/* HACK: we need a dummy surface to paginate... can we use something simpler? */
+
+static cairo_status_t
+dummy_write_func (G_GNUC_UNUSED gpointer closure,
+ G_GNUC_UNUSED const guchar *data,
+ G_GNUC_UNUSED guint length)
+{
+ return CAIRO_STATUS_SUCCESS;
+}
+
+#define PRINTER_DPI (72.)
+
+static cairo_surface_t *
+create_preview_surface_platform (GtkPaperSize *paper_size,
+ double *dpi_x,
+ double *dpi_y)
+{
+ double width, height;
+ cairo_surface_t *sf;
+
+ width = gtk_paper_size_get_width (paper_size, GTK_UNIT_POINTS);
+ height = gtk_paper_size_get_height (paper_size, GTK_UNIT_POINTS);
+
+ *dpi_x = *dpi_y = PRINTER_DPI;
+
+ sf = cairo_pdf_surface_create_for_stream (dummy_write_func, NULL,
+ width, height);
+ return sf;
+}
+
+static cairo_surface_t *
+create_preview_surface (PlumaPrintPreview *preview,
+ double *dpi_x,
+ double *dpi_y)
+{
+ GtkPageSetup *page_setup;
+ GtkPaperSize *paper_size;
+
+ page_setup = gtk_print_context_get_page_setup (preview->priv->context);
+ /* gtk_page_setup_get_paper_size swaps width and height for landscape */
+ paper_size = gtk_page_setup_get_paper_size (page_setup);
+
+ return create_preview_surface_platform (paper_size, dpi_x, dpi_y);
+}
+
+GtkWidget *
+pluma_print_preview_new (GtkPrintOperation *op,
+ GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context)
+{
+ PlumaPrintPreview *preview;
+ GtkPageSetup *page_setup;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ double dpi_x, dpi_y;
+
+ g_return_val_if_fail (GTK_IS_PRINT_OPERATION (op), NULL);
+ g_return_val_if_fail (GTK_IS_PRINT_OPERATION_PREVIEW (gtk_preview), NULL);
+
+ preview = g_object_new (PLUMA_TYPE_PRINT_PREVIEW, NULL);
+
+ preview->priv->operation = g_object_ref (op);
+ preview->priv->gtk_preview = g_object_ref (gtk_preview);
+ preview->priv->context = g_object_ref (context);
+
+ /* FIXME: is this legal?? */
+ gtk_print_operation_set_unit (op, GTK_UNIT_POINTS);
+
+ g_signal_connect (gtk_preview, "ready",
+ G_CALLBACK (preview_ready), preview);
+ g_signal_connect (gtk_preview, "got-page-size",
+ G_CALLBACK (preview_got_page_size), preview);
+
+ page_setup = gtk_print_context_get_page_setup (preview->priv->context);
+ update_paper_size (preview, page_setup);
+
+ /* FIXME: we need a cr to paginate... but we can't get the drawing
+ * area surface because it's not there yet... for now I create
+ * a dummy pdf surface */
+
+ surface = create_preview_surface (preview, &dpi_x, &dpi_y);
+ cr = cairo_create (surface);
+ gtk_print_context_set_cairo_context (context, cr, dpi_x, dpi_y);
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+
+ return GTK_WIDGET (preview);
+}
+
diff --git a/pluma/pluma-print-preview.h b/pluma/pluma-print-preview.h
new file mode 100755
index 00000000..80f52864
--- /dev/null
+++ b/pluma/pluma-print-preview.h
@@ -0,0 +1,71 @@
+/*
+ * pluma-print-preview.h
+ *
+ * Copyright (C) 2008 Paolo Borelli
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2006. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: pluma-commands-search.c 5931 2007-09-25 20:05:40Z pborelli $
+ */
+
+
+#ifndef __PLUMA_PRINT_PREVIEW_H__
+#define __PLUMA_PRINT_PREVIEW_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_PRINT_PREVIEW (pluma_print_preview_get_type ())
+#define PLUMA_PRINT_PREVIEW(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), PLUMA_TYPE_PRINT_PREVIEW, PlumaPrintPreview))
+#define PLUMA_PRINT_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_PRINT_PREVIEW, PlumaPrintPreviewClass))
+#define PLUMA_IS_PRINT_PREVIEW(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), PLUMA_TYPE_PRINT_PREVIEW))
+#define PLUMA_IS_PRINT_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PRINT_PREVIEW))
+#define PLUMA_PRINT_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_PRINT_PREVIEW, PlumaPrintPreviewClass))
+
+typedef struct _PlumaPrintPreview PlumaPrintPreview;
+typedef struct _PlumaPrintPreviewPrivate PlumaPrintPreviewPrivate;
+typedef struct _PlumaPrintPreviewClass PlumaPrintPreviewClass;
+
+struct _PlumaPrintPreview
+{
+ GtkVBox parent;
+ PlumaPrintPreviewPrivate *priv;
+};
+
+struct _PlumaPrintPreviewClass
+{
+ GtkVBoxClass parent_class;
+
+ void (* close) (PlumaPrintPreview *preview);
+};
+
+
+GType pluma_print_preview_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_print_preview_new (GtkPrintOperation *op,
+ GtkPrintOperationPreview *gtk_preview,
+ GtkPrintContext *context);
+
+G_END_DECLS
+
+#endif /* __PLUMA_PRINT_PREVIEW_H__ */
diff --git a/pluma/pluma-progress-message-area.c b/pluma/pluma-progress-message-area.c
new file mode 100755
index 00000000..5674c75b
--- /dev/null
+++ b/pluma/pluma-progress-message-area.c
@@ -0,0 +1,261 @@
+/*
+ * pluma-progress-message-area.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+ /* TODO: add properties */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include "pluma-progress-message-area.h"
+
+enum {
+ PROP_0,
+ PROP_HAS_CANCEL_BUTTON
+};
+
+
+#define PLUMA_PROGRESS_MESSAGE_AREA_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_PROGRESS_MESSAGE_AREA, PlumaProgressMessageAreaPrivate))
+
+struct _PlumaProgressMessageAreaPrivate
+{
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *progress;
+};
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+G_DEFINE_TYPE(PlumaProgressMessageArea, pluma_progress_message_area, PLUMA_TYPE_MESSAGE_AREA)
+#else
+G_DEFINE_TYPE(PlumaProgressMessageArea, pluma_progress_message_area, GTK_TYPE_INFO_BAR)
+#endif
+
+static void
+pluma_progress_message_area_set_has_cancel_button (PlumaProgressMessageArea *area,
+ gboolean has_button)
+{
+ if (has_button)
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_add_button (PLUMA_MESSAGE_AREA (area),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+#else
+ gtk_info_bar_add_button (GTK_INFO_BAR (area),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+#endif
+
+ g_object_notify (G_OBJECT (area), "has-cancel-button");
+}
+
+static void
+pluma_progress_message_area_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaProgressMessageArea *area;
+
+ area = PLUMA_PROGRESS_MESSAGE_AREA (object);
+
+ switch (prop_id)
+ {
+ case PROP_HAS_CANCEL_BUTTON:
+ pluma_progress_message_area_set_has_cancel_button (area,
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_progress_message_area_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaProgressMessageArea *area;
+
+ area = PLUMA_PROGRESS_MESSAGE_AREA (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_progress_message_area_class_init (PlumaProgressMessageAreaClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = pluma_progress_message_area_set_property;
+ gobject_class->get_property = pluma_progress_message_area_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_HAS_CANCEL_BUTTON,
+ g_param_spec_boolean ("has-cancel-button",
+ "Has Cancel Button",
+ "If the message area has a cancel button",
+ TRUE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (gobject_class, sizeof(PlumaProgressMessageAreaPrivate));
+}
+
+static void
+pluma_progress_message_area_init (PlumaProgressMessageArea *area)
+{
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+
+ area->priv = PLUMA_PROGRESS_MESSAGE_AREA_GET_PRIVATE (area);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+ area->priv->image = gtk_image_new_from_icon_name (GTK_STOCK_MISSING_IMAGE,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_widget_show (area->priv->image);
+ gtk_misc_set_alignment (GTK_MISC (area->priv->image), 0.5, 0.5);
+ gtk_box_pack_start (GTK_BOX (hbox), area->priv->image, FALSE, FALSE, 4);
+
+ area->priv->label = gtk_label_new ("");
+ gtk_widget_show (area->priv->label);
+ gtk_box_pack_start (GTK_BOX (hbox), area->priv->label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (area->priv->label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (area->priv->label), 0, 0.5);
+ gtk_label_set_ellipsize (GTK_LABEL (area->priv->label),
+ PANGO_ELLIPSIZE_END);
+
+ area->priv->progress = gtk_progress_bar_new ();
+ gtk_widget_show (area->priv->progress);
+ gtk_box_pack_start (GTK_BOX (vbox), area->priv->progress, TRUE, FALSE, 0);
+ gtk_widget_set_size_request (area->priv->progress, -1, 15);
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_set_contents (PLUMA_MESSAGE_AREA (area),
+ vbox);
+#else
+ GtkWidget *content;
+
+ content = gtk_info_bar_get_content_area (GTK_INFO_BAR (area));
+ gtk_container_add (GTK_CONTAINER (content), vbox);
+#endif
+}
+
+GtkWidget *
+pluma_progress_message_area_new (const gchar *stock_id,
+ const gchar *markup,
+ gboolean has_cancel)
+{
+ PlumaProgressMessageArea *area;
+
+ g_return_val_if_fail (stock_id != NULL, NULL);
+ g_return_val_if_fail (markup != NULL, NULL);
+
+ area = PLUMA_PROGRESS_MESSAGE_AREA (g_object_new (PLUMA_TYPE_PROGRESS_MESSAGE_AREA,
+ "has-cancel-button", has_cancel,
+ NULL));
+
+ pluma_progress_message_area_set_stock_image (area,
+ stock_id);
+
+ pluma_progress_message_area_set_markup (area,
+ markup);
+
+ return GTK_WIDGET (area);
+}
+
+void
+pluma_progress_message_area_set_stock_image (PlumaProgressMessageArea *area,
+ const gchar *stock_id)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (area));
+ g_return_if_fail (stock_id != NULL);
+
+ gtk_image_set_from_stock (GTK_IMAGE (area->priv->image),
+ stock_id,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+}
+
+void
+pluma_progress_message_area_set_markup (PlumaProgressMessageArea *area,
+ const gchar *markup)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (area));
+ g_return_if_fail (markup != NULL);
+
+ gtk_label_set_markup (GTK_LABEL (area->priv->label),
+ markup);
+}
+
+void
+pluma_progress_message_area_set_text (PlumaProgressMessageArea *area,
+ const gchar *text)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (area));
+ g_return_if_fail (text != NULL);
+
+ gtk_label_set_text (GTK_LABEL (area->priv->label),
+ text);
+}
+
+void
+pluma_progress_message_area_set_fraction (PlumaProgressMessageArea *area,
+ gdouble fraction)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (area));
+
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (area->priv->progress),
+ fraction);
+}
+
+void
+pluma_progress_message_area_pulse (PlumaProgressMessageArea *area)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (area));
+
+ gtk_progress_bar_pulse (GTK_PROGRESS_BAR (area->priv->progress));
+}
diff --git a/pluma/pluma-progress-message-area.h b/pluma/pluma-progress-message-area.h
new file mode 100755
index 00000000..3e8f651a
--- /dev/null
+++ b/pluma/pluma-progress-message-area.h
@@ -0,0 +1,112 @@
+/*
+ * pluma-progress-message-area.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_PROGRESS_MESSAGE_AREA_H__
+#define __PLUMA_PROGRESS_MESSAGE_AREA_H__
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+#include <pluma/pluma-message-area.h>
+#else
+#include <gtk/gtk.h>
+#endif
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_PROGRESS_MESSAGE_AREA (pluma_progress_message_area_get_type())
+#define PLUMA_PROGRESS_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_PROGRESS_MESSAGE_AREA, PlumaProgressMessageArea))
+#define PLUMA_PROGRESS_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_PROGRESS_MESSAGE_AREA, PlumaProgressMessageAreaClass))
+#define PLUMA_IS_PROGRESS_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_PROGRESS_MESSAGE_AREA))
+#define PLUMA_IS_PROGRESS_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_PROGRESS_MESSAGE_AREA))
+#define PLUMA_PROGRESS_MESSAGE_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_PROGRESS_MESSAGE_AREA, PlumaProgressMessageAreaClass))
+
+/* Private structure type */
+typedef struct _PlumaProgressMessageAreaPrivate PlumaProgressMessageAreaPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaProgressMessageArea PlumaProgressMessageArea;
+
+struct _PlumaProgressMessageArea
+{
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ PlumaMessageArea parent;
+#else
+ GtkInfoBar parent;
+#endif
+
+ /*< private > */
+ PlumaProgressMessageAreaPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaProgressMessageAreaClass PlumaProgressMessageAreaClass;
+
+struct _PlumaProgressMessageAreaClass
+{
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ PlumaMessageAreaClass parent_class;
+#else
+ GtkInfoBarClass parent_class;
+#endif
+};
+
+/*
+ * Public methods
+ */
+GType pluma_progress_message_area_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_progress_message_area_new (const gchar *stock_id,
+ const gchar *markup,
+ gboolean has_cancel);
+
+void pluma_progress_message_area_set_stock_image (PlumaProgressMessageArea *area,
+ const gchar *stock_id);
+
+void pluma_progress_message_area_set_markup (PlumaProgressMessageArea *area,
+ const gchar *markup);
+
+void pluma_progress_message_area_set_text (PlumaProgressMessageArea *area,
+ const gchar *text);
+
+void pluma_progress_message_area_set_fraction (PlumaProgressMessageArea *area,
+ gdouble fraction);
+
+void pluma_progress_message_area_pulse (PlumaProgressMessageArea *area);
+
+
+G_END_DECLS
+
+#endif /* __PLUMA_PROGRESS_MESSAGE_AREA_H__ */
diff --git a/pluma/pluma-session.c b/pluma/pluma-session.c
new file mode 100755
index 00000000..2a8b40e9
--- /dev/null
+++ b/pluma/pluma-session.c
@@ -0,0 +1,601 @@
+/*
+ * pluma-session.c - Basic session management for pluma
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002 Ximian, Inc.
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * Author: Federico Mena-Quintero <[email protected]>
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+
+#include <libxml/tree.h>
+#include <libxml/xmlwriter.h>
+
+#include "pluma-session.h"
+
+#include "pluma-debug.h"
+#include "pluma-plugins-engine.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-metadata-manager.h"
+#include "pluma-window.h"
+#include "pluma-app.h"
+#include "pluma-commands.h"
+#include "dialogs/pluma-close-confirmation-dialog.h"
+#include "smclient/eggsmclient.h"
+
+/* The master client we use for SM */
+static EggSMClient *master_client = NULL;
+
+/* global var used during quit_requested */
+static GSList *window_dirty_list;
+
+static void ask_next_confirmation (void);
+
+#define PLUMA_SESSION_LIST_OF_DOCS_TO_SAVE "pluma-session-list-of-docs-to-save-key"
+
+static void
+save_window_session (GKeyFile *state_file,
+ const gchar *group_name,
+ PlumaWindow *window)
+{
+ const gchar *role;
+ int width, height;
+ PlumaPanel *panel;
+ GList *docs, *l;
+ GPtrArray *doc_array;
+ PlumaDocument *active_document;
+ gchar *uri;
+
+ pluma_debug (DEBUG_SESSION);
+
+ role = gtk_window_get_role (GTK_WINDOW (window));
+ g_key_file_set_string (state_file, group_name, "role", role);
+ gtk_window_get_size (GTK_WINDOW (window), &width, &height);
+ g_key_file_set_integer (state_file, group_name, "width", width);
+ g_key_file_set_integer (state_file, group_name, "height", height);
+
+ panel = pluma_window_get_side_panel (window);
+ g_key_file_set_boolean (state_file, group_name, "side-panel-visible",
+ GTK_WIDGET_VISIBLE (panel));
+
+ panel = pluma_window_get_bottom_panel (window);
+ g_key_file_set_boolean (state_file, group_name, "bottom-panel-visible",
+ GTK_WIDGET_VISIBLE (panel));
+
+ active_document = pluma_window_get_active_document (window);
+ if (active_document)
+ {
+ uri = pluma_document_get_uri (active_document);
+ g_key_file_set_string (state_file, group_name,
+ "active-document", uri);
+ }
+
+ docs = pluma_window_get_documents (window);
+
+ doc_array = g_ptr_array_new ();
+ for (l = docs; l != NULL; l = g_list_next (l))
+ {
+ uri = pluma_document_get_uri (PLUMA_DOCUMENT (l->data));
+
+ if (uri != NULL)
+ g_ptr_array_add (doc_array, uri);
+
+ }
+ g_list_free (docs);
+
+ if (doc_array->len)
+ {
+ guint i;
+
+ g_key_file_set_string_list (state_file, group_name,
+ "documents",
+ (const char **)doc_array->pdata,
+ doc_array->len);
+ for (i = 0; i < doc_array->len; i++)
+ g_free (doc_array->pdata[i]);
+ }
+ g_ptr_array_free (doc_array, TRUE);
+}
+
+static void
+client_save_state_cb (EggSMClient *client,
+ GKeyFile *state_file,
+ gpointer user_data)
+{
+ const GList *windows;
+ gchar *group_name;
+ int n;
+
+ windows = pluma_app_get_windows (pluma_app_get_default ());
+ n = 1;
+
+ while (windows != NULL)
+ {
+ group_name = g_strdup_printf ("pluma window %d", n);
+ save_window_session (state_file,
+ group_name,
+ PLUMA_WINDOW (windows->data));
+ g_free (group_name);
+
+ windows = g_list_next (windows);
+ n++;
+ }
+}
+
+static void
+window_handled (PlumaWindow *window)
+{
+ window_dirty_list = g_slist_remove (window_dirty_list, window);
+
+ /* whee... we made it! */
+ if (window_dirty_list == NULL)
+ egg_sm_client_will_quit (master_client, TRUE);
+ else
+ ask_next_confirmation ();
+}
+
+static void
+window_state_change (PlumaWindow *window,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ PlumaWindowState state;
+ GList *unsaved_docs;
+ GList *docs_to_save;
+ GList *l;
+ gboolean done = TRUE;
+
+ state = pluma_window_get_state (window);
+
+ /* we are still saving */
+ if (state & PLUMA_WINDOW_STATE_SAVING)
+ return;
+
+ unsaved_docs = pluma_window_get_unsaved_documents (window);
+
+ docs_to_save = g_object_get_data (G_OBJECT (window),
+ PLUMA_SESSION_LIST_OF_DOCS_TO_SAVE);
+
+
+ for (l = docs_to_save; l != NULL; l = l->next)
+ {
+ if (g_list_find (unsaved_docs, l->data))
+ {
+ done = FALSE;
+ break;
+ }
+ }
+
+ if (done)
+ {
+ g_signal_handlers_disconnect_by_func (window, window_state_change, data);
+ g_list_free (docs_to_save);
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_SESSION_LIST_OF_DOCS_TO_SAVE,
+ NULL);
+
+ window_handled (window);
+ }
+
+ g_list_free (unsaved_docs);
+}
+
+static void
+close_confirmation_dialog_response_handler (PlumaCloseConfirmationDialog *dlg,
+ gint response_id,
+ PlumaWindow *window)
+{
+ GList *selected_documents;
+ GSList *l;
+
+ pluma_debug (DEBUG_COMMANDS);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_YES:
+ /* save selected docs */
+
+ g_signal_connect (window,
+ "notify::state",
+ G_CALLBACK (window_state_change),
+ NULL);
+
+ selected_documents = pluma_close_confirmation_dialog_get_selected_documents (dlg);
+
+ g_return_if_fail (g_object_get_data (G_OBJECT (window),
+ PLUMA_SESSION_LIST_OF_DOCS_TO_SAVE) == NULL);
+
+ g_object_set_data (G_OBJECT (window),
+ PLUMA_SESSION_LIST_OF_DOCS_TO_SAVE,
+ selected_documents);
+
+ _pluma_cmd_file_save_documents_list (window, selected_documents);
+
+ /* FIXME: also need to lock the window to prevent further changes... */
+
+ break;
+
+ case GTK_RESPONSE_NO:
+ /* dont save */
+ window_handled (window);
+ break;
+
+ default:
+ /* disconnect window_state_changed where needed */
+ for (l = window_dirty_list; l != NULL; l = l->next)
+ g_signal_handlers_disconnect_by_func (window,
+ window_state_change, NULL);
+ g_slist_free (window_dirty_list);
+ window_dirty_list = NULL;
+
+ /* cancel shutdown */
+ egg_sm_client_will_quit (master_client, FALSE);
+
+ break;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+}
+
+static void
+show_confirmation_dialog (PlumaWindow *window)
+{
+ GList *unsaved_docs;
+ GtkWidget *dlg;
+
+ pluma_debug (DEBUG_SESSION);
+
+ unsaved_docs = pluma_window_get_unsaved_documents (window);
+
+ g_return_if_fail (unsaved_docs != NULL);
+
+ if (unsaved_docs->next == NULL)
+ {
+ /* There is only one unsaved document */
+ PlumaTab *tab;
+ PlumaDocument *doc;
+
+ doc = PLUMA_DOCUMENT (unsaved_docs->data);
+
+ tab = pluma_tab_get_from_document (doc);
+ g_return_if_fail (tab != NULL);
+
+ pluma_window_set_active_tab (window, tab);
+
+ dlg = pluma_close_confirmation_dialog_new_single (
+ GTK_WINDOW (window),
+ doc,
+ TRUE);
+ }
+ else
+ {
+ dlg = pluma_close_confirmation_dialog_new (GTK_WINDOW (window),
+ unsaved_docs,
+ TRUE);
+ }
+
+ g_list_free (unsaved_docs);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (close_confirmation_dialog_response_handler),
+ window);
+
+ gtk_widget_show (dlg);
+}
+
+static void
+ask_next_confirmation (void)
+{
+ g_return_if_fail (window_dirty_list != NULL);
+
+ /* pop up the confirmation dialog for the first window
+ * in the dirty list. The next confirmation is asked once
+ * this one has been handled.
+ */
+ show_confirmation_dialog (PLUMA_WINDOW (window_dirty_list->data));
+}
+
+/* quit_requested handler for the master client */
+static void
+client_quit_requested_cb (EggSMClient *client, gpointer data)
+{
+ PlumaApp *app;
+ const GList *l;
+
+ pluma_debug (DEBUG_SESSION);
+
+ app = pluma_app_get_default ();
+
+ if (window_dirty_list != NULL)
+ {
+ g_critical ("global variable window_dirty_list not NULL");
+ window_dirty_list = NULL;
+ }
+
+ for (l = pluma_app_get_windows (app); l != NULL; l = l->next)
+ {
+ if (pluma_window_get_unsaved_documents (PLUMA_WINDOW (l->data)) != NULL)
+ {
+ window_dirty_list = g_slist_prepend (window_dirty_list, l->data);
+ }
+ }
+
+ /* no modified docs */
+ if (window_dirty_list == NULL)
+ {
+ egg_sm_client_will_quit (client, TRUE);
+
+ return;
+ }
+
+ ask_next_confirmation ();
+
+ pluma_debug_message (DEBUG_SESSION, "END");
+}
+
+/* quit handler for the master client */
+static void
+client_quit_cb (EggSMClient *client, gpointer data)
+{
+#if 0
+ pluma_debug (DEBUG_SESSION);
+
+ if (!client->save_yourself_emitted)
+ pluma_file_close_all ();
+
+ pluma_debug_message (DEBUG_FILE, "All files closed.");
+
+ matecomponent_mdi_destroy (MATECOMPONENT_MDI (pluma_mdi));
+
+ pluma_debug_message (DEBUG_FILE, "Unref pluma_mdi.");
+
+ g_object_unref (G_OBJECT (pluma_mdi));
+
+ pluma_debug_message (DEBUG_FILE, "Unref pluma_mdi: DONE");
+
+ pluma_debug_message (DEBUG_FILE, "Unref pluma_app_server.");
+
+ matecomponent_object_unref (pluma_app_server);
+
+ pluma_debug_message (DEBUG_FILE, "Unref pluma_app_server: DONE");
+#endif
+
+ gtk_main_quit ();
+}
+
+/**
+ * pluma_session_init:
+ *
+ * Initializes session management support. This function should be called near
+ * the beginning of the program.
+ **/
+void
+pluma_session_init (void)
+{
+ pluma_debug (DEBUG_SESSION);
+
+ if (master_client)
+ return;
+
+ master_client = egg_sm_client_get ();
+ g_signal_connect (master_client,
+ "save_state",
+ G_CALLBACK (client_save_state_cb),
+ NULL);
+ g_signal_connect (master_client,
+ "quit_requested",
+ G_CALLBACK (client_quit_requested_cb),
+ NULL);
+ g_signal_connect (master_client,
+ "quit",
+ G_CALLBACK (client_quit_cb),
+ NULL);
+}
+
+/**
+ * pluma_session_is_restored:
+ *
+ * Returns whether this pluma is running from a restarted session.
+ *
+ * Return value: TRUE if the session manager restarted us, FALSE otherwise.
+ * This should be used to determine whether to pay attention to command line
+ * arguments in case the session was not restored.
+ **/
+gboolean
+pluma_session_is_restored (void)
+{
+ gboolean restored;
+
+ pluma_debug (DEBUG_SESSION);
+
+ if (!master_client)
+ return FALSE;
+
+ restored = egg_sm_client_is_resumed (master_client);
+
+ pluma_debug_message (DEBUG_SESSION, restored ? "RESTORED" : "NOT RESTORED");
+
+ return restored;
+}
+
+static void
+parse_window (GKeyFile *state_file, const char *group_name)
+{
+ PlumaWindow *window;
+ gchar *role, *active_document, **documents;
+ int width, height;
+ gboolean visible;
+ PlumaPanel *panel;
+ GError *error = NULL;
+
+ role = g_key_file_get_string (state_file, group_name, "role", NULL);
+
+ pluma_debug_message (DEBUG_SESSION, "Window role: %s", role);
+
+ window = _pluma_app_restore_window (pluma_app_get_default (), (gchar *) role);
+ g_free (role);
+
+ if (window == NULL)
+ {
+ g_warning ("Couldn't restore window");
+ return;
+ }
+
+ width = g_key_file_get_integer (state_file, group_name,
+ "width", &error);
+ if (error)
+ {
+ g_clear_error (&error);
+ width = -1;
+ }
+ height = g_key_file_get_integer (state_file, group_name,
+ "height", &error);
+ if (error)
+ {
+ g_clear_error (&error);
+ height = -1;
+ }
+ gtk_window_set_default_size (GTK_WINDOW (window), width, height);
+
+
+ visible = g_key_file_get_boolean (state_file, group_name,
+ "side-panel-visible", &error);
+ if (error)
+ {
+ g_clear_error (&error);
+ visible = FALSE;
+ }
+
+ panel = pluma_window_get_side_panel (window);
+
+ if (visible)
+ {
+ pluma_debug_message (DEBUG_SESSION, "Side panel visible");
+ gtk_widget_show (GTK_WIDGET (panel));
+ }
+ else
+ {
+ pluma_debug_message (DEBUG_SESSION, "Side panel _NOT_ visible");
+ gtk_widget_hide (GTK_WIDGET (panel));
+ }
+
+ visible = g_key_file_get_boolean (state_file, group_name,
+ "bottom-panel-visible", &error);
+ if (error)
+ {
+ g_clear_error (&error);
+ visible = FALSE;
+ }
+
+ panel = pluma_window_get_bottom_panel (window);
+ if (visible)
+ {
+ pluma_debug_message (DEBUG_SESSION, "Bottom panel visible");
+ gtk_widget_show (GTK_WIDGET (panel));
+ }
+ else
+ {
+ pluma_debug_message (DEBUG_SESSION, "Bottom panel _NOT_ visible");
+ gtk_widget_hide (GTK_WIDGET (panel));
+ }
+
+ active_document = g_key_file_get_string (state_file, group_name,
+ "active-document", NULL);
+ documents = g_key_file_get_string_list (state_file, group_name,
+ "documents", NULL, NULL);
+ if (documents)
+ {
+ int i;
+ gboolean jump_to = FALSE;
+
+ for (i = 0; documents[i]; i++)
+ {
+ if (active_document != NULL)
+ jump_to = strcmp (active_document,
+ documents[i]) == 0;
+
+ pluma_debug_message (DEBUG_SESSION,
+ "URI: %s (%s)",
+ documents[i],
+ jump_to ? "active" : "not active");
+ pluma_window_create_tab_from_uri (window,
+ documents[i],
+ NULL,
+ 0,
+ FALSE,
+ jump_to);
+ }
+ g_strfreev (documents);
+ }
+
+ g_free (active_document);
+
+ gtk_widget_show (GTK_WIDGET (window));
+}
+
+/**
+ * pluma_session_load:
+ *
+ * Loads the session by fetching the necessary information from the session
+ * manager and opening files.
+ *
+ * Return value: TRUE if the session was loaded successfully, FALSE otherwise.
+ **/
+gboolean
+pluma_session_load (void)
+{
+ GKeyFile *state_file;
+ gchar **groups;
+ int i;
+
+ pluma_debug (DEBUG_SESSION);
+
+ state_file = egg_sm_client_get_state_file (master_client);
+ if (state_file == NULL)
+ return FALSE;
+
+ groups = g_key_file_get_groups (state_file, NULL);
+
+ for (i = 0; groups[i] != NULL; i++)
+ {
+ if (g_str_has_prefix (groups[i], "pluma window "))
+ parse_window (state_file, groups[i]);
+ }
+
+ g_strfreev (groups);
+ g_key_file_free (state_file);
+
+ return TRUE;
+}
diff --git a/pluma/pluma-session.h b/pluma/pluma-session.h
new file mode 100755
index 00000000..4e48f2d6
--- /dev/null
+++ b/pluma/pluma-session.h
@@ -0,0 +1,47 @@
+/*
+ * pluma-session.h - Basic session management for pluma
+ * This file is part of pluma
+ *
+ * Copyright (C) 2002 Ximian, Inc.
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * Author: Federico Mena-Quintero <[email protected]>
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id
+ */
+
+#ifndef __PLUMA_SESSION_H__
+#define __PLUMA_SESSION_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void pluma_session_init (void);
+gboolean pluma_session_is_restored (void);
+gboolean pluma_session_load (void);
+
+G_END_DECLS
+
+#endif /* __PLUMA_SESSION_H__ */
diff --git a/pluma/pluma-smart-charset-converter.c b/pluma/pluma-smart-charset-converter.c
new file mode 100755
index 00000000..5f3f8fe1
--- /dev/null
+++ b/pluma/pluma-smart-charset-converter.c
@@ -0,0 +1,422 @@
+/*
+ * pluma-smart-charset-converter.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro
+ *
+ * pluma 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.
+ *
+ * pluma 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 pluma; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "pluma-smart-charset-converter.h"
+#include "pluma-debug.h"
+#include "pluma-document.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#define PLUMA_SMART_CHARSET_CONVERTER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), PLUMA_TYPE_SMART_CHARSET_CONVERTER, PlumaSmartCharsetConverterPrivate))
+
+struct _PlumaSmartCharsetConverterPrivate
+{
+ GCharsetConverter *charset_conv;
+
+ GSList *encodings;
+ GSList *current_encoding;
+
+ guint is_utf8 : 1;
+ guint use_first : 1;
+};
+
+static void pluma_smart_charset_converter_iface_init (GConverterIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (PlumaSmartCharsetConverter, pluma_smart_charset_converter,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
+ pluma_smart_charset_converter_iface_init))
+
+static void
+pluma_smart_charset_converter_finalize (GObject *object)
+{
+ PlumaSmartCharsetConverter *smart = PLUMA_SMART_CHARSET_CONVERTER (object);
+
+ g_slist_free (smart->priv->encodings);
+
+ pluma_debug_message (DEBUG_UTILS, "finalizing smart charset converter");
+
+ G_OBJECT_CLASS (pluma_smart_charset_converter_parent_class)->finalize (object);
+}
+
+static void
+pluma_smart_charset_converter_dispose (GObject *object)
+{
+ PlumaSmartCharsetConverter *smart = PLUMA_SMART_CHARSET_CONVERTER (object);
+
+ if (smart->priv->charset_conv != NULL)
+ {
+ g_object_unref (smart->priv->charset_conv);
+ smart->priv->charset_conv = NULL;
+ }
+
+ pluma_debug_message (DEBUG_UTILS, "disposing smart charset converter");
+
+ G_OBJECT_CLASS (pluma_smart_charset_converter_parent_class)->dispose (object);
+}
+
+static void
+pluma_smart_charset_converter_class_init (PlumaSmartCharsetConverterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_smart_charset_converter_finalize;
+ object_class->dispose = pluma_smart_charset_converter_dispose;
+
+ g_type_class_add_private (object_class, sizeof (PlumaSmartCharsetConverterPrivate));
+}
+
+static void
+pluma_smart_charset_converter_init (PlumaSmartCharsetConverter *smart)
+{
+ smart->priv = PLUMA_SMART_CHARSET_CONVERTER_GET_PRIVATE (smart);
+
+ smart->priv->charset_conv = NULL;
+ smart->priv->encodings = NULL;
+ smart->priv->current_encoding = NULL;
+ smart->priv->is_utf8 = FALSE;
+ smart->priv->use_first = FALSE;
+
+ pluma_debug_message (DEBUG_UTILS, "initializing smart charset converter");
+}
+
+static const PlumaEncoding *
+get_encoding (PlumaSmartCharsetConverter *smart)
+{
+ if (smart->priv->current_encoding == NULL)
+ {
+ smart->priv->current_encoding = smart->priv->encodings;
+ }
+ else
+ {
+ smart->priv->current_encoding = g_slist_next (smart->priv->current_encoding);
+ }
+
+ if (smart->priv->current_encoding != NULL)
+ return (const PlumaEncoding *)smart->priv->current_encoding->data;
+
+#if 0
+ FIXME: uncomment this when using fallback
+ /* If we tried all encodings, we return the first encoding */
+ smart->priv->use_first = TRUE;
+ smart->priv->current_encoding = smart->priv->encodings;
+
+ return (const PlumaEncoding *)smart->priv->current_encoding->data;
+#endif
+ return NULL;
+}
+
+static gboolean
+try_convert (GCharsetConverter *converter,
+ const void *inbuf,
+ gsize inbuf_size)
+{
+ GError *err;
+ gsize bytes_read, nread;
+ gsize bytes_written, nwritten;
+ GConverterResult res;
+ gchar *out;
+ gboolean ret;
+ gsize out_size;
+
+ if (inbuf == NULL || inbuf_size == 0)
+ {
+ return FALSE;
+ }
+
+ err = NULL;
+ nread = 0;
+ nwritten = 0;
+ out_size = inbuf_size * 4;
+ out = g_malloc (out_size);
+
+ do
+ {
+ res = g_converter_convert (G_CONVERTER (converter),
+ inbuf + nread,
+ inbuf_size - nread,
+ out + nwritten,
+ out_size - nwritten,
+ G_CONVERTER_INPUT_AT_END,
+ &bytes_read,
+ &bytes_written,
+ &err);
+
+ nread += bytes_read;
+ nwritten += bytes_written;
+ } while (res != G_CONVERTER_FINISHED && res != G_CONVERTER_ERROR && err == NULL);
+
+ if (err != NULL)
+ {
+ if (err->code == G_CONVERT_ERROR_PARTIAL_INPUT)
+ {
+ /* FIXME We can get partial input while guessing the
+ encoding because we just take some amount of text
+ to guess from. */
+ ret = TRUE;
+ }
+ else
+ {
+ ret = FALSE;
+ }
+
+ g_error_free (err);
+ }
+ else
+ {
+ ret = TRUE;
+ }
+
+ /* FIXME: Check the remainder? */
+ if (ret == TRUE && !g_utf8_validate (out, nwritten, NULL))
+ {
+ ret = FALSE;
+ }
+
+ g_free (out);
+
+ return ret;
+}
+
+static GCharsetConverter *
+guess_encoding (PlumaSmartCharsetConverter *smart,
+ const void *inbuf,
+ gsize inbuf_size)
+{
+ GCharsetConverter *conv = NULL;
+
+ if (inbuf == NULL || inbuf_size == 0)
+ {
+ smart->priv->is_utf8 = TRUE;
+ return NULL;
+ }
+
+ if (smart->priv->encodings != NULL &&
+ smart->priv->encodings->next == NULL)
+ smart->priv->use_first = TRUE;
+
+ /* We just check the first block */
+ while (TRUE)
+ {
+ const PlumaEncoding *enc;
+
+ if (conv != NULL)
+ {
+ g_object_unref (conv);
+ conv = NULL;
+ }
+
+ /* We get an encoding from the list */
+ enc = get_encoding (smart);
+
+ /* if it is NULL we didn't guess anything */
+ if (enc == NULL)
+ {
+ break;
+ }
+
+ pluma_debug_message (DEBUG_UTILS, "trying charset: %s",
+ pluma_encoding_get_charset (smart->priv->current_encoding->data));
+
+ if (enc == pluma_encoding_get_utf8 ())
+ {
+ gsize remainder;
+ const gchar *end;
+
+ if (g_utf8_validate (inbuf, inbuf_size, &end) ||
+ smart->priv->use_first)
+ {
+ smart->priv->is_utf8 = TRUE;
+ break;
+ }
+
+ /* Check if the end is less than one char */
+ remainder = inbuf_size - (end - (gchar *)inbuf);
+ if (remainder < 6)
+ {
+ smart->priv->is_utf8 = TRUE;
+ break;
+ }
+
+ continue;
+ }
+
+ conv = g_charset_converter_new ("UTF-8",
+ pluma_encoding_get_charset (enc),
+ NULL);
+
+ /* If we tried all encodings we use the first one */
+ if (smart->priv->use_first)
+ {
+ break;
+ }
+
+ /* Try to convert */
+ if (try_convert (conv, inbuf, inbuf_size))
+ {
+ break;
+ }
+ }
+
+ if (conv != NULL)
+ {
+ g_converter_reset (G_CONVERTER (conv));
+
+ /* FIXME: uncomment this when we want to use the fallback
+ g_charset_converter_set_use_fallback (conv, TRUE);*/
+ }
+
+ return conv;
+}
+
+static GConverterResult
+pluma_smart_charset_converter_convert (GConverter *converter,
+ const void *inbuf,
+ gsize inbuf_size,
+ void *outbuf,
+ gsize outbuf_size,
+ GConverterFlags flags,
+ gsize *bytes_read,
+ gsize *bytes_written,
+ GError **error)
+{
+ PlumaSmartCharsetConverter *smart = PLUMA_SMART_CHARSET_CONVERTER (converter);
+
+ /* Guess the encoding if we didn't make it yet */
+ if (smart->priv->charset_conv == NULL &&
+ !smart->priv->is_utf8)
+ {
+ smart->priv->charset_conv = guess_encoding (smart, inbuf, inbuf_size);
+
+ /* If we still have the previous case is that we didn't guess
+ anything */
+ if (smart->priv->charset_conv == NULL &&
+ !smart->priv->is_utf8)
+ {
+ /* FIXME: Add a different domain when we kill pluma_convert */
+ g_set_error_literal (error, PLUMA_DOCUMENT_ERROR,
+ PLUMA_DOCUMENT_ERROR_ENCODING_AUTO_DETECTION_FAILED,
+ _("It is not possible to detect the encoding automatically"));
+ return G_CONVERTER_ERROR;
+ }
+ }
+
+ /* Now if the encoding is utf8 just redirect the input to the output */
+ if (smart->priv->is_utf8)
+ {
+ gsize size;
+ GConverterResult ret;
+
+ size = MIN (inbuf_size, outbuf_size);
+
+ memcpy (outbuf, inbuf, size);
+ *bytes_read = size;
+ *bytes_written = size;
+
+ ret = G_CONVERTER_CONVERTED;
+
+ if (flags & G_CONVERTER_INPUT_AT_END)
+ ret = G_CONVERTER_FINISHED;
+ else if (flags & G_CONVERTER_FLUSH)
+ ret = G_CONVERTER_FLUSHED;
+
+ return ret;
+ }
+
+ /* If we reached here is because we need to convert the text so, we
+ convert it with the charset converter */
+ return g_converter_convert (G_CONVERTER (smart->priv->charset_conv),
+ inbuf,
+ inbuf_size,
+ outbuf,
+ outbuf_size,
+ flags,
+ bytes_read,
+ bytes_written,
+ error);
+}
+
+static void
+pluma_smart_charset_converter_reset (GConverter *converter)
+{
+ PlumaSmartCharsetConverter *smart = PLUMA_SMART_CHARSET_CONVERTER (converter);
+
+ smart->priv->current_encoding = NULL;
+ smart->priv->is_utf8 = FALSE;
+
+ if (smart->priv->charset_conv != NULL)
+ {
+ g_object_unref (smart->priv->charset_conv);
+ smart->priv->charset_conv = NULL;
+ }
+}
+
+static void
+pluma_smart_charset_converter_iface_init (GConverterIface *iface)
+{
+ iface->convert = pluma_smart_charset_converter_convert;
+ iface->reset = pluma_smart_charset_converter_reset;
+}
+
+PlumaSmartCharsetConverter *
+pluma_smart_charset_converter_new (GSList *candidate_encodings)
+{
+ PlumaSmartCharsetConverter *smart;
+
+ g_return_val_if_fail (candidate_encodings != NULL, NULL);
+
+ smart = g_object_new (PLUMA_TYPE_SMART_CHARSET_CONVERTER, NULL);
+
+ smart->priv->encodings = g_slist_copy (candidate_encodings);
+
+ return smart;
+}
+
+const PlumaEncoding *
+pluma_smart_charset_converter_get_guessed (PlumaSmartCharsetConverter *smart)
+{
+ g_return_val_if_fail (PLUMA_IS_SMART_CHARSET_CONVERTER (smart), NULL);
+
+ if (smart->priv->current_encoding != NULL)
+ {
+ return (const PlumaEncoding *)smart->priv->current_encoding->data;
+ }
+ else if (smart->priv->is_utf8)
+ {
+ return pluma_encoding_get_utf8 ();
+ }
+
+ return NULL;
+}
+
+guint
+pluma_smart_charset_converter_get_num_fallbacks (PlumaSmartCharsetConverter *smart)
+{
+ g_return_val_if_fail (PLUMA_IS_SMART_CHARSET_CONVERTER (smart), FALSE);
+
+ if (smart->priv->charset_conv == NULL)
+ return FALSE;
+
+ return g_charset_converter_get_num_fallbacks (smart->priv->charset_conv) != 0;
+}
+
diff --git a/pluma/pluma-smart-charset-converter.h b/pluma/pluma-smart-charset-converter.h
new file mode 100755
index 00000000..3f6e74e8
--- /dev/null
+++ b/pluma/pluma-smart-charset-converter.h
@@ -0,0 +1,66 @@
+/*
+ * pluma-smart-charset-converter.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro
+ *
+ * pluma 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.
+ *
+ * pluma 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 pluma; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef __PLUMA_SMART_CHARSET_CONVERTER_H__
+#define __PLUMA_SMART_CHARSET_CONVERTER_H__
+
+#include <glib-object.h>
+
+#include "pluma-encodings.h"
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_SMART_CHARSET_CONVERTER (pluma_smart_charset_converter_get_type ())
+#define PLUMA_SMART_CHARSET_CONVERTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_SMART_CHARSET_CONVERTER, PlumaSmartCharsetConverter))
+#define PLUMA_SMART_CHARSET_CONVERTER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_SMART_CHARSET_CONVERTER, PlumaSmartCharsetConverter const))
+#define PLUMA_SMART_CHARSET_CONVERTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_SMART_CHARSET_CONVERTER, PlumaSmartCharsetConverterClass))
+#define PLUMA_IS_SMART_CHARSET_CONVERTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_SMART_CHARSET_CONVERTER))
+#define PLUMA_IS_SMART_CHARSET_CONVERTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_SMART_CHARSET_CONVERTER))
+#define PLUMA_SMART_CHARSET_CONVERTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_SMART_CHARSET_CONVERTER, PlumaSmartCharsetConverterClass))
+
+typedef struct _PlumaSmartCharsetConverter PlumaSmartCharsetConverter;
+typedef struct _PlumaSmartCharsetConverterClass PlumaSmartCharsetConverterClass;
+typedef struct _PlumaSmartCharsetConverterPrivate PlumaSmartCharsetConverterPrivate;
+
+struct _PlumaSmartCharsetConverter
+{
+ GObject parent;
+
+ PlumaSmartCharsetConverterPrivate *priv;
+};
+
+struct _PlumaSmartCharsetConverterClass
+{
+ GObjectClass parent_class;
+};
+
+GType pluma_smart_charset_converter_get_type (void) G_GNUC_CONST;
+
+PlumaSmartCharsetConverter *pluma_smart_charset_converter_new (GSList *candidate_encodings);
+
+const PlumaEncoding *pluma_smart_charset_converter_get_guessed (PlumaSmartCharsetConverter *smart);
+
+guint pluma_smart_charset_converter_get_num_fallbacks(PlumaSmartCharsetConverter *smart);
+
+G_END_DECLS
+
+#endif /* __PLUMA_SMART_CHARSET_CONVERTER_H__ */
diff --git a/pluma/pluma-spinner.c b/pluma/pluma-spinner.c
new file mode 100755
index 00000000..38a6f34e
--- /dev/null
+++ b/pluma/pluma-spinner.c
@@ -0,0 +1,989 @@
+/*
+ * pluma-spinner.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2002-2004 Marco Pesenti Gritti
+ * Copyright (C) 2004 Christian Persch
+ * Copyright (C) 2000 - Eazel, Inc.
+ *
+ * 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.
+ */
+
+/*
+ * This widget was originally written by Andy Hertzfeld <[email protected]> for
+ * Caja. It was then modified by Marco Pesenti Gritti and Christian Persch
+ * for Epiphany.
+ *
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pluma-spinner.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+
+/* Spinner cache implementation */
+
+#define PLUMA_TYPE_SPINNER_CACHE (pluma_spinner_cache_get_type())
+#define PLUMA_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), PLUMA_TYPE_SPINNER_CACHE, PlumaSpinnerCache))
+#define PLUMA_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_SPINNER_CACHE, PlumaSpinnerCacheClass))
+#define PLUMA_IS_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), PLUMA_TYPE_SPINNER_CACHE))
+#define PLUMA_IS_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PLUMA_TYPE_SPINNER_CACHE))
+#define PLUMA_SPINNER_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_SPINNER_CACHE, PlumaSpinnerCacheClass))
+
+typedef struct _PlumaSpinnerCache PlumaSpinnerCache;
+typedef struct _PlumaSpinnerCacheClass PlumaSpinnerCacheClass;
+typedef struct _PlumaSpinnerCachePrivate PlumaSpinnerCachePrivate;
+
+struct _PlumaSpinnerCacheClass
+{
+ GObjectClass parent_class;
+};
+
+struct _PlumaSpinnerCache
+{
+ GObject parent_object;
+
+ /*< private >*/
+ PlumaSpinnerCachePrivate *priv;
+};
+
+#define PLUMA_SPINNER_CACHE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_SPINNER_CACHE, PlumaSpinnerCachePrivate))
+
+struct _PlumaSpinnerCachePrivate
+{
+ /* Hash table of GdkScreen -> PlumaSpinnerCacheData */
+ GHashTable *hash;
+};
+
+typedef struct
+{
+ guint ref_count;
+ GtkIconSize size;
+ gint width;
+ gint height;
+ GdkPixbuf **animation_pixbufs;
+ guint n_animation_pixbufs;
+} PlumaSpinnerImages;
+
+#define LAST_ICON_SIZE GTK_ICON_SIZE_DIALOG + 1
+#define SPINNER_ICON_NAME "process-working"
+#define SPINNER_FALLBACK_ICON_NAME "mate-spinner"
+#define PLUMA_SPINNER_IMAGES_INVALID ((PlumaSpinnerImages *) 0x1)
+
+typedef struct
+{
+ GdkScreen *screen;
+ GtkIconTheme *icon_theme;
+ PlumaSpinnerImages *images[LAST_ICON_SIZE];
+} PlumaSpinnerCacheData;
+
+static void pluma_spinner_cache_class_init (PlumaSpinnerCacheClass *klass);
+static void pluma_spinner_cache_init (PlumaSpinnerCache *cache);
+
+static GObjectClass *pluma_spinner_cache_parent_class;
+
+static GType
+pluma_spinner_cache_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ const GTypeInfo our_info =
+ {
+ sizeof (PlumaSpinnerCacheClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) pluma_spinner_cache_class_init,
+ NULL,
+ NULL,
+ sizeof (PlumaSpinnerCache),
+ 0,
+ (GInstanceInitFunc) pluma_spinner_cache_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "PlumaSpinnerCache",
+ &our_info, 0);
+ }
+
+ return type;
+}
+
+static PlumaSpinnerImages *
+pluma_spinner_images_ref (PlumaSpinnerImages *images)
+{
+ g_return_val_if_fail (images != NULL, NULL);
+
+ images->ref_count++;
+
+ return images;
+}
+
+static void
+pluma_spinner_images_unref (PlumaSpinnerImages *images)
+{
+ g_return_if_fail (images != NULL);
+
+ images->ref_count--;
+ if (images->ref_count == 0)
+ {
+ guint i;
+
+ /* LOG ("Freeing spinner images %p for size %d", images, images->size); */
+
+ for (i = 0; i < images->n_animation_pixbufs; ++i)
+ {
+ g_object_unref (images->animation_pixbufs[i]);
+ }
+ g_free (images->animation_pixbufs);
+
+ g_free (images);
+ }
+}
+
+static void
+pluma_spinner_cache_data_unload (PlumaSpinnerCacheData *data)
+{
+ GtkIconSize size;
+ PlumaSpinnerImages *images;
+
+ g_return_if_fail (data != NULL);
+
+ /* LOG ("PlumaSpinnerDataCache unload for screen %p", data->screen); */
+
+ for (size = GTK_ICON_SIZE_INVALID; size < LAST_ICON_SIZE; ++size)
+ {
+ images = data->images[size];
+ data->images[size] = NULL;
+
+ if (images != NULL && images != PLUMA_SPINNER_IMAGES_INVALID)
+ {
+ pluma_spinner_images_unref (images);
+ }
+ }
+}
+
+static GdkPixbuf *
+extract_frame (GdkPixbuf *grid_pixbuf,
+ int x,
+ int y,
+ int size)
+{
+ GdkPixbuf *pixbuf;
+
+ if (x + size > gdk_pixbuf_get_width (grid_pixbuf) ||
+ y + size > gdk_pixbuf_get_height (grid_pixbuf))
+ {
+ return NULL;
+ }
+
+ pixbuf = gdk_pixbuf_new_subpixbuf (grid_pixbuf,
+ x, y,
+ size, size);
+ g_return_val_if_fail (pixbuf != NULL, NULL);
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+scale_to_size (GdkPixbuf *pixbuf,
+ int dw,
+ int dh)
+{
+ GdkPixbuf *result;
+ int pw, ph;
+
+ g_return_val_if_fail (pixbuf != NULL, NULL);
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ if (pw != dw || ph != dh)
+ {
+ result = gdk_pixbuf_scale_simple (pixbuf, dw, dh,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ return result;
+ }
+
+ return pixbuf;
+}
+
+static PlumaSpinnerImages *
+pluma_spinner_images_load (GdkScreen *screen,
+ GtkIconTheme *icon_theme,
+ GtkIconSize icon_size)
+{
+ PlumaSpinnerImages *images;
+ GdkPixbuf *icon_pixbuf, *pixbuf;
+ GtkIconInfo *icon_info = NULL;
+ int grid_width, grid_height, x, y, requested_size, size, isw, ish, n;
+ const char *icon;
+ GSList *list = NULL, *l;
+
+ /* LOG ("PlumaSpinnerCacheData loading for screen %p at size %d", screen, icon_size); */
+
+ /* START_PROFILER ("loading spinner animation") */
+
+ if (!gtk_icon_size_lookup_for_settings (gtk_settings_get_for_screen (screen),
+ icon_size, &isw, &ish))
+ goto loser;
+
+ requested_size = MAX (ish, isw);
+
+ /* Load the animation. The 'rest icon' is the 0th frame */
+ icon_info = gtk_icon_theme_lookup_icon (icon_theme,
+ SPINNER_ICON_NAME,
+ requested_size, 0);
+ if (icon_info == NULL)
+ {
+ g_warning ("Throbber animation not found");
+
+ /* If the icon naming spec compliant name wasn't found, try the old name */
+ icon_info = gtk_icon_theme_lookup_icon (icon_theme,
+ SPINNER_FALLBACK_ICON_NAME,
+ requested_size, 0);
+ if (icon_info == NULL)
+ {
+ g_warning ("Throbber fallback animation not found either");
+ goto loser;
+ }
+ }
+
+ g_assert (icon_info != NULL);
+
+ size = gtk_icon_info_get_base_size (icon_info);
+ icon = gtk_icon_info_get_filename (icon_info);
+
+ if (icon == NULL)
+ goto loser;
+
+ icon_pixbuf = gdk_pixbuf_new_from_file (icon, NULL);
+ gtk_icon_info_free (icon_info);
+ icon_info = NULL;
+
+ if (icon_pixbuf == NULL)
+ {
+ g_warning ("Could not load the spinner file");
+ goto loser;
+ }
+
+ grid_width = gdk_pixbuf_get_width (icon_pixbuf);
+ grid_height = gdk_pixbuf_get_height (icon_pixbuf);
+
+ n = 0;
+ for (y = 0; y < grid_height; y += size)
+ {
+ for (x = 0; x < grid_width ; x += size)
+ {
+ pixbuf = extract_frame (icon_pixbuf, x, y, size);
+
+ if (pixbuf)
+ {
+ list = g_slist_prepend (list, pixbuf);
+ ++n;
+ }
+ else
+ {
+ g_warning ("Cannot extract frame (%d, %d) from the grid\n", x, y);
+ }
+ }
+ }
+
+ g_object_unref (icon_pixbuf);
+
+ if (list == NULL)
+ goto loser;
+
+ /* g_assert (n > 0); */
+
+ if (size > requested_size)
+ {
+ for (l = list; l != NULL; l = l->next)
+ {
+ l->data = scale_to_size (l->data, isw, ish);
+ }
+ }
+
+ /* Now we've successfully got all the data */
+ images = g_new (PlumaSpinnerImages, 1);
+ images->ref_count = 1;
+
+ images->size = icon_size;
+ images->width = images->height = requested_size;
+
+ images->n_animation_pixbufs = n;
+ images->animation_pixbufs = g_new (GdkPixbuf *, n);
+
+ for (l = list; l != NULL; l = l->next)
+ {
+ g_assert (l->data != NULL);
+ images->animation_pixbufs[--n] = l->data;
+ }
+ g_assert (n == 0);
+
+ g_slist_free (list);
+
+ /* STOP_PROFILER ("loading spinner animation") */
+ return images;
+
+loser:
+ if (icon_info)
+ {
+ gtk_icon_info_free (icon_info);
+ }
+
+ g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+
+ /* STOP_PROFILER ("loading spinner animation") */
+
+ return NULL;
+}
+
+static PlumaSpinnerCacheData *
+pluma_spinner_cache_data_new (GdkScreen *screen)
+{
+ PlumaSpinnerCacheData *data;
+
+ data = g_new0 (PlumaSpinnerCacheData, 1);
+
+ data->screen = screen;
+ data->icon_theme = gtk_icon_theme_get_for_screen (screen);
+ g_signal_connect_swapped (data->icon_theme,
+ "changed",
+ G_CALLBACK (pluma_spinner_cache_data_unload),
+ data);
+
+ return data;
+}
+
+static void
+pluma_spinner_cache_data_free (PlumaSpinnerCacheData *data)
+{
+ g_return_if_fail (data != NULL);
+ g_return_if_fail (data->icon_theme != NULL);
+
+ g_signal_handlers_disconnect_by_func (data->icon_theme,
+ G_CALLBACK (pluma_spinner_cache_data_unload),
+ data);
+
+ pluma_spinner_cache_data_unload (data);
+
+ g_free (data);
+}
+
+static PlumaSpinnerImages *
+pluma_spinner_cache_get_images (PlumaSpinnerCache *cache,
+ GdkScreen *screen,
+ GtkIconSize icon_size)
+{
+ PlumaSpinnerCachePrivate *priv = cache->priv;
+ PlumaSpinnerCacheData *data;
+ PlumaSpinnerImages *images;
+
+ /* LOG ("Getting animation images for screen %p at size %d", screen, icon_size); */
+
+ g_return_val_if_fail (icon_size >= 0 && icon_size < LAST_ICON_SIZE, NULL);
+
+ /* Backward compat: "invalid" meant "native" size which doesn't exist anymore */
+ if (icon_size == GTK_ICON_SIZE_INVALID)
+ {
+ icon_size = GTK_ICON_SIZE_DIALOG;
+ }
+
+ data = g_hash_table_lookup (priv->hash, screen);
+ if (data == NULL)
+ {
+ data = pluma_spinner_cache_data_new (screen);
+ /* FIXME: think about what happens when the screen's display is closed later on */
+ g_hash_table_insert (priv->hash, screen, data);
+ }
+
+ images = data->images[icon_size];
+ if (images == PLUMA_SPINNER_IMAGES_INVALID)
+ {
+ /* Load failed, but don't try endlessly again! */
+ return NULL;
+ }
+
+ if (images != NULL)
+ {
+ /* Return cached data */
+ return pluma_spinner_images_ref (images);
+ }
+
+ images = pluma_spinner_images_load (screen, data->icon_theme, icon_size);
+
+ if (images == NULL)
+ {
+ /* Mark as failed-to-load */
+ data->images[icon_size] = PLUMA_SPINNER_IMAGES_INVALID;
+
+ return NULL;
+ }
+
+ data->images[icon_size] = images;
+
+ return pluma_spinner_images_ref (images);
+}
+
+static void
+pluma_spinner_cache_init (PlumaSpinnerCache *cache)
+{
+ PlumaSpinnerCachePrivate *priv;
+
+ priv = cache->priv = PLUMA_SPINNER_CACHE_GET_PRIVATE (cache);
+
+ /* LOG ("PlumaSpinnerCache initialising"); */
+
+ priv->hash = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify) pluma_spinner_cache_data_free);
+}
+
+static void
+pluma_spinner_cache_finalize (GObject *object)
+{
+ PlumaSpinnerCache *cache = PLUMA_SPINNER_CACHE (object);
+ PlumaSpinnerCachePrivate *priv = cache->priv;
+
+ g_hash_table_destroy (priv->hash);
+
+ /* LOG ("PlumaSpinnerCache finalised"); */
+
+ G_OBJECT_CLASS (pluma_spinner_cache_parent_class)->finalize (object);
+}
+
+static void
+pluma_spinner_cache_class_init (PlumaSpinnerCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ pluma_spinner_cache_parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = pluma_spinner_cache_finalize;
+
+ g_type_class_add_private (object_class, sizeof (PlumaSpinnerCachePrivate));
+}
+
+static PlumaSpinnerCache *spinner_cache = NULL;
+
+static PlumaSpinnerCache *
+pluma_spinner_cache_ref (void)
+{
+ if (spinner_cache == NULL)
+ {
+ PlumaSpinnerCache **cache_ptr;
+
+ spinner_cache = g_object_new (PLUMA_TYPE_SPINNER_CACHE, NULL);
+ cache_ptr = &spinner_cache;
+ g_object_add_weak_pointer (G_OBJECT (spinner_cache),
+ (gpointer *) cache_ptr);
+
+ return spinner_cache;
+ }
+
+ return g_object_ref (spinner_cache);
+}
+
+/* Spinner implementation */
+
+#define SPINNER_TIMEOUT 125 /* ms */
+
+#define PLUMA_SPINNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_SPINNER, PlumaSpinnerPrivate))
+
+struct _PlumaSpinnerPrivate
+{
+ GtkIconTheme *icon_theme;
+ PlumaSpinnerCache *cache;
+ GtkIconSize size;
+ PlumaSpinnerImages *images;
+ guint current_image;
+ guint timeout;
+ guint timer_task;
+ guint spinning : 1;
+ guint need_load : 1;
+};
+
+static void pluma_spinner_class_init (PlumaSpinnerClass *class);
+static void pluma_spinner_init (PlumaSpinner *spinner);
+
+static GObjectClass *parent_class;
+
+GType
+pluma_spinner_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ const GTypeInfo our_info =
+ {
+ sizeof (PlumaSpinnerClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) pluma_spinner_class_init,
+ NULL,
+ NULL, /* class_data */
+ sizeof (PlumaSpinner),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) pluma_spinner_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_WIDGET,
+ "PlumaSpinner",
+ &our_info, 0);
+ }
+
+ return type;
+}
+
+static gboolean
+pluma_spinner_load_images (PlumaSpinner *spinner)
+{
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ if (priv->need_load)
+ {
+ /* START_PROFILER ("pluma_spinner_load_images") */
+
+ priv->images =
+ pluma_spinner_cache_get_images (priv->cache,
+ gtk_widget_get_screen (GTK_WIDGET (spinner)),
+ priv->size);
+
+ /* STOP_PROFILER ("pluma_spinner_load_images") */
+
+ priv->current_image = 0; /* 'rest' icon */
+ priv->need_load = FALSE;
+ }
+
+ return priv->images != NULL;
+}
+
+static void
+pluma_spinner_unload_images (PlumaSpinner *spinner)
+{
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ if (priv->images != NULL)
+ {
+ pluma_spinner_images_unref (priv->images);
+ priv->images = NULL;
+ }
+
+ priv->current_image = 0;
+ priv->need_load = TRUE;
+}
+
+static void
+icon_theme_changed_cb (GtkIconTheme *icon_theme,
+ PlumaSpinner *spinner)
+{
+ pluma_spinner_unload_images (spinner);
+ gtk_widget_queue_resize (GTK_WIDGET (spinner));
+}
+
+static void
+pluma_spinner_init (PlumaSpinner *spinner)
+{
+ PlumaSpinnerPrivate *priv;
+
+ priv = spinner->priv = PLUMA_SPINNER_GET_PRIVATE (spinner);
+
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (spinner), GTK_NO_WINDOW);
+
+ priv->cache = pluma_spinner_cache_ref ();
+ priv->size = GTK_ICON_SIZE_DIALOG;
+ priv->spinning = FALSE;
+ priv->timeout = SPINNER_TIMEOUT;
+ priv->need_load = TRUE;
+}
+
+static int
+pluma_spinner_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ PlumaSpinner *spinner = PLUMA_SPINNER (widget);
+ PlumaSpinnerPrivate *priv = spinner->priv;
+ PlumaSpinnerImages *images;
+ GdkPixbuf *pixbuf;
+ GdkGC *gc;
+ int x_offset, y_offset, width, height;
+ GdkRectangle pix_area, dest;
+
+ if (!GTK_WIDGET_DRAWABLE (spinner))
+ {
+ return FALSE;
+ }
+
+ if (priv->need_load &&
+ !pluma_spinner_load_images (spinner))
+ {
+ return FALSE;
+ }
+
+ images = priv->images;
+ if (images == NULL)
+ {
+ return FALSE;
+ }
+
+ /* Otherwise |images| will be NULL anyway */
+ g_assert (images->n_animation_pixbufs > 0);
+
+ g_assert (priv->current_image >= 0 &&
+ priv->current_image < images->n_animation_pixbufs);
+
+ pixbuf = images->animation_pixbufs[priv->current_image];
+
+ g_assert (pixbuf != NULL);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ /* Compute the offsets for the image centered on our allocation */
+ x_offset = (widget->allocation.width - width) / 2;
+ y_offset = (widget->allocation.height - height) / 2;
+
+ pix_area.x = x_offset + widget->allocation.x;
+ pix_area.y = y_offset + widget->allocation.y;
+ pix_area.width = width;
+ pix_area.height = height;
+
+ if (!gdk_rectangle_intersect (&event->area, &pix_area, &dest))
+ {
+ return FALSE;
+ }
+
+ gc = gdk_gc_new (widget->window);
+ gdk_draw_pixbuf (widget->window, gc, pixbuf,
+ dest.x - x_offset - widget->allocation.x,
+ dest.y - y_offset - widget->allocation.y,
+ dest.x, dest.y,
+ dest.width, dest.height,
+ GDK_RGB_DITHER_MAX, 0, 0);
+ g_object_unref (gc);
+
+ return FALSE;
+}
+
+static gboolean
+bump_spinner_frame_cb (PlumaSpinner *spinner)
+{
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ /* This can happen when we've unloaded the images on a theme
+ * change, but haven't been in the queued size request yet.
+ * Just skip this update.
+ */
+ if (priv->images == NULL)
+ return TRUE;
+
+ priv->current_image++;
+ if (priv->current_image >= priv->images->n_animation_pixbufs)
+ {
+ /* the 0th frame is the 'rest' icon */
+ priv->current_image = MIN (1, priv->images->n_animation_pixbufs);
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (spinner));
+
+ /* run again */
+ return TRUE;
+}
+
+/**
+ * pluma_spinner_start:
+ * @spinner: a #PlumaSpinner
+ *
+ * Start the spinner animation.
+ **/
+void
+pluma_spinner_start (PlumaSpinner *spinner)
+{
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ priv->spinning = TRUE;
+
+ if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)) &&
+ priv->timer_task == 0 &&
+ pluma_spinner_load_images (spinner))
+ {
+ /* the 0th frame is the 'rest' icon */
+ priv->current_image = MIN (1, priv->images->n_animation_pixbufs);
+
+ priv->timer_task = g_timeout_add_full (G_PRIORITY_LOW,
+ priv->timeout,
+ (GSourceFunc) bump_spinner_frame_cb,
+ spinner,
+ NULL);
+ }
+}
+
+static void
+pluma_spinner_remove_update_callback (PlumaSpinner *spinner)
+{
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ if (priv->timer_task != 0)
+ {
+ g_source_remove (priv->timer_task);
+ priv->timer_task = 0;
+ }
+}
+
+/**
+ * pluma_spinner_stop:
+ * @spinner: a #PlumaSpinner
+ *
+ * Stop the spinner animation.
+ **/
+void
+pluma_spinner_stop (PlumaSpinner *spinner)
+{
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ priv->spinning = FALSE;
+ priv->current_image = 0;
+
+ if (priv->timer_task != 0)
+ {
+ pluma_spinner_remove_update_callback (spinner);
+
+ if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)))
+ gtk_widget_queue_draw (GTK_WIDGET (spinner));
+ }
+}
+
+/*
+ * pluma_spinner_set_size:
+ * @spinner: a #PlumaSpinner
+ * @size: the size of type %GtkIconSize
+ *
+ * Set the size of the spinner.
+ **/
+void
+pluma_spinner_set_size (PlumaSpinner *spinner,
+ GtkIconSize size)
+{
+ if (size == GTK_ICON_SIZE_INVALID)
+ {
+ size = GTK_ICON_SIZE_DIALOG;
+ }
+
+ if (size != spinner->priv->size)
+ {
+ pluma_spinner_unload_images (spinner);
+
+ spinner->priv->size = size;
+
+ gtk_widget_queue_resize (GTK_WIDGET (spinner));
+ }
+}
+
+#if 0
+/*
+* pluma_spinner_set_timeout:
+* @spinner: a #PlumaSpinner
+* @timeout: time delay between updates to the spinner.
+*
+* Sets the timeout delay for spinner updates.
+**/
+void
+pluma_spinner_set_timeout (PlumaSpinner *spinner,
+ guint timeout)
+{
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ if (timeout != priv->timeout)
+ {
+ pluma_spinner_stop (spinner);
+
+ priv->timeout = timeout;
+
+ if (priv->spinning)
+ {
+ pluma_spinner_start (spinner);
+ }
+ }
+}
+#endif
+
+static void
+pluma_spinner_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ PlumaSpinner *spinner = PLUMA_SPINNER (widget);
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ if ((priv->need_load &&
+ !pluma_spinner_load_images (spinner)) ||
+ priv->images == NULL)
+ {
+ requisition->width = requisition->height = 0;
+ gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget),
+ priv->size,
+ &requisition->width,
+ &requisition->height);
+ return;
+ }
+
+ requisition->width = priv->images->width;
+ requisition->height = priv->images->height;
+
+ /* FIXME fix this hack */
+ /* allocate some extra margin so we don't butt up against toolbar edges */
+ if (priv->size != GTK_ICON_SIZE_MENU)
+ {
+ requisition->width += 2;
+ requisition->height += 2;
+ }
+}
+
+static void
+pluma_spinner_map (GtkWidget *widget)
+{
+ PlumaSpinner *spinner = PLUMA_SPINNER (widget);
+ PlumaSpinnerPrivate *priv = spinner->priv;
+
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+ if (priv->spinning)
+ {
+ pluma_spinner_start (spinner);
+ }
+}
+
+static void
+pluma_spinner_unmap (GtkWidget *widget)
+{
+ PlumaSpinner *spinner = PLUMA_SPINNER (widget);
+
+ pluma_spinner_remove_update_callback (spinner);
+
+ GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+static void
+pluma_spinner_dispose (GObject *object)
+{
+ PlumaSpinner *spinner = PLUMA_SPINNER (object);
+
+ g_signal_handlers_disconnect_by_func
+ (spinner->priv->icon_theme,
+ G_CALLBACK (icon_theme_changed_cb), spinner);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+pluma_spinner_finalize (GObject *object)
+{
+ PlumaSpinner *spinner = PLUMA_SPINNER (object);
+
+ pluma_spinner_remove_update_callback (spinner);
+ pluma_spinner_unload_images (spinner);
+
+ g_object_unref (spinner->priv->cache);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+pluma_spinner_screen_changed (GtkWidget *widget,
+ GdkScreen *old_screen)
+{
+ PlumaSpinner *spinner = PLUMA_SPINNER (widget);
+ PlumaSpinnerPrivate *priv = spinner->priv;
+ GdkScreen *screen;
+
+ if (GTK_WIDGET_CLASS (parent_class)->screen_changed)
+ {
+ GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, old_screen);
+ }
+
+ screen = gtk_widget_get_screen (widget);
+
+ /* FIXME: this seems to be happening when then spinner is destroyed!? */
+ if (old_screen == screen)
+ return;
+
+ /* We'll get mapped again on the new screen, but not unmapped from
+ * the old screen, so remove timeout here.
+ */
+ pluma_spinner_remove_update_callback (spinner);
+
+ pluma_spinner_unload_images (spinner);
+
+ if (old_screen != NULL)
+ {
+ g_signal_handlers_disconnect_by_func
+ (gtk_icon_theme_get_for_screen (old_screen),
+ G_CALLBACK (icon_theme_changed_cb), spinner);
+ }
+
+ priv->icon_theme = gtk_icon_theme_get_for_screen (screen);
+ g_signal_connect (priv->icon_theme, "changed",
+ G_CALLBACK (icon_theme_changed_cb), spinner);
+}
+
+static void
+pluma_spinner_class_init (PlumaSpinnerClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class->dispose = pluma_spinner_dispose;
+ object_class->finalize = pluma_spinner_finalize;
+
+ widget_class->expose_event = pluma_spinner_expose;
+ widget_class->size_request = pluma_spinner_size_request;
+ widget_class->map = pluma_spinner_map;
+ widget_class->unmap = pluma_spinner_unmap;
+ widget_class->screen_changed = pluma_spinner_screen_changed;
+
+ g_type_class_add_private (object_class, sizeof (PlumaSpinnerPrivate));
+}
+
+/*
+ * pluma_spinner_new:
+ *
+ * Create a new #PlumaSpinner. The spinner is a widget
+ * that gives the user feedback about network status with
+ * an animated image.
+ *
+ * Return Value: the spinner #GtkWidget
+ **/
+GtkWidget *
+pluma_spinner_new (void)
+{
+ return GTK_WIDGET (g_object_new (PLUMA_TYPE_SPINNER, NULL));
+}
diff --git a/pluma/pluma-spinner.h b/pluma/pluma-spinner.h
new file mode 100755
index 00000000..0461beab
--- /dev/null
+++ b/pluma/pluma-spinner.h
@@ -0,0 +1,95 @@
+/*
+ * pluma-spinner.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2000 - Eazel, Inc.
+ *
+ * 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.
+ */
+
+/*
+ * This widget was originally written by Andy Hertzfeld <[email protected]> for
+ * Caja.
+ *
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_SPINNER_H__
+#define __PLUMA_SPINNER_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_SPINNER (pluma_spinner_get_type ())
+#define PLUMA_SPINNER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), PLUMA_TYPE_SPINNER, PlumaSpinner))
+#define PLUMA_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), PLUMA_TYPE_SPINNER, PlumaSpinnerClass))
+#define PLUMA_IS_SPINNER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), PLUMA_TYPE_SPINNER))
+#define PLUMA_IS_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), PLUMA_TYPE_SPINNER))
+#define PLUMA_SPINNER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), PLUMA_TYPE_SPINNER, PlumaSpinnerClass))
+
+
+/* Private structure type */
+typedef struct _PlumaSpinnerPrivate PlumaSpinnerPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaSpinner PlumaSpinner;
+
+struct _PlumaSpinner
+{
+ GtkWidget parent;
+
+ /*< private >*/
+ PlumaSpinnerPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaSpinnerClass PlumaSpinnerClass;
+
+struct _PlumaSpinnerClass
+{
+ GtkWidgetClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType pluma_spinner_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_spinner_new (void);
+
+void pluma_spinner_start (PlumaSpinner *throbber);
+
+void pluma_spinner_stop (PlumaSpinner *throbber);
+
+void pluma_spinner_set_size (PlumaSpinner *spinner,
+ GtkIconSize size);
+
+G_END_DECLS
+
+#endif /* __PLUMA_SPINNER_H__ */
diff --git a/pluma/pluma-status-combo-box.c b/pluma/pluma-status-combo-box.c
new file mode 100755
index 00000000..df8808da
--- /dev/null
+++ b/pluma/pluma-status-combo-box.c
@@ -0,0 +1,418 @@
+/*
+ * pluma-status-combo-box.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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.
+ */
+
+#include "pluma-status-combo-box.h"
+
+#define COMBO_BOX_TEXT_DATA "PlumaStatusComboBoxTextData"
+
+#define PLUMA_STATUS_COMBO_BOX_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), PLUMA_TYPE_STATUS_COMBO_BOX, PlumaStatusComboBoxPrivate))
+
+struct _PlumaStatusComboBoxPrivate
+{
+ GtkWidget *frame;
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkWidget *item;
+ GtkWidget *arrow;
+
+ GtkWidget *menu;
+ GtkWidget *current_item;
+};
+
+/* Signals */
+enum
+{
+ CHANGED,
+ NUM_SIGNALS
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+
+ PROP_LABEL
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE(PlumaStatusComboBox, pluma_status_combo_box, GTK_TYPE_EVENT_BOX)
+
+static void
+pluma_status_combo_box_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (pluma_status_combo_box_parent_class)->finalize (object);
+}
+
+static void
+pluma_status_combo_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaStatusComboBox *obj = PLUMA_STATUS_COMBO_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ g_value_set_string (value, pluma_status_combo_box_get_label (obj));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_status_combo_box_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaStatusComboBox *obj = PLUMA_STATUS_COMBO_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ pluma_status_combo_box_set_label (obj, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_status_combo_box_changed (PlumaStatusComboBox *combo,
+ GtkMenuItem *item)
+{
+ const gchar *text;
+
+ text = g_object_get_data (G_OBJECT (item), COMBO_BOX_TEXT_DATA);
+
+ if (text != NULL)
+ {
+ gtk_label_set_markup (GTK_LABEL (combo->priv->item), text);
+ combo->priv->current_item = GTK_WIDGET (item);
+ }
+}
+
+static void
+pluma_status_combo_box_class_init (PlumaStatusComboBoxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_status_combo_box_finalize;
+ object_class->get_property = pluma_status_combo_box_get_property;
+ object_class->set_property = pluma_status_combo_box_set_property;
+
+ klass->changed = pluma_status_combo_box_changed;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaStatusComboBoxClass,
+ changed), NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
+ GTK_TYPE_MENU_ITEM);
+
+ g_object_class_install_property (object_class, PROP_LABEL,
+ g_param_spec_string ("label",
+ "LABEL",
+ "The label",
+ NULL,
+ G_PARAM_READWRITE));
+
+ /* Set up a style for the button to decrease spacing. */
+ gtk_rc_parse_string (
+ "style \"pluma-status-combo-button-style\"\n"
+ "{\n"
+ " GtkWidget::focus-padding = 0\n"
+ " GtkWidget::focus-line-width = 0\n"
+ " xthickness = 0\n"
+ " ythickness = 0\n"
+ "}\n"
+ "widget \"*.pluma-status-combo-button\" style \"pluma-status-combo-button-style\"");
+
+ g_type_class_add_private (object_class, sizeof(PlumaStatusComboBoxPrivate));
+}
+
+static void
+menu_deactivate (GtkMenu *menu,
+ PlumaStatusComboBox *combo)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo->priv->button), FALSE);
+}
+
+static void
+menu_position_func (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ PlumaStatusComboBox *combo)
+{
+ GtkRequisition request;
+
+ *push_in = FALSE;
+
+ gtk_widget_size_request (gtk_widget_get_toplevel (GTK_WIDGET (menu)), &request);
+
+ /* get the origin... */
+ gdk_window_get_origin (GTK_WIDGET (combo)->window, x, y);
+
+ /* make the menu as wide as the widget */
+ if (request.width < GTK_WIDGET (combo)->allocation.width)
+ {
+ gtk_widget_set_size_request (GTK_WIDGET (menu), GTK_WIDGET (combo)->allocation.width, -1);
+ }
+
+ /* position it above the widget */
+ *y -= request.height;
+}
+
+static void
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ PlumaStatusComboBox *combo)
+{
+ GtkRequisition request;
+ gint max_height;
+
+ gtk_widget_size_request (combo->priv->menu, &request);
+
+ /* do something relative to our own height here, maybe we can do better */
+ max_height = GTK_WIDGET (combo)->allocation.height * 20;
+
+ if (request.height > max_height)
+ {
+ gtk_widget_set_size_request (combo->priv->menu, -1, max_height);
+ gtk_widget_set_size_request (gtk_widget_get_toplevel (combo->priv->menu), -1, max_height);
+ }
+
+ gtk_menu_popup (GTK_MENU (combo->priv->menu),
+ NULL,
+ NULL,
+ (GtkMenuPositionFunc)menu_position_func,
+ combo,
+ event->button,
+ event->time);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo->priv->button), TRUE);
+
+ if (combo->priv->current_item)
+ {
+ gtk_menu_shell_select_item (GTK_MENU_SHELL (combo->priv->menu),
+ combo->priv->current_item);
+ }
+}
+
+static void
+set_shadow_type (PlumaStatusComboBox *combo)
+{
+ GtkShadowType shadow_type;
+ GtkWidget *statusbar;
+
+ /* This is a hack needed to use the shadow type of a statusbar */
+ statusbar = gtk_statusbar_new ();
+ gtk_widget_ensure_style (statusbar);
+
+ gtk_widget_style_get (statusbar, "shadow-type", &shadow_type, NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (combo->priv->frame), shadow_type);
+
+ gtk_widget_destroy (statusbar);
+}
+
+static void
+pluma_status_combo_box_init (PlumaStatusComboBox *self)
+{
+ self->priv = PLUMA_STATUS_COMBO_BOX_GET_PRIVATE (self);
+
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (self), TRUE);
+
+ self->priv->frame = gtk_frame_new (NULL);
+ gtk_widget_show (self->priv->frame);
+
+ self->priv->button = gtk_toggle_button_new ();
+ gtk_widget_set_name (self->priv->button, "pluma-status-combo-button");
+ gtk_button_set_relief (GTK_BUTTON (self->priv->button), GTK_RELIEF_NONE);
+ gtk_widget_show (self->priv->button);
+
+ set_shadow_type (self);
+
+ self->priv->hbox = gtk_hbox_new (FALSE, 3);
+ gtk_widget_show (self->priv->hbox);
+
+ gtk_container_add (GTK_CONTAINER (self), self->priv->frame);
+ gtk_container_add (GTK_CONTAINER (self->priv->frame), self->priv->button);
+ gtk_container_add (GTK_CONTAINER (self->priv->button), self->priv->hbox);
+
+ self->priv->label = gtk_label_new ("");
+ gtk_widget_show (self->priv->label);
+
+ gtk_label_set_single_line_mode (GTK_LABEL (self->priv->label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (self->priv->label), 0.0, 0.5);
+
+ gtk_box_pack_start (GTK_BOX (self->priv->hbox), self->priv->label, FALSE, TRUE, 0);
+
+ self->priv->item = gtk_label_new ("");
+ gtk_widget_show (self->priv->item);
+
+ gtk_label_set_single_line_mode (GTK_LABEL (self->priv->item), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (self->priv->item), 0, 0.5);
+
+ gtk_box_pack_start (GTK_BOX (self->priv->hbox), self->priv->item, TRUE, TRUE, 0);
+
+ self->priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_widget_show (self->priv->arrow);
+ gtk_misc_set_alignment (GTK_MISC (self->priv->arrow), 0.5, 0.5);
+
+ gtk_box_pack_start (GTK_BOX (self->priv->hbox), self->priv->arrow, FALSE, TRUE, 0);
+
+ self->priv->menu = gtk_menu_new ();
+ g_object_ref_sink (self->priv->menu);
+
+ g_signal_connect (self->priv->button,
+ "button-press-event",
+ G_CALLBACK (button_press_event),
+ self);
+ g_signal_connect (self->priv->menu,
+ "deactivate",
+ G_CALLBACK (menu_deactivate),
+ self);
+}
+
+/* public functions */
+GtkWidget *
+pluma_status_combo_box_new (const gchar *label)
+{
+ return g_object_new (PLUMA_TYPE_STATUS_COMBO_BOX, "label", label, NULL);
+}
+
+void
+pluma_status_combo_box_set_label (PlumaStatusComboBox *combo,
+ const gchar *label)
+{
+ gchar *text;
+
+ g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
+
+ text = g_strconcat (" ", label, ": ", NULL);
+ gtk_label_set_markup (GTK_LABEL (combo->priv->label), text);
+ g_free (text);
+}
+
+const gchar *
+pluma_status_combo_box_get_label (PlumaStatusComboBox *combo)
+{
+ g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
+
+ return gtk_label_get_label (GTK_LABEL (combo->priv->label));
+}
+
+static void
+item_activated (GtkMenuItem *item,
+ PlumaStatusComboBox *combo)
+{
+ pluma_status_combo_box_set_item (combo, item);
+}
+
+void
+pluma_status_combo_box_add_item (PlumaStatusComboBox *combo,
+ GtkMenuItem *item,
+ const gchar *text)
+{
+ g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
+ g_return_if_fail (GTK_IS_MENU_ITEM (item));
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (combo->priv->menu), GTK_WIDGET (item));
+
+ pluma_status_combo_box_set_item_text (combo, item, text);
+ g_signal_connect (item, "activate", G_CALLBACK (item_activated), combo);
+}
+
+void
+pluma_status_combo_box_remove_item (PlumaStatusComboBox *combo,
+ GtkMenuItem *item)
+{
+ g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
+ g_return_if_fail (GTK_IS_MENU_ITEM (item));
+
+ gtk_container_remove (GTK_CONTAINER (combo->priv->menu),
+ GTK_WIDGET (item));
+}
+
+GList *
+pluma_status_combo_box_get_items (PlumaStatusComboBox *combo)
+{
+ g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
+
+ return gtk_container_get_children (GTK_CONTAINER (combo->priv->menu));
+}
+
+const gchar *
+pluma_status_combo_box_get_item_text (PlumaStatusComboBox *combo,
+ GtkMenuItem *item)
+{
+ const gchar *ret = NULL;
+
+ g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
+ g_return_val_if_fail (GTK_IS_MENU_ITEM (item), NULL);
+
+ ret = g_object_get_data (G_OBJECT (item), COMBO_BOX_TEXT_DATA);
+
+ return ret;
+}
+
+void
+pluma_status_combo_box_set_item_text (PlumaStatusComboBox *combo,
+ GtkMenuItem *item,
+ const gchar *text)
+{
+ g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
+ g_return_if_fail (GTK_IS_MENU_ITEM (item));
+
+ g_object_set_data_full (G_OBJECT (item),
+ COMBO_BOX_TEXT_DATA,
+ g_strdup (text),
+ (GDestroyNotify)g_free);
+}
+
+void
+pluma_status_combo_box_set_item (PlumaStatusComboBox *combo,
+ GtkMenuItem *item)
+{
+ g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
+ g_return_if_fail (GTK_IS_MENU_ITEM (item));
+
+ g_signal_emit (combo, signals[CHANGED], 0, item, NULL);
+}
+
+GtkLabel *
+pluma_status_combo_box_get_item_label (PlumaStatusComboBox *combo)
+{
+ g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
+
+ return GTK_LABEL (combo->priv->item);
+}
+
diff --git a/pluma/pluma-status-combo-box.h b/pluma/pluma-status-combo-box.h
new file mode 100755
index 00000000..f95f8f4b
--- /dev/null
+++ b/pluma/pluma-status-combo-box.h
@@ -0,0 +1,82 @@
+/*
+ * pluma-status-combo-box.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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 __PLUMA_STATUS_COMBO_BOX_H__
+#define __PLUMA_STATUS_COMBO_BOX_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_STATUS_COMBO_BOX (pluma_status_combo_box_get_type ())
+#define PLUMA_STATUS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_STATUS_COMBO_BOX, PlumaStatusComboBox))
+#define PLUMA_STATUS_COMBO_BOX_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_STATUS_COMBO_BOX, PlumaStatusComboBox const))
+#define PLUMA_STATUS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_STATUS_COMBO_BOX, PlumaStatusComboBoxClass))
+#define PLUMA_IS_STATUS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_STATUS_COMBO_BOX))
+#define PLUMA_IS_STATUS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_STATUS_COMBO_BOX))
+#define PLUMA_STATUS_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_STATUS_COMBO_BOX, PlumaStatusComboBoxClass))
+
+typedef struct _PlumaStatusComboBox PlumaStatusComboBox;
+typedef struct _PlumaStatusComboBoxClass PlumaStatusComboBoxClass;
+typedef struct _PlumaStatusComboBoxPrivate PlumaStatusComboBoxPrivate;
+
+struct _PlumaStatusComboBox {
+ GtkEventBox parent;
+
+ PlumaStatusComboBoxPrivate *priv;
+};
+
+struct _PlumaStatusComboBoxClass {
+ GtkEventBoxClass parent_class;
+
+ void (*changed) (PlumaStatusComboBox *combo,
+ GtkMenuItem *item);
+};
+
+GType pluma_status_combo_box_get_type (void) G_GNUC_CONST;
+GtkWidget *pluma_status_combo_box_new (const gchar *label);
+
+const gchar *pluma_status_combo_box_get_label (PlumaStatusComboBox *combo);
+void pluma_status_combo_box_set_label (PlumaStatusComboBox *combo,
+ const gchar *label);
+
+void pluma_status_combo_box_add_item (PlumaStatusComboBox *combo,
+ GtkMenuItem *item,
+ const gchar *text);
+void pluma_status_combo_box_remove_item (PlumaStatusComboBox *combo,
+ GtkMenuItem *item);
+
+GList *pluma_status_combo_box_get_items (PlumaStatusComboBox *combo);
+const gchar *pluma_status_combo_box_get_item_text (PlumaStatusComboBox *combo,
+ GtkMenuItem *item);
+void pluma_status_combo_box_set_item_text (PlumaStatusComboBox *combo,
+ GtkMenuItem *item,
+ const gchar *text);
+
+void pluma_status_combo_box_set_item (PlumaStatusComboBox *combo,
+ GtkMenuItem *item);
+
+GtkLabel *pluma_status_combo_box_get_item_label (PlumaStatusComboBox *combo);
+
+G_END_DECLS
+
+#endif /* __PLUMA_STATUS_COMBO_BOX_H__ */
diff --git a/pluma/pluma-statusbar.c b/pluma/pluma-statusbar.c
new file mode 100755
index 00000000..bf0f3dd3
--- /dev/null
+++ b/pluma/pluma-statusbar.c
@@ -0,0 +1,448 @@
+/*
+ * pluma-statusbar.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Borelli
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "pluma-statusbar.h"
+
+#define PLUMA_STATUSBAR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object),\
+ PLUMA_TYPE_STATUSBAR,\
+ PlumaStatusbarPrivate))
+
+struct _PlumaStatusbarPrivate
+{
+ GtkWidget *overwrite_mode_statusbar;
+ GtkWidget *cursor_position_statusbar;
+
+ GtkWidget *state_frame;
+ GtkWidget *load_image;
+ GtkWidget *save_image;
+ GtkWidget *print_image;
+
+ GtkWidget *error_frame;
+ GtkWidget *error_event_box;
+
+ /* tmp flash timeout data */
+ guint flash_timeout;
+ guint flash_context_id;
+ guint flash_message_id;
+};
+
+G_DEFINE_TYPE(PlumaStatusbar, pluma_statusbar, GTK_TYPE_STATUSBAR)
+
+
+static gchar *
+get_overwrite_mode_string (gboolean overwrite)
+{
+ return g_strconcat (" ", overwrite ? _("OVR") : _("INS"), NULL);
+}
+
+static gint
+get_overwrite_mode_length (void)
+{
+ return 2 + MAX (g_utf8_strlen (_("OVR"), -1), g_utf8_strlen (_("INS"), -1));
+}
+
+static void
+pluma_statusbar_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ /* don't allow gtk_statusbar_set_has_resize_grip to mess with us.
+ * See _pluma_statusbar_set_has_resize_grip for an explanation.
+ */
+ if (strcmp (g_param_spec_get_name (pspec), "has-resize-grip") == 0)
+ {
+ gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (object), FALSE);
+ return;
+ }
+
+ if (G_OBJECT_CLASS (pluma_statusbar_parent_class)->notify)
+ G_OBJECT_CLASS (pluma_statusbar_parent_class)->notify (object, pspec);
+}
+
+static void
+pluma_statusbar_finalize (GObject *object)
+{
+ PlumaStatusbar *statusbar = PLUMA_STATUSBAR (object);
+
+ if (statusbar->priv->flash_timeout > 0)
+ g_source_remove (statusbar->priv->flash_timeout);
+
+ G_OBJECT_CLASS (pluma_statusbar_parent_class)->finalize (object);
+}
+
+static void
+pluma_statusbar_class_init (PlumaStatusbarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->notify = pluma_statusbar_notify;
+ object_class->finalize = pluma_statusbar_finalize;
+
+ g_type_class_add_private (object_class, sizeof (PlumaStatusbarPrivate));
+}
+
+#define RESIZE_GRIP_EXTRA_WIDTH 30
+
+static void
+set_statusbar_width_chars (GtkWidget *statusbar,
+ gint n_chars,
+ gboolean has_resize_grip)
+{
+ PangoContext *context;
+ PangoFontMetrics *metrics;
+ gint char_width, digit_width, width;
+ GtkStyle *style;
+
+ context = gtk_widget_get_pango_context (statusbar);
+ style = gtk_widget_get_style (GTK_WIDGET (statusbar));
+ metrics = pango_context_get_metrics (context,
+ style->font_desc,
+ pango_context_get_language (context));
+
+ char_width = pango_font_metrics_get_approximate_digit_width (metrics);
+ digit_width = pango_font_metrics_get_approximate_char_width (metrics);
+
+ width = PANGO_PIXELS (MAX (char_width, digit_width) * n_chars);
+
+ pango_font_metrics_unref (metrics);
+
+ /* If there is a resize grip, allocate some extra width.
+ * It would be nice to calculate the exact size programmatically
+ * but I could not find out how to do it */
+ if (has_resize_grip)
+ width += RESIZE_GRIP_EXTRA_WIDTH;
+
+ gtk_widget_set_size_request (statusbar, width, -1);
+}
+
+static void
+pluma_statusbar_init (PlumaStatusbar *statusbar)
+{
+ GtkWidget *hbox;
+ GtkWidget *error_image;
+
+ statusbar->priv = PLUMA_STATUSBAR_GET_PRIVATE (statusbar);
+
+ gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (statusbar), FALSE);
+
+ statusbar->priv->overwrite_mode_statusbar = gtk_statusbar_new ();
+ gtk_widget_show (statusbar->priv->overwrite_mode_statusbar);
+ gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (statusbar->priv->overwrite_mode_statusbar),
+ TRUE);
+ set_statusbar_width_chars (statusbar->priv->overwrite_mode_statusbar,
+ get_overwrite_mode_length (),
+ TRUE);
+ gtk_box_pack_end (GTK_BOX (statusbar),
+ statusbar->priv->overwrite_mode_statusbar,
+ FALSE, TRUE, 0);
+
+ statusbar->priv->cursor_position_statusbar = gtk_statusbar_new ();
+ gtk_widget_show (statusbar->priv->cursor_position_statusbar);
+ gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (statusbar->priv->cursor_position_statusbar),
+ FALSE);
+ set_statusbar_width_chars (statusbar->priv->cursor_position_statusbar, 18, FALSE);
+ gtk_box_pack_end (GTK_BOX (statusbar),
+ statusbar->priv->cursor_position_statusbar,
+ FALSE, TRUE, 0);
+
+ statusbar->priv->state_frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (statusbar->priv->state_frame), GTK_SHADOW_IN);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (statusbar->priv->state_frame), hbox);
+
+ statusbar->priv->load_image = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
+ statusbar->priv->save_image = gtk_image_new_from_stock (GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
+ statusbar->priv->print_image = gtk_image_new_from_stock (GTK_STOCK_PRINT, GTK_ICON_SIZE_MENU);
+
+ gtk_widget_show (hbox);
+
+ gtk_box_pack_start (GTK_BOX (hbox),
+ statusbar->priv->load_image,
+ FALSE, TRUE, 4);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ statusbar->priv->save_image,
+ FALSE, TRUE, 4);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ statusbar->priv->print_image,
+ FALSE, TRUE, 4);
+
+ gtk_box_pack_start (GTK_BOX (statusbar),
+ statusbar->priv->state_frame,
+ FALSE, TRUE, 0);
+
+ statusbar->priv->error_frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (statusbar->priv->error_frame), GTK_SHADOW_IN);
+
+ error_image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU);
+ gtk_misc_set_padding (GTK_MISC (error_image), 4, 0);
+ gtk_widget_show (error_image);
+
+ statusbar->priv->error_event_box = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (statusbar->priv->error_event_box),
+ FALSE);
+ gtk_widget_show (statusbar->priv->error_event_box);
+
+ gtk_container_add (GTK_CONTAINER (statusbar->priv->error_frame),
+ statusbar->priv->error_event_box);
+ gtk_container_add (GTK_CONTAINER (statusbar->priv->error_event_box),
+ error_image);
+
+ gtk_box_pack_start (GTK_BOX (statusbar),
+ statusbar->priv->error_frame,
+ FALSE, TRUE, 0);
+
+ gtk_box_reorder_child (GTK_BOX (statusbar),
+ statusbar->priv->error_frame,
+ 0);
+}
+
+/**
+ * pluma_statusbar_new:
+ *
+ * Creates a new #PlumaStatusbar.
+ *
+ * Return value: the new #PlumaStatusbar object
+ **/
+GtkWidget *
+pluma_statusbar_new (void)
+{
+ return GTK_WIDGET (g_object_new (PLUMA_TYPE_STATUSBAR, NULL));
+}
+
+/**
+ * pluma_set_has_resize_grip:
+ * @statusbar: a #PlumaStatusbar
+ * @show: if the resize grip is shown
+ *
+ * Sets if a resize grip showld be shown.
+ *
+ **/
+ /*
+ * I don't like this much, in a perfect world it would have been
+ * possible to override the parent property and use
+ * gtk_statusbar_set_has_resize_grip. Unfortunately this is not
+ * possible and it's not even possible to intercept the notify signal
+ * since the parent property should always be set to false thus when
+ * using set_resize_grip (FALSE) the property doesn't change and the
+ * notification is not emitted.
+ * For now just add this private method; if needed we can turn it into
+ * a property.
+ */
+void
+_pluma_statusbar_set_has_resize_grip (PlumaStatusbar *bar,
+ gboolean show)
+{
+ g_return_if_fail (PLUMA_IS_STATUSBAR (bar));
+
+ gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (bar->priv->overwrite_mode_statusbar),
+ show);
+}
+
+/**
+ * pluma_statusbar_set_overwrite:
+ * @statusbar: a #PlumaStatusbar
+ * @overwrite: if the overwrite mode is set
+ *
+ * Sets the overwrite mode on the statusbar.
+ **/
+void
+pluma_statusbar_set_overwrite (PlumaStatusbar *statusbar,
+ gboolean overwrite)
+{
+ gchar *msg;
+
+ g_return_if_fail (PLUMA_IS_STATUSBAR (statusbar));
+
+ gtk_statusbar_pop (GTK_STATUSBAR (statusbar->priv->overwrite_mode_statusbar), 0);
+
+ msg = get_overwrite_mode_string (overwrite);
+
+ gtk_statusbar_push (GTK_STATUSBAR (statusbar->priv->overwrite_mode_statusbar), 0, msg);
+
+ g_free (msg);
+}
+
+void
+pluma_statusbar_clear_overwrite (PlumaStatusbar *statusbar)
+{
+ g_return_if_fail (PLUMA_IS_STATUSBAR (statusbar));
+
+ gtk_statusbar_pop (GTK_STATUSBAR (statusbar->priv->overwrite_mode_statusbar), 0);
+}
+
+/**
+ * pluma_statusbar_cursor_position:
+ * @statusbar: an #PlumaStatusbar
+ * @line: line position
+ * @col: column position
+ *
+ * Sets the cursor position on the statusbar.
+ **/
+void
+pluma_statusbar_set_cursor_position (PlumaStatusbar *statusbar,
+ gint line,
+ gint col)
+{
+ gchar *msg;
+
+ g_return_if_fail (PLUMA_IS_STATUSBAR (statusbar));
+
+ gtk_statusbar_pop (GTK_STATUSBAR (statusbar->priv->cursor_position_statusbar), 0);
+
+ if ((line == -1) && (col == -1))
+ return;
+
+ /* Translators: "Ln" is an abbreviation for "Line", Col is an abbreviation for "Column". Please,
+ use abbreviations if possible to avoid space problems. */
+ msg = g_strdup_printf (_(" Ln %d, Col %d"), line, col);
+
+ gtk_statusbar_push (GTK_STATUSBAR (statusbar->priv->cursor_position_statusbar), 0, msg);
+
+ g_free (msg);
+}
+
+static gboolean
+remove_message_timeout (PlumaStatusbar *statusbar)
+{
+ gtk_statusbar_remove (GTK_STATUSBAR (statusbar),
+ statusbar->priv->flash_context_id,
+ statusbar->priv->flash_message_id);
+
+ /* remove the timeout */
+ statusbar->priv->flash_timeout = 0;
+ return FALSE;
+}
+
+/**
+ * pluma_statusbar_flash_message:
+ * @statusbar: a #PlumaStatusbar
+ * @context_id: message context_id
+ * @format: message to flash on the statusbar
+ *
+ * Flash a temporary message on the statusbar.
+ */
+void
+pluma_statusbar_flash_message (PlumaStatusbar *statusbar,
+ guint context_id,
+ const gchar *format, ...)
+{
+ const guint32 flash_length = 3000; /* three seconds */
+ va_list args;
+ gchar *msg;
+
+ g_return_if_fail (PLUMA_IS_STATUSBAR (statusbar));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ msg = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ /* remove a currently ongoing flash message */
+ if (statusbar->priv->flash_timeout > 0)
+ {
+ g_source_remove (statusbar->priv->flash_timeout);
+ statusbar->priv->flash_timeout = 0;
+
+ gtk_statusbar_remove (GTK_STATUSBAR (statusbar),
+ statusbar->priv->flash_context_id,
+ statusbar->priv->flash_message_id);
+ }
+
+ statusbar->priv->flash_context_id = context_id;
+ statusbar->priv->flash_message_id = gtk_statusbar_push (GTK_STATUSBAR (statusbar),
+ context_id,
+ msg);
+
+ statusbar->priv->flash_timeout = g_timeout_add (flash_length,
+ (GtkFunction) remove_message_timeout,
+ statusbar);
+
+ g_free (msg);
+}
+
+void
+pluma_statusbar_set_window_state (PlumaStatusbar *statusbar,
+ PlumaWindowState state,
+ gint num_of_errors)
+{
+ g_return_if_fail (PLUMA_IS_STATUSBAR (statusbar));
+
+ gtk_widget_hide (statusbar->priv->state_frame);
+ gtk_widget_hide (statusbar->priv->save_image);
+ gtk_widget_hide (statusbar->priv->load_image);
+ gtk_widget_hide (statusbar->priv->print_image);
+
+ if (state & PLUMA_WINDOW_STATE_SAVING)
+ {
+ gtk_widget_show (statusbar->priv->state_frame);
+ gtk_widget_show (statusbar->priv->save_image);
+ }
+ if (state & PLUMA_WINDOW_STATE_LOADING)
+ {
+ gtk_widget_show (statusbar->priv->state_frame);
+ gtk_widget_show (statusbar->priv->load_image);
+ }
+
+ if (state & PLUMA_WINDOW_STATE_PRINTING)
+ {
+ gtk_widget_show (statusbar->priv->state_frame);
+ gtk_widget_show (statusbar->priv->print_image);
+ }
+
+ if (state & PLUMA_WINDOW_STATE_ERROR)
+ {
+ gchar *tip;
+
+ tip = g_strdup_printf (ngettext("There is a tab with errors",
+ "There are %d tabs with errors",
+ num_of_errors),
+ num_of_errors);
+
+ gtk_widget_set_tooltip_text (statusbar->priv->error_event_box,
+ tip);
+ g_free (tip);
+
+ gtk_widget_show (statusbar->priv->error_frame);
+ }
+ else
+ {
+ gtk_widget_hide (statusbar->priv->error_frame);
+ }
+}
+
+
diff --git a/pluma/pluma-statusbar.h b/pluma/pluma-statusbar.h
new file mode 100755
index 00000000..740c1d54
--- /dev/null
+++ b/pluma/pluma-statusbar.h
@@ -0,0 +1,99 @@
+/*
+ * pluma-statusbar.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Borelli
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef PLUMA_STATUSBAR_H
+#define PLUMA_STATUSBAR_H
+
+#include <gtk/gtk.h>
+#include <pluma/pluma-window.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_STATUSBAR (pluma_statusbar_get_type ())
+#define PLUMA_STATUSBAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), PLUMA_TYPE_STATUSBAR, PlumaStatusbar))
+#define PLUMA_STATUSBAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), PLUMA_TYPE_STATUSBAR, PlumaStatusbarClass))
+#define PLUMA_IS_STATUSBAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), PLUMA_TYPE_STATUSBAR))
+#define PLUMA_IS_STATUSBAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), PLUMA_TYPE_STATUSBAR))
+#define PLUMA_STATUSBAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), PLUMA_TYPE_STATUSBAR, PlumaStatusbarClass))
+
+typedef struct _PlumaStatusbar PlumaStatusbar;
+typedef struct _PlumaStatusbarPrivate PlumaStatusbarPrivate;
+typedef struct _PlumaStatusbarClass PlumaStatusbarClass;
+
+struct _PlumaStatusbar
+{
+ GtkStatusbar parent;
+
+ /* <private/> */
+ PlumaStatusbarPrivate *priv;
+};
+
+struct _PlumaStatusbarClass
+{
+ GtkStatusbarClass parent_class;
+};
+
+GType pluma_statusbar_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_statusbar_new (void);
+
+/* FIXME: status is not defined in any .h */
+#define PlumaStatus gint
+void pluma_statusbar_set_window_state (PlumaStatusbar *statusbar,
+ PlumaWindowState state,
+ gint num_of_errors);
+
+void pluma_statusbar_set_overwrite (PlumaStatusbar *statusbar,
+ gboolean overwrite);
+
+void pluma_statusbar_set_cursor_position (PlumaStatusbar *statusbar,
+ gint line,
+ gint col);
+
+void pluma_statusbar_clear_overwrite (PlumaStatusbar *statusbar);
+
+void pluma_statusbar_flash_message (PlumaStatusbar *statusbar,
+ guint context_id,
+ const gchar *format,
+ ...) G_GNUC_PRINTF(3, 4);
+/* FIXME: these would be nice for plugins...
+void pluma_statusbar_add_widget (PlumaStatusbar *statusbar,
+ GtkWidget *widget);
+void pluma_statusbar_remove_widget (PlumaStatusbar *statusbar,
+ GtkWidget *widget);
+*/
+
+/*
+ * Non exported functions
+ */
+void _pluma_statusbar_set_has_resize_grip (PlumaStatusbar *statusbar,
+ gboolean show);
+
+G_END_DECLS
+
+#endif
diff --git a/pluma/pluma-style-scheme-manager.c b/pluma/pluma-style-scheme-manager.c
new file mode 100755
index 00000000..8bb91a6f
--- /dev/null
+++ b/pluma/pluma-style-scheme-manager.c
@@ -0,0 +1,364 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-source-style-manager.c
+ *
+ * Copyright (C) 2007 - Paolo Borelli and Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2007. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "pluma-style-scheme-manager.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-dirs.h"
+
+static GtkSourceStyleSchemeManager *style_scheme_manager = NULL;
+
+static gchar *
+get_pluma_styles_path (void)
+{
+ gchar *config_dir;
+ gchar *dir = NULL;
+
+ config_dir = pluma_dirs_get_user_config_dir ();
+
+ if (config_dir != NULL)
+ {
+ dir = g_build_filename (config_dir,
+ "styles",
+ NULL);
+ g_free (config_dir);
+ }
+
+ return dir;
+}
+
+static void
+add_pluma_styles_path (GtkSourceStyleSchemeManager *mgr)
+{
+ gchar *dir;
+
+ dir = get_pluma_styles_path();
+
+ if (dir != NULL)
+ {
+ gtk_source_style_scheme_manager_append_search_path (mgr, dir);
+ g_free (dir);
+ }
+}
+
+GtkSourceStyleSchemeManager *
+pluma_get_style_scheme_manager (void)
+{
+ if (style_scheme_manager == NULL)
+ {
+ style_scheme_manager = gtk_source_style_scheme_manager_new ();
+ add_pluma_styles_path (style_scheme_manager);
+ }
+
+ return style_scheme_manager;
+}
+
+static gint
+schemes_compare (gconstpointer a, gconstpointer b)
+{
+ GtkSourceStyleScheme *scheme_a = (GtkSourceStyleScheme *)a;
+ GtkSourceStyleScheme *scheme_b = (GtkSourceStyleScheme *)b;
+
+ const gchar *name_a = gtk_source_style_scheme_get_name (scheme_a);
+ const gchar *name_b = gtk_source_style_scheme_get_name (scheme_b);
+
+ return g_utf8_collate (name_a, name_b);
+}
+
+GSList *
+pluma_style_scheme_manager_list_schemes_sorted (GtkSourceStyleSchemeManager *manager)
+{
+ const gchar * const * scheme_ids;
+ GSList *schemes = NULL;
+
+ g_return_val_if_fail (GTK_IS_SOURCE_STYLE_SCHEME_MANAGER (manager), NULL);
+
+ scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
+
+ while (*scheme_ids != NULL)
+ {
+ GtkSourceStyleScheme *scheme;
+
+ scheme = gtk_source_style_scheme_manager_get_scheme (manager,
+ *scheme_ids);
+
+ schemes = g_slist_prepend (schemes, scheme);
+
+ ++scheme_ids;
+ }
+
+ if (schemes != NULL)
+ schemes = g_slist_sort (schemes, (GCompareFunc)schemes_compare);
+
+ return schemes;
+}
+
+gboolean
+_pluma_style_scheme_manager_scheme_is_pluma_user_scheme (GtkSourceStyleSchemeManager *manager,
+ const gchar *scheme_id)
+{
+ GtkSourceStyleScheme *scheme;
+ const gchar *filename;
+ gchar *dir;
+ gboolean res = FALSE;
+
+ scheme = gtk_source_style_scheme_manager_get_scheme (manager, scheme_id);
+ if (scheme == NULL)
+ return FALSE;
+
+ filename = gtk_source_style_scheme_get_filename (scheme);
+ if (filename == NULL)
+ return FALSE;
+
+ dir = get_pluma_styles_path ();
+
+ res = g_str_has_prefix (filename, dir);
+
+ g_free (dir);
+
+ return res;
+}
+
+/**
+ * file_copy:
+ * @name: a pointer to a %NULL-terminated string, that names
+ * the file to be copied, in the GLib file name encoding
+ * @dest_name: a pointer to a %NULL-terminated string, that is the
+ * name for the destination file, in the GLib file name encoding
+ * @error: return location for a #GError, or %NULL
+ *
+ * Copies file @name to @dest_name.
+ *
+ * If the call was successful, it returns %TRUE. If the call was not
+ * successful, it returns %FALSE and sets @error. The error domain
+ * is #G_FILE_ERROR. Possible error
+ * codes are those in the #GFileError enumeration.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise.
+ */
+static gboolean
+file_copy (const gchar *name,
+ const gchar *dest_name,
+ GError **error)
+{
+ gchar *contents;
+ gsize length;
+ gchar *dest_dir;
+
+ /* FIXME - Paolo (Aug. 13, 2007):
+ * Since the style scheme files are relatively small, we can implement
+ * file copy getting all the content of the source file in a buffer and
+ * then write the content to the destination file. In this way we
+ * can use the g_file_get_contents and g_file_set_contents and avoid to
+ * write custom code to copy the file (with sane error management).
+ * If needed we can improve this code later. */
+
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (dest_name != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ /* Note: we allow to copy a file to itself since this is not a problem
+ * in our use case */
+
+ /* Ensure the destination directory exists */
+ dest_dir = g_path_get_dirname (dest_name);
+
+ errno = 0;
+ if (g_mkdir_with_parents (dest_dir, 0755) != 0)
+ {
+ gint save_errno = errno;
+ gchar *display_filename = g_filename_display_name (dest_dir);
+
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (save_errno),
+ _("Directory '%s' could not be created: g_mkdir_with_parents() failed: %s"),
+ display_filename,
+ g_strerror (save_errno));
+
+ g_free (dest_dir);
+ g_free (display_filename);
+
+ return FALSE;
+ }
+
+ g_free (dest_dir);
+
+ if (!g_file_get_contents (name, &contents, &length, error))
+ return FALSE;
+
+ if (!g_file_set_contents (dest_name, contents, length, error))
+ return FALSE;
+
+ g_free (contents);
+
+ return TRUE;
+}
+
+/**
+ * _pluma_style_scheme_manager_install_scheme:
+ * @manager: a #GtkSourceStyleSchemeManager
+ * @fname: the file name of the style scheme to be installed
+ *
+ * Install a new user scheme.
+ * This function copies @fname in #PLUMA_STYLES_DIR and ask the style manager to
+ * recompute the list of available style schemes. It then checks if a style
+ * scheme with the right file name exists.
+ *
+ * If the call was succesful, it returns the id of the installed scheme
+ * otherwise %NULL.
+ *
+ * Return value: the id of the installed scheme, %NULL otherwise.
+ */
+const gchar *
+_pluma_style_scheme_manager_install_scheme (GtkSourceStyleSchemeManager *manager,
+ const gchar *fname)
+{
+ gchar *new_file_name = NULL;
+ gchar *dirname;
+ gchar *styles_dir;
+ GError *error = NULL;
+ gboolean copied = FALSE;
+
+ const gchar* const *ids;
+
+ g_return_val_if_fail (GTK_IS_SOURCE_STYLE_SCHEME_MANAGER (manager), NULL);
+ g_return_val_if_fail (fname != NULL, NULL);
+
+ dirname = g_path_get_dirname (fname);
+ styles_dir = get_pluma_styles_path();
+
+ if (strcmp (dirname, styles_dir) != 0)
+ {
+ gchar *basename;
+
+ basename = g_path_get_basename (fname);
+ new_file_name = g_build_filename (styles_dir, basename, NULL);
+ g_free (basename);
+
+ /* Copy the style scheme file into PLUMA_STYLES_DIR */
+ if (!file_copy (fname, new_file_name, &error))
+ {
+ g_free (new_file_name);
+
+ g_message ("Cannot install style scheme:\n%s",
+ error->message);
+
+ return NULL;
+ }
+
+ copied = TRUE;
+ }
+ else
+ {
+ new_file_name = g_strdup (fname);
+ }
+
+ g_free (dirname);
+ g_free (styles_dir);
+
+ /* Reload the available style schemes */
+ gtk_source_style_scheme_manager_force_rescan (manager);
+
+ /* Check the new style scheme has been actually installed */
+ ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
+
+ while (*ids != NULL)
+ {
+ GtkSourceStyleScheme *scheme;
+ const gchar *filename;
+
+ scheme = gtk_source_style_scheme_manager_get_scheme (
+ pluma_get_style_scheme_manager (), *ids);
+
+ filename = gtk_source_style_scheme_get_filename (scheme);
+
+ if (filename && (strcmp (filename, new_file_name) == 0))
+ {
+ /* The style scheme has been correctly installed */
+ g_free (new_file_name);
+
+ return gtk_source_style_scheme_get_id (scheme);
+ }
+ ++ids;
+ }
+
+ /* The style scheme has not been correctly installed */
+ if (copied)
+ g_unlink (new_file_name);
+
+ g_free (new_file_name);
+
+ return NULL;
+}
+
+/**
+ * _pluma_style_scheme_manager_uninstall_scheme:
+ * @manager: a #GtkSourceStyleSchemeManager
+ * @id: the id of the style scheme to be uninstalled
+ *
+ * Uninstall a user scheme.
+ *
+ * If the call was succesful, it returns %TRUE
+ * otherwise %FALSE.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise.
+ */
+gboolean
+_pluma_style_scheme_manager_uninstall_scheme (GtkSourceStyleSchemeManager *manager,
+ const gchar *id)
+{
+ GtkSourceStyleScheme *scheme;
+ const gchar *filename;
+
+ g_return_val_if_fail (GTK_IS_SOURCE_STYLE_SCHEME_MANAGER (manager), FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+
+ scheme = gtk_source_style_scheme_manager_get_scheme (manager, id);
+ if (scheme == NULL)
+ return FALSE;
+
+ filename = gtk_source_style_scheme_get_filename (scheme);
+ if (filename == NULL)
+ return FALSE;
+
+ if (g_unlink (filename) == -1)
+ return FALSE;
+
+ /* Reload the available style schemes */
+ gtk_source_style_scheme_manager_force_rescan (manager);
+
+ return TRUE;
+}
diff --git a/pluma/pluma-style-scheme-manager.h b/pluma/pluma-style-scheme-manager.h
new file mode 100755
index 00000000..17309358
--- /dev/null
+++ b/pluma/pluma-style-scheme-manager.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pluma-style-scheme-manager.h
+ *
+ * Copyright (C) 2007 - Paolo Borelli
+ *
+ * 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.
+ *
+ * $Id: pluma-source-style-manager.h 5598 2007-04-15 13:16:24Z pborelli $
+ */
+
+#ifndef __PLUMA_STYLE_SCHEME_MANAGER_H__
+#define __PLUMA_STYLE_SCHEME_MANAGER_H__
+
+#include <gtksourceview/gtksourcestyleschememanager.h>
+
+G_BEGIN_DECLS
+
+GtkSourceStyleSchemeManager *
+ pluma_get_style_scheme_manager (void);
+
+/* Returns a sorted list of style schemes */
+GSList *pluma_style_scheme_manager_list_schemes_sorted
+ (GtkSourceStyleSchemeManager *manager);
+
+/*
+ * Non exported functions
+ */
+gboolean _pluma_style_scheme_manager_scheme_is_pluma_user_scheme
+ (GtkSourceStyleSchemeManager *manager,
+ const gchar *scheme_id);
+
+const gchar *_pluma_style_scheme_manager_install_scheme
+ (GtkSourceStyleSchemeManager *manager,
+ const gchar *fname);
+
+gboolean _pluma_style_scheme_manager_uninstall_scheme
+ (GtkSourceStyleSchemeManager *manager,
+ const gchar *id);
+
+G_END_DECLS
+
+#endif /* __PLUMA_STYLE_SCHEME_MANAGER_H__ */
diff --git a/pluma/pluma-tab-label.c b/pluma/pluma-tab-label.c
new file mode 100755
index 00000000..6947c1be
--- /dev/null
+++ b/pluma/pluma-tab-label.c
@@ -0,0 +1,371 @@
+/*
+ * pluma-tab-label.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Paolo Borelli
+ *
+ * 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 <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "pluma-tab-label.h"
+#include "pluma-close-button.h"
+
+#ifdef BUILD_SPINNER
+#include "pluma-spinner.h"
+#endif
+
+#define PLUMA_TAB_LABEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), PLUMA_TYPE_TAB_LABEL, PlumaTabLabelPrivate))
+
+/* Signals */
+enum
+{
+ CLOSE_CLICKED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_TAB
+};
+
+struct _PlumaTabLabelPrivate
+{
+ PlumaTab *tab;
+
+ GtkWidget *ebox;
+ GtkWidget *close_button;
+ GtkWidget *spinner;
+ GtkWidget *icon;
+ GtkWidget *label;
+
+ gboolean close_button_sensitive;
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (PlumaTabLabel, pluma_tab_label, GTK_TYPE_HBOX)
+
+static void
+pluma_tab_label_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (pluma_tab_label_parent_class)->finalize (object);
+}
+
+static void
+pluma_tab_label_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaTabLabel *tab_label = PLUMA_TAB_LABEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_TAB:
+ tab_label->priv->tab = PLUMA_TAB (g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_tab_label_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaTabLabel *tab_label = PLUMA_TAB_LABEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_TAB:
+ g_value_set_object (value, tab_label->priv->tab);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+close_button_clicked_cb (GtkWidget *widget,
+ PlumaTabLabel *tab_label)
+{
+ g_signal_emit (tab_label, signals[CLOSE_CLICKED], 0, NULL);
+}
+
+static void
+sync_tip (PlumaTab *tab, PlumaTabLabel *tab_label)
+{
+ gchar *str;
+
+ str = _pluma_tab_get_tooltips (tab);
+ g_return_if_fail (str != NULL);
+
+ gtk_widget_set_tooltip_markup (tab_label->priv->ebox, str);
+ g_free (str);
+}
+
+static void
+sync_name (PlumaTab *tab, GParamSpec *pspec, PlumaTabLabel *tab_label)
+{
+ gchar *str;
+
+ g_return_if_fail (tab == tab_label->priv->tab);
+
+ str = _pluma_tab_get_name (tab);
+ g_return_if_fail (str != NULL);
+
+ gtk_label_set_text (GTK_LABEL (tab_label->priv->label), str);
+ g_free (str);
+
+ sync_tip (tab, tab_label);
+}
+
+static void
+sync_state (PlumaTab *tab, GParamSpec *pspec, PlumaTabLabel *tab_label)
+{
+ PlumaTabState state;
+
+ g_return_if_fail (tab == tab_label->priv->tab);
+
+ state = pluma_tab_get_state (tab);
+
+ gtk_widget_set_sensitive (tab_label->priv->close_button,
+ tab_label->priv->close_button_sensitive &&
+ (state != PLUMA_TAB_STATE_CLOSING) &&
+ (state != PLUMA_TAB_STATE_SAVING) &&
+ (state != PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW) &&
+ (state != PLUMA_TAB_STATE_SAVING_ERROR));
+
+ if ((state == PLUMA_TAB_STATE_LOADING) ||
+ (state == PLUMA_TAB_STATE_SAVING) ||
+ (state == PLUMA_TAB_STATE_REVERTING))
+ {
+ gtk_widget_hide (tab_label->priv->icon);
+
+ gtk_widget_show (tab_label->priv->spinner);
+#ifdef BUILD_SPINNER
+ pluma_spinner_start (PLUMA_SPINNER (tab_label->priv->spinner));
+#else
+ gtk_spinner_start (GTK_SPINNER (tab_label->priv->spinner));
+#endif
+ }
+ else
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = _pluma_tab_get_icon (tab);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (tab_label->priv->icon), pixbuf);
+
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+
+ gtk_widget_show (tab_label->priv->icon);
+
+ gtk_widget_hide (tab_label->priv->spinner);
+#ifdef BUILD_SPINNER
+ pluma_spinner_stop (PLUMA_SPINNER (tab_label->priv->spinner));
+#else
+ gtk_spinner_stop (GTK_SPINNER (tab_label->priv->spinner));
+#endif
+ }
+
+ /* sync tip since encoding is known only after load/save end */
+ sync_tip (tab, tab_label);
+}
+
+static void
+pluma_tab_label_constructed (GObject *object)
+{
+ PlumaTabLabel *tab_label = PLUMA_TAB_LABEL (object);
+
+ if (!tab_label->priv->tab)
+ {
+ g_critical ("The tab label was not properly constructed");
+ return;
+ }
+
+ sync_name (tab_label->priv->tab, NULL, tab_label);
+ sync_state (tab_label->priv->tab, NULL, tab_label);
+
+ g_signal_connect_object (tab_label->priv->tab,
+ "notify::name",
+ G_CALLBACK (sync_name),
+ tab_label,
+ 0);
+
+ g_signal_connect_object (tab_label->priv->tab,
+ "notify::state",
+ G_CALLBACK (sync_state),
+ tab_label,
+ 0);
+}
+
+static void
+pluma_tab_label_class_init (PlumaTabLabelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_tab_label_finalize;
+ object_class->set_property = pluma_tab_label_set_property;
+ object_class->get_property = pluma_tab_label_get_property;
+ object_class->constructed = pluma_tab_label_constructed;
+
+ signals[CLOSE_CLICKED] =
+ g_signal_new ("close-clicked",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PlumaTabLabelClass, close_clicked),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_object_class_install_property (object_class,
+ PROP_TAB,
+ g_param_spec_object ("tab",
+ "Tab",
+ "The PlumaTab",
+ PLUMA_TYPE_TAB,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (object_class, sizeof(PlumaTabLabelPrivate));
+}
+
+static void
+pluma_tab_label_init (PlumaTabLabel *tab_label)
+{
+ GtkWidget *ebox;
+ GtkWidget *hbox;
+ GtkWidget *close_button;
+ GtkWidget *spinner;
+ GtkWidget *icon;
+ GtkWidget *label;
+ GtkWidget *dummy_label;
+
+ tab_label->priv = PLUMA_TAB_LABEL_GET_PRIVATE (tab_label);
+
+ tab_label->priv->close_button_sensitive = TRUE;
+
+ ebox = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (ebox), FALSE);
+ gtk_box_pack_start (GTK_BOX (tab_label), ebox, TRUE, TRUE, 0);
+ tab_label->priv->ebox = ebox;
+
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_container_add (GTK_CONTAINER (ebox), hbox);
+
+ close_button = pluma_close_button_new ();
+ gtk_widget_set_tooltip_text (close_button, _("Close document"));
+ gtk_box_pack_start (GTK_BOX (tab_label), close_button, FALSE, FALSE, 0);
+ tab_label->priv->close_button = close_button;
+
+ g_signal_connect (close_button,
+ "clicked",
+ G_CALLBACK (close_button_clicked_cb),
+ tab_label);
+
+#ifdef BUILD_SPINNER
+ spinner = pluma_spinner_new ();
+ pluma_spinner_set_size (PLUMA_SPINNER (spinner), GTK_ICON_SIZE_MENU);
+#else
+ spinner = gtk_spinner_new ();
+#endif
+ gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+ tab_label->priv->spinner = spinner;
+
+ /* setup icon, empty by default */
+ icon = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+ tab_label->priv->icon = icon;
+
+ label = gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (label), 0, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ tab_label->priv->label = label;
+
+ dummy_label = gtk_label_new ("");
+ gtk_box_pack_start (GTK_BOX (hbox), dummy_label, TRUE, TRUE, 0);
+
+ gtk_widget_show (ebox);
+ gtk_widget_show (hbox);
+ gtk_widget_show (close_button);
+ gtk_widget_show (icon);
+ gtk_widget_show (label);
+ gtk_widget_show (dummy_label);
+}
+
+void
+pluma_tab_label_set_close_button_sensitive (PlumaTabLabel *tab_label,
+ gboolean sensitive)
+{
+ PlumaTabState state;
+
+ g_return_if_fail (PLUMA_IS_TAB_LABEL (tab_label));
+
+ sensitive = (sensitive != FALSE);
+
+ if (sensitive == tab_label->priv->close_button_sensitive)
+ return;
+
+ tab_label->priv->close_button_sensitive = sensitive;
+
+ state = pluma_tab_get_state (tab_label->priv->tab);
+
+ gtk_widget_set_sensitive (tab_label->priv->close_button,
+ tab_label->priv->close_button_sensitive &&
+ (state != PLUMA_TAB_STATE_CLOSING) &&
+ (state != PLUMA_TAB_STATE_SAVING) &&
+ (state != PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW) &&
+ (state != PLUMA_TAB_STATE_PRINTING) &&
+ (state != PLUMA_TAB_STATE_PRINT_PREVIEWING) &&
+ (state != PLUMA_TAB_STATE_SAVING_ERROR));
+}
+
+PlumaTab *
+pluma_tab_label_get_tab (PlumaTabLabel *tab_label)
+{
+ g_return_val_if_fail (PLUMA_IS_TAB_LABEL (tab_label), NULL);
+
+ return tab_label->priv->tab;
+}
+
+GtkWidget *
+pluma_tab_label_new (PlumaTab *tab)
+{
+ PlumaTabLabel *tab_label;
+
+ tab_label = g_object_new (PLUMA_TYPE_TAB_LABEL,
+ "homogeneous", FALSE,
+ "tab", tab,
+ NULL);
+
+ return GTK_WIDGET (tab_label);
+}
diff --git a/pluma/pluma-tab-label.h b/pluma/pluma-tab-label.h
new file mode 100755
index 00000000..ab74582a
--- /dev/null
+++ b/pluma/pluma-tab-label.h
@@ -0,0 +1,66 @@
+/*
+ * pluma-tab-label.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2010 - Paolo Borelli
+ *
+ * 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 __PLUMA_TAB_LABEL_H__
+#define __PLUMA_TAB_LABEL_H__
+
+#include <gtk/gtk.h>
+#include <pluma/pluma-tab.h>
+
+G_BEGIN_DECLS
+
+#define PLUMA_TYPE_TAB_LABEL (pluma_tab_label_get_type ())
+#define PLUMA_TAB_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_TAB_LABEL, PlumaTabLabel))
+#define PLUMA_TAB_LABEL_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLUMA_TYPE_TAB_LABEL, PlumaTabLabel const))
+#define PLUMA_TAB_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PLUMA_TYPE_TAB_LABEL, PlumaTabLabelClass))
+#define PLUMA_IS_TAB_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLUMA_TYPE_TAB_LABEL))
+#define PLUMA_IS_TAB_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_TAB_LABEL))
+#define PLUMA_TAB_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PLUMA_TYPE_TAB_LABEL, PlumaTabLabelClass))
+
+typedef struct _PlumaTabLabel PlumaTabLabel;
+typedef struct _PlumaTabLabelClass PlumaTabLabelClass;
+typedef struct _PlumaTabLabelPrivate PlumaTabLabelPrivate;
+
+struct _PlumaTabLabel {
+ GtkHBox parent;
+
+ PlumaTabLabelPrivate *priv;
+};
+
+struct _PlumaTabLabelClass {
+ GtkHBoxClass parent_class;
+
+ void (* close_clicked) (PlumaTabLabel *tab_label);
+};
+
+GType pluma_tab_label_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_tab_label_new (PlumaTab *tab);
+
+PlumaTab *pluma_tab_label_get_tab (PlumaTabLabel *tab_label);
+
+void pluma_tab_label_set_close_button_sensitive (PlumaTabLabel *tab_label,
+ gboolean sensitive);
+
+G_END_DECLS
+
+#endif /* __PLUMA_TAB_LABEL_H__ */
diff --git a/pluma/pluma-tab.c b/pluma/pluma-tab.c
new file mode 100755
index 00000000..6d5ab975
--- /dev/null
+++ b/pluma/pluma-tab.c
@@ -0,0 +1,2832 @@
+/*
+ * pluma-tab.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "pluma-app.h"
+#include "pluma-notebook.h"
+#include "pluma-tab.h"
+#include "pluma-utils.h"
+#include "pluma-io-error-message-area.h"
+#include "pluma-print-job.h"
+#include "pluma-print-preview.h"
+#include "pluma-progress-message-area.h"
+#include "pluma-debug.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-enum-types.h"
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+#include "pluma-message-area.h"
+#endif
+
+#define PLUMA_TAB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_TAB, PlumaTabPrivate))
+
+#define PLUMA_TAB_KEY "PLUMA_TAB_KEY"
+
+struct _PlumaTabPrivate
+{
+ PlumaTabState state;
+
+ GtkWidget *view;
+ GtkWidget *view_scrolled_window;
+
+ GtkWidget *message_area;
+ GtkWidget *print_preview;
+
+ PlumaPrintJob *print_job;
+
+ /* tmp data for saving */
+ gchar *tmp_save_uri;
+
+ /* tmp data for loading */
+ gint tmp_line_pos;
+ const PlumaEncoding *tmp_encoding;
+
+ GTimer *timer;
+ guint times_called;
+
+ PlumaDocumentSaveFlags save_flags;
+
+ gint auto_save_interval;
+ guint auto_save_timeout;
+
+ gint not_editable : 1;
+ gint auto_save : 1;
+
+ gint ask_if_externally_modified : 1;
+};
+
+G_DEFINE_TYPE(PlumaTab, pluma_tab, GTK_TYPE_VBOX)
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_STATE,
+ PROP_AUTO_SAVE,
+ PROP_AUTO_SAVE_INTERVAL
+};
+
+static gboolean pluma_tab_auto_save (PlumaTab *tab);
+
+static void
+install_auto_save_timeout (PlumaTab *tab)
+{
+ gint timeout;
+
+ pluma_debug (DEBUG_TAB);
+
+ g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+ g_return_if_fail (tab->priv->auto_save);
+ g_return_if_fail (tab->priv->auto_save_interval > 0);
+
+ g_return_if_fail (tab->priv->state != PLUMA_TAB_STATE_LOADING);
+ g_return_if_fail (tab->priv->state != PLUMA_TAB_STATE_SAVING);
+ g_return_if_fail (tab->priv->state != PLUMA_TAB_STATE_REVERTING);
+ g_return_if_fail (tab->priv->state != PLUMA_TAB_STATE_LOADING_ERROR);
+ g_return_if_fail (tab->priv->state != PLUMA_TAB_STATE_SAVING_ERROR);
+ g_return_if_fail (tab->priv->state != PLUMA_TAB_STATE_SAVING_ERROR);
+ g_return_if_fail (tab->priv->state != PLUMA_TAB_STATE_REVERTING_ERROR);
+
+ /* Add a new timeout */
+ timeout = g_timeout_add_seconds (tab->priv->auto_save_interval * 60,
+ (GSourceFunc) pluma_tab_auto_save,
+ tab);
+
+ tab->priv->auto_save_timeout = timeout;
+}
+
+static gboolean
+install_auto_save_timeout_if_needed (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+
+ pluma_debug (DEBUG_TAB);
+
+ g_return_val_if_fail (tab->priv->auto_save_timeout <= 0, FALSE);
+ g_return_val_if_fail ((tab->priv->state == PLUMA_TAB_STATE_NORMAL) ||
+ (tab->priv->state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW) ||
+ (tab->priv->state == PLUMA_TAB_STATE_CLOSING), FALSE);
+
+ if (tab->priv->state == PLUMA_TAB_STATE_CLOSING)
+ return FALSE;
+
+ doc = pluma_tab_get_document (tab);
+
+ if (tab->priv->auto_save &&
+ !pluma_document_is_untitled (doc) &&
+ !pluma_document_get_readonly (doc))
+ {
+ install_auto_save_timeout (tab);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+remove_auto_save_timeout (PlumaTab *tab)
+{
+ pluma_debug (DEBUG_TAB);
+
+ /* FIXME: check sugli stati */
+
+ g_return_if_fail (tab->priv->auto_save_timeout > 0);
+
+ g_source_remove (tab->priv->auto_save_timeout);
+ tab->priv->auto_save_timeout = 0;
+}
+
+static void
+pluma_tab_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaTab *tab = PLUMA_TAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_take_string (value,
+ _pluma_tab_get_name (tab));
+ break;
+ case PROP_STATE:
+ g_value_set_enum (value,
+ pluma_tab_get_state (tab));
+ break;
+ case PROP_AUTO_SAVE:
+ g_value_set_boolean (value,
+ pluma_tab_get_auto_save_enabled (tab));
+ break;
+ case PROP_AUTO_SAVE_INTERVAL:
+ g_value_set_int (value,
+ pluma_tab_get_auto_save_interval (tab));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_tab_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaTab *tab = PLUMA_TAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTO_SAVE:
+ pluma_tab_set_auto_save_enabled (tab,
+ g_value_get_boolean (value));
+ break;
+ case PROP_AUTO_SAVE_INTERVAL:
+ pluma_tab_set_auto_save_interval (tab,
+ g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pluma_tab_finalize (GObject *object)
+{
+ PlumaTab *tab = PLUMA_TAB (object);
+
+ if (tab->priv->timer != NULL)
+ g_timer_destroy (tab->priv->timer);
+
+ g_free (tab->priv->tmp_save_uri);
+
+ if (tab->priv->auto_save_timeout > 0)
+ remove_auto_save_timeout (tab);
+
+ G_OBJECT_CLASS (pluma_tab_parent_class)->finalize (object);
+}
+
+static void
+pluma_tab_class_init (PlumaTabClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_tab_finalize;
+ object_class->get_property = pluma_tab_get_property;
+ object_class->set_property = pluma_tab_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "The tab's name",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_STATE,
+ g_param_spec_enum ("state",
+ "State",
+ "The tab's state",
+ PLUMA_TYPE_TAB_STATE,
+ PLUMA_TAB_STATE_NORMAL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTO_SAVE,
+ g_param_spec_boolean ("autosave",
+ "Autosave",
+ "Autosave feature",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTO_SAVE_INTERVAL,
+ g_param_spec_int ("autosave-interval",
+ "AutosaveInterval",
+ "Time between two autosaves",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof (PlumaTabPrivate));
+}
+
+/**
+ * pluma_tab_get_state:
+ * @tab: a #PlumaTab
+ *
+ * Gets the #PlumaTabState of @tab.
+ *
+ * Returns: the #PlumaTabState of @tab
+ */
+PlumaTabState
+pluma_tab_get_state (PlumaTab *tab)
+{
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), PLUMA_TAB_STATE_NORMAL);
+
+ return tab->priv->state;
+}
+
+static void
+set_cursor_according_to_state (GtkTextView *view,
+ PlumaTabState state)
+{
+ GdkCursor *cursor;
+ GdkWindow *text_window;
+ GdkWindow *left_window;
+
+ text_window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_TEXT);
+ left_window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_LEFT);
+
+ if ((state == PLUMA_TAB_STATE_LOADING) ||
+ (state == PLUMA_TAB_STATE_REVERTING) ||
+ (state == PLUMA_TAB_STATE_SAVING) ||
+ (state == PLUMA_TAB_STATE_PRINTING) ||
+ (state == PLUMA_TAB_STATE_PRINT_PREVIEWING) ||
+ (state == PLUMA_TAB_STATE_CLOSING))
+ {
+ cursor = gdk_cursor_new_for_display (
+ gtk_widget_get_display (GTK_WIDGET (view)),
+ GDK_WATCH);
+
+ if (text_window != NULL)
+ gdk_window_set_cursor (text_window, cursor);
+ if (left_window != NULL)
+ gdk_window_set_cursor (left_window, cursor);
+
+ gdk_cursor_unref (cursor);
+ }
+ else
+ {
+ cursor = gdk_cursor_new_for_display (
+ gtk_widget_get_display (GTK_WIDGET (view)),
+ GDK_XTERM);
+
+ if (text_window != NULL)
+ gdk_window_set_cursor (text_window, cursor);
+ if (left_window != NULL)
+ gdk_window_set_cursor (left_window, NULL);
+
+ gdk_cursor_unref (cursor);
+ }
+}
+
+static void
+view_realized (GtkTextView *view,
+ PlumaTab *tab)
+{
+ set_cursor_according_to_state (view, tab->priv->state);
+}
+
+static void
+set_view_properties_according_to_state (PlumaTab *tab,
+ PlumaTabState state)
+{
+ gboolean val;
+
+ val = ((state == PLUMA_TAB_STATE_NORMAL) &&
+ (tab->priv->print_preview == NULL) &&
+ !tab->priv->not_editable);
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (tab->priv->view), val);
+
+ val = ((state != PLUMA_TAB_STATE_LOADING) &&
+ (state != PLUMA_TAB_STATE_CLOSING));
+ gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (tab->priv->view), val);
+
+ val = ((state != PLUMA_TAB_STATE_LOADING) &&
+ (state != PLUMA_TAB_STATE_CLOSING) &&
+ (pluma_prefs_manager_get_highlight_current_line ()));
+ gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (tab->priv->view), val);
+}
+
+static void
+pluma_tab_set_state (PlumaTab *tab,
+ PlumaTabState state)
+{
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail ((state >= 0) && (state < PLUMA_TAB_NUM_OF_STATES));
+
+ if (tab->priv->state == state)
+ return;
+
+ tab->priv->state = state;
+
+ set_view_properties_according_to_state (tab, state);
+
+ if ((state == PLUMA_TAB_STATE_LOADING_ERROR) || /* FIXME: add other states if needed */
+ (state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW))
+ {
+ gtk_widget_hide (tab->priv->view_scrolled_window);
+ }
+ else
+ {
+ if (tab->priv->print_preview == NULL)
+ gtk_widget_show (tab->priv->view_scrolled_window);
+ }
+
+ set_cursor_according_to_state (GTK_TEXT_VIEW (tab->priv->view),
+ state);
+
+ g_object_notify (G_OBJECT (tab), "state");
+}
+
+static void
+document_uri_notify_handler (PlumaDocument *document,
+ GParamSpec *pspec,
+ PlumaTab *tab)
+{
+ pluma_debug (DEBUG_TAB);
+
+ /* Notify the change in the URI */
+ g_object_notify (G_OBJECT (tab), "name");
+}
+
+static void
+document_shortname_notify_handler (PlumaDocument *document,
+ GParamSpec *pspec,
+ PlumaTab *tab)
+{
+ pluma_debug (DEBUG_TAB);
+
+ /* Notify the change in the shortname */
+ g_object_notify (G_OBJECT (tab), "name");
+}
+
+static void
+document_modified_changed (GtkTextBuffer *document,
+ PlumaTab *tab)
+{
+ g_object_notify (G_OBJECT (tab), "name");
+}
+
+static void
+set_message_area (PlumaTab *tab,
+ GtkWidget *message_area)
+{
+ if (tab->priv->message_area == message_area)
+ return;
+
+ if (tab->priv->message_area != NULL)
+ gtk_widget_destroy (tab->priv->message_area);
+
+ tab->priv->message_area = message_area;
+
+ if (message_area == NULL)
+ return;
+
+ gtk_box_pack_start (GTK_BOX (tab),
+ tab->priv->message_area,
+ FALSE,
+ FALSE,
+ 0);
+
+ g_object_add_weak_pointer (G_OBJECT (tab->priv->message_area),
+ (gpointer *)&tab->priv->message_area);
+}
+
+static void
+remove_tab (PlumaTab *tab)
+{
+ PlumaNotebook *notebook;
+
+ notebook = PLUMA_NOTEBOOK (gtk_widget_get_parent (GTK_WIDGET (tab)));
+
+ pluma_notebook_remove_tab (notebook, tab);
+}
+
+static void
+io_loading_error_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ PlumaView *view;
+ gchar *uri;
+ const PlumaEncoding *encoding;
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ view = pluma_tab_get_view (tab);
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ uri = pluma_document_get_uri (doc);
+ g_return_if_fail (uri != NULL);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_OK:
+ encoding = pluma_conversion_error_message_area_get_encoding (
+ GTK_WIDGET (message_area));
+
+ if (encoding != NULL)
+ {
+ tab->priv->tmp_encoding = encoding;
+ }
+
+ set_message_area (tab, NULL);
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_LOADING);
+
+ g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+
+ pluma_document_load (doc,
+ uri,
+ tab->priv->tmp_encoding,
+ tab->priv->tmp_line_pos,
+ FALSE);
+ break;
+ case GTK_RESPONSE_YES:
+ /* This means that we want to edit the document anyway */
+ set_message_area (tab, NULL);
+ _pluma_document_set_readonly (doc, FALSE);
+ break;
+ case GTK_RESPONSE_NO:
+ /* We don't want to edit the document just show it */
+ set_message_area (tab, NULL);
+ break;
+ default:
+ _pluma_recent_remove (PLUMA_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), uri);
+
+ remove_tab (tab);
+ break;
+ }
+
+ g_free (uri);
+}
+
+static void
+file_already_open_warning_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ PlumaView *view;
+
+ view = pluma_tab_get_view (tab);
+
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ tab->priv->not_editable = FALSE;
+
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (view),
+ TRUE);
+ }
+
+ gtk_widget_destroy (message_area);
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+load_cancelled (GtkWidget *area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area));
+
+ g_object_ref (tab);
+ pluma_document_load_cancel (pluma_tab_get_document (tab));
+ g_object_unref (tab);
+}
+
+static void
+unrecoverable_reverting_error_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ PlumaView *view;
+
+ pluma_tab_set_state (tab,
+ PLUMA_TAB_STATE_NORMAL);
+
+ set_message_area (tab, NULL);
+
+ view = pluma_tab_get_view (tab);
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+
+ install_auto_save_timeout_if_needed (tab);
+}
+
+#define MAX_MSG_LENGTH 100
+
+static void
+show_loading_message_area (PlumaTab *tab)
+{
+ GtkWidget *area;
+ PlumaDocument *doc = NULL;
+ gchar *name;
+ gchar *dirname = NULL;
+ gchar *msg = NULL;
+ gchar *name_markup;
+ gchar *dirname_markup;
+ gint len;
+
+ if (tab->priv->message_area != NULL)
+ return;
+
+ pluma_debug (DEBUG_TAB);
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (doc != NULL);
+
+ name = pluma_document_get_short_name_for_display (doc);
+ len = g_utf8_strlen (name, -1);
+
+ /* if the name is awfully long, truncate it and be done with it,
+ * otherwise also show the directory (ellipsized if needed)
+ */
+ if (len > MAX_MSG_LENGTH)
+ {
+ gchar *str;
+
+ str = pluma_utils_str_middle_truncate (name, MAX_MSG_LENGTH);
+ g_free (name);
+ name = str;
+ }
+ else
+ {
+ GFile *file;
+
+ file = pluma_document_get_location (doc);
+ if (file != NULL)
+ {
+ gchar *str;
+
+ str = pluma_utils_location_get_dirname_for_display (file);
+ g_object_unref (file);
+
+ /* use the remaining space for the dir, but use a min of 20 chars
+ * so that we do not end up with a dirname like "(a...b)".
+ * This means that in the worst case when the filename is long 99
+ * we have a title long 99 + 20, but I think it's a rare enough
+ * case to be acceptable. It's justa darn title afterall :)
+ */
+ dirname = pluma_utils_str_middle_truncate (str,
+ MAX (20, MAX_MSG_LENGTH - len));
+ g_free (str);
+ }
+ }
+
+ name_markup = g_markup_printf_escaped ("<b>%s</b>", name);
+
+ if (tab->priv->state == PLUMA_TAB_STATE_REVERTING)
+ {
+ if (dirname != NULL)
+ {
+ dirname_markup = g_markup_printf_escaped ("<b>%s</b>", dirname);
+
+ /* Translators: the first %s is a file name (e.g. test.txt) the second one
+ is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */
+ msg = g_strdup_printf (_("Reverting %s from %s"),
+ name_markup,
+ dirname_markup);
+ g_free (dirname_markup);
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Reverting %s"),
+ name_markup);
+ }
+
+ area = pluma_progress_message_area_new (GTK_STOCK_REVERT_TO_SAVED,
+ msg,
+ TRUE);
+ }
+ else
+ {
+ if (dirname != NULL)
+ {
+ dirname_markup = g_markup_printf_escaped ("<b>%s</b>", dirname);
+
+ /* Translators: the first %s is a file name (e.g. test.txt) the second one
+ is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */
+ msg = g_strdup_printf (_("Loading %s from %s"),
+ name_markup,
+ dirname_markup);
+ g_free (dirname_markup);
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Loading %s"),
+ name_markup);
+ }
+
+ area = pluma_progress_message_area_new (GTK_STOCK_OPEN,
+ msg,
+ TRUE);
+ }
+
+ g_signal_connect (area,
+ "response",
+ G_CALLBACK (load_cancelled),
+ tab);
+
+ gtk_widget_show (area);
+
+ set_message_area (tab, area);
+
+ g_free (msg);
+ g_free (name);
+ g_free (name_markup);
+ g_free (dirname);
+}
+
+static void
+show_saving_message_area (PlumaTab *tab)
+{
+ GtkWidget *area;
+ PlumaDocument *doc = NULL;
+ gchar *short_name;
+ gchar *from;
+ gchar *to = NULL;
+ gchar *from_markup;
+ gchar *to_markup;
+ gchar *msg = NULL;
+ gint len;
+
+ g_return_if_fail (tab->priv->tmp_save_uri != NULL);
+
+ if (tab->priv->message_area != NULL)
+ return;
+
+ pluma_debug (DEBUG_TAB);
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (doc != NULL);
+
+ short_name = pluma_document_get_short_name_for_display (doc);
+
+ len = g_utf8_strlen (short_name, -1);
+
+ /* if the name is awfully long, truncate it and be done with it,
+ * otherwise also show the directory (ellipsized if needed)
+ */
+ if (len > MAX_MSG_LENGTH)
+ {
+ from = pluma_utils_str_middle_truncate (short_name,
+ MAX_MSG_LENGTH);
+ g_free (short_name);
+ }
+ else
+ {
+ gchar *str;
+
+ from = short_name;
+
+ to = pluma_utils_uri_for_display (tab->priv->tmp_save_uri);
+
+ str = pluma_utils_str_middle_truncate (to,
+ MAX (20, MAX_MSG_LENGTH - len));
+ g_free (to);
+
+ to = str;
+ }
+
+ from_markup = g_markup_printf_escaped ("<b>%s</b>", from);
+
+ if (to != NULL)
+ {
+ to_markup = g_markup_printf_escaped ("<b>%s</b>", to);
+
+ /* Translators: the first %s is a file name (e.g. test.txt) the second one
+ is a directory (e.g. ssh://master.gnome.org/home/users/paolo) */
+ msg = g_strdup_printf (_("Saving %s to %s"),
+ from_markup,
+ to_markup);
+ g_free (to_markup);
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Saving %s"), from_markup);
+ }
+
+ area = pluma_progress_message_area_new (GTK_STOCK_SAVE,
+ msg,
+ FALSE);
+
+ gtk_widget_show (area);
+
+ set_message_area (tab, area);
+
+ g_free (msg);
+ g_free (to);
+ g_free (from);
+ g_free (from_markup);
+}
+
+static void
+message_area_set_progress (PlumaTab *tab,
+ goffset size,
+ goffset total_size)
+{
+ if (tab->priv->message_area == NULL)
+ return;
+
+ pluma_debug_message (DEBUG_TAB, "%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, size, total_size);
+
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area));
+
+ if (total_size == 0)
+ {
+ if (size != 0)
+ pluma_progress_message_area_pulse (
+ PLUMA_PROGRESS_MESSAGE_AREA (tab->priv->message_area));
+ else
+ pluma_progress_message_area_set_fraction (
+ PLUMA_PROGRESS_MESSAGE_AREA (tab->priv->message_area),
+ 0);
+ }
+ else
+ {
+ gdouble frac;
+
+ frac = (gdouble)size / (gdouble)total_size;
+
+ pluma_progress_message_area_set_fraction (
+ PLUMA_PROGRESS_MESSAGE_AREA (tab->priv->message_area),
+ frac);
+ }
+}
+
+static void
+document_loading (PlumaDocument *document,
+ goffset size,
+ goffset total_size,
+ PlumaTab *tab)
+{
+ gdouble et;
+ gdouble total_time;
+
+ g_return_if_fail ((tab->priv->state == PLUMA_TAB_STATE_LOADING) ||
+ (tab->priv->state == PLUMA_TAB_STATE_REVERTING));
+
+ pluma_debug_message (DEBUG_TAB, "%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, size, total_size);
+
+ if (tab->priv->timer == NULL)
+ {
+ g_return_if_fail (tab->priv->times_called == 0);
+ tab->priv->timer = g_timer_new ();
+ }
+
+ et = g_timer_elapsed (tab->priv->timer, NULL);
+
+ /* et : total_time = size : total_size */
+ total_time = (et * total_size) / size;
+
+ if ((total_time - et) > 3.0)
+ {
+ show_loading_message_area (tab);
+ }
+
+ message_area_set_progress (tab, size, total_size);
+}
+
+static gboolean
+remove_tab_idle (PlumaTab *tab)
+{
+ remove_tab (tab);
+
+ return FALSE;
+}
+
+static void
+document_loaded (PlumaDocument *document,
+ const GError *error,
+ PlumaTab *tab)
+{
+ GtkWidget *emsg;
+ GFile *location;
+ gchar *uri;
+ const PlumaEncoding *encoding;
+
+ g_return_if_fail ((tab->priv->state == PLUMA_TAB_STATE_LOADING) ||
+ (tab->priv->state == PLUMA_TAB_STATE_REVERTING));
+ g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+
+ if (tab->priv->timer != NULL)
+ {
+ g_timer_destroy (tab->priv->timer);
+ tab->priv->timer = NULL;
+ }
+ tab->priv->times_called = 0;
+
+ set_message_area (tab, NULL);
+
+ location = pluma_document_get_location (document);
+ uri = pluma_document_get_uri (document);
+
+ /* if the error is CONVERSION FALLBACK don't treat it as a normal error */
+ if (error != NULL &&
+ (error->domain != PLUMA_DOCUMENT_ERROR || error->code != PLUMA_DOCUMENT_ERROR_CONVERSION_FALLBACK))
+ {
+ if (tab->priv->state == PLUMA_TAB_STATE_LOADING)
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_LOADING_ERROR);
+ else
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_REVERTING_ERROR);
+
+ encoding = pluma_document_get_encoding (document);
+
+ if (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_CANCELLED)
+ {
+ /* remove the tab, but in an idle handler, since
+ * we are in the handler of doc loaded and we
+ * don't want doc and tab to be finalized now.
+ */
+ g_idle_add ((GSourceFunc) remove_tab_idle, tab);
+
+ goto end;
+ }
+ else
+ {
+ _pluma_recent_remove (PLUMA_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), uri);
+
+ if (tab->priv->state == PLUMA_TAB_STATE_LOADING_ERROR)
+ {
+ emsg = pluma_io_loading_error_message_area_new (uri,
+ tab->priv->tmp_encoding,
+ error);
+ g_signal_connect (emsg,
+ "response",
+ G_CALLBACK (io_loading_error_message_area_response),
+ tab);
+ }
+ else
+ {
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_REVERTING_ERROR);
+
+ emsg = pluma_unrecoverable_reverting_error_message_area_new (uri,
+ error);
+
+ g_signal_connect (emsg,
+ "response",
+ G_CALLBACK (unrecoverable_reverting_error_message_area_response),
+ tab);
+ }
+
+ set_message_area (tab, emsg);
+ }
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_set_default_response (PLUMA_MESSAGE_AREA (emsg),
+ GTK_RESPONSE_CANCEL);
+#else
+ gtk_info_bar_set_default_response (GTK_INFO_BAR (emsg),
+ GTK_RESPONSE_CANCEL);
+#endif
+
+ gtk_widget_show (emsg);
+
+ g_object_unref (location);
+ g_free (uri);
+
+ return;
+ }
+ else
+ {
+ gchar *mime;
+ GList *all_documents;
+ GList *l;
+
+ g_return_if_fail (uri != NULL);
+
+ mime = pluma_document_get_mime_type (document);
+ _pluma_recent_add (PLUMA_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))),
+ uri,
+ mime);
+ g_free (mime);
+
+ if (error &&
+ error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_CONVERSION_FALLBACK)
+ {
+ GtkWidget *emsg;
+
+ _pluma_document_set_readonly (document, TRUE);
+
+ emsg = pluma_io_loading_error_message_area_new (uri,
+ tab->priv->tmp_encoding,
+ error);
+
+ set_message_area (tab, emsg);
+
+ g_signal_connect (emsg,
+ "response",
+ G_CALLBACK (io_loading_error_message_area_response),
+ tab);
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_set_default_response (PLUMA_MESSAGE_AREA (emsg),
+ GTK_RESPONSE_CANCEL);
+#else
+ gtk_info_bar_set_default_response (GTK_INFO_BAR (emsg),
+ GTK_RESPONSE_CANCEL);
+#endif
+
+ gtk_widget_show (emsg);
+ }
+
+ /* Scroll to the cursor when the document is loaded */
+ pluma_view_scroll_to_cursor (PLUMA_VIEW (tab->priv->view));
+
+ all_documents = pluma_app_get_documents (pluma_app_get_default ());
+
+ for (l = all_documents; l != NULL; l = g_list_next (l))
+ {
+ PlumaDocument *d = PLUMA_DOCUMENT (l->data);
+
+ if (d != document)
+ {
+ GFile *loc;
+
+ loc = pluma_document_get_location (d);
+
+ if ((loc != NULL) &&
+ g_file_equal (location, loc))
+ {
+ GtkWidget *w;
+ PlumaView *view;
+
+ view = pluma_tab_get_view (tab);
+
+ tab->priv->not_editable = TRUE;
+
+ w = pluma_file_already_open_warning_message_area_new (uri);
+
+ set_message_area (tab, w);
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_set_default_response (PLUMA_MESSAGE_AREA (w),
+ GTK_RESPONSE_CANCEL);
+#else
+ gtk_info_bar_set_default_response (GTK_INFO_BAR (w),
+ GTK_RESPONSE_CANCEL);
+#endif
+
+ gtk_widget_show (w);
+
+ g_signal_connect (w,
+ "response",
+ G_CALLBACK (file_already_open_warning_message_area_response),
+ tab);
+
+ g_object_unref (loc);
+ break;
+ }
+
+ if (loc != NULL)
+ g_object_unref (loc);
+ }
+ }
+
+ g_list_free (all_documents);
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_NORMAL);
+
+ install_auto_save_timeout_if_needed (tab);
+
+ tab->priv->ask_if_externally_modified = TRUE;
+ }
+
+ end:
+ g_object_unref (location);
+ g_free (uri);
+
+ tab->priv->tmp_line_pos = 0;
+ tab->priv->tmp_encoding = NULL;
+}
+
+static void
+document_saving (PlumaDocument *document,
+ goffset size,
+ goffset total_size,
+ PlumaTab *tab)
+{
+ gdouble et;
+ gdouble total_time;
+
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_SAVING);
+
+ pluma_debug_message (DEBUG_TAB, "%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, size, total_size);
+
+
+ if (tab->priv->timer == NULL)
+ {
+ g_return_if_fail (tab->priv->times_called == 0);
+ tab->priv->timer = g_timer_new ();
+ }
+
+ et = g_timer_elapsed (tab->priv->timer, NULL);
+
+ /* et : total_time = size : total_size */
+ total_time = (et * total_size)/size;
+
+ if ((total_time - et) > 3.0)
+ {
+ show_saving_message_area (tab);
+ }
+
+ message_area_set_progress (tab, size, total_size);
+
+ tab->priv->times_called++;
+}
+
+static void
+end_saving (PlumaTab *tab)
+{
+ /* Reset tmp data for saving */
+ g_free (tab->priv->tmp_save_uri);
+ tab->priv->tmp_save_uri = NULL;
+ tab->priv->tmp_encoding = NULL;
+
+ install_auto_save_timeout_if_needed (tab);
+}
+
+static void
+unrecoverable_saving_error_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ PlumaView *view;
+
+ if (tab->priv->print_preview != NULL)
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW);
+ else
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_NORMAL);
+
+ end_saving (tab);
+
+ set_message_area (tab, NULL);
+
+ view = pluma_tab_get_view (tab);
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+no_backup_error_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ set_message_area (tab, NULL);
+
+ g_return_if_fail (tab->priv->tmp_save_uri != NULL);
+ g_return_if_fail (tab->priv->tmp_encoding != NULL);
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SAVING);
+
+ /* don't bug the user again with this... */
+ tab->priv->save_flags |= PLUMA_DOCUMENT_SAVE_IGNORE_BACKUP;
+
+ g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+
+ /* Force saving */
+ pluma_document_save (doc, tab->priv->save_flags);
+ }
+ else
+ {
+ unrecoverable_saving_error_message_area_response (message_area,
+ response_id,
+ tab);
+ }
+}
+
+static void
+externally_modified_error_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ set_message_area (tab, NULL);
+
+ g_return_if_fail (tab->priv->tmp_save_uri != NULL);
+ g_return_if_fail (tab->priv->tmp_encoding != NULL);
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SAVING);
+
+ g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+
+ /* ignore mtime should not be persisted in save flags across saves */
+
+ /* Force saving */
+ pluma_document_save (doc, tab->priv->save_flags | PLUMA_DOCUMENT_SAVE_IGNORE_MTIME);
+ }
+ else
+ {
+ unrecoverable_saving_error_message_area_response (message_area,
+ response_id,
+ tab);
+ }
+}
+
+static void
+recoverable_saving_error_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ const PlumaEncoding *encoding;
+
+ encoding = pluma_conversion_error_message_area_get_encoding (
+ GTK_WIDGET (message_area));
+
+ g_return_if_fail (encoding != NULL);
+
+ set_message_area (tab, NULL);
+
+ g_return_if_fail (tab->priv->tmp_save_uri != NULL);
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SAVING);
+
+ tab->priv->tmp_encoding = encoding;
+
+ pluma_debug_message (DEBUG_TAB, "Force saving with URI '%s'", tab->priv->tmp_save_uri);
+
+ g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+
+ pluma_document_save_as (doc,
+ tab->priv->tmp_save_uri,
+ tab->priv->tmp_encoding,
+ tab->priv->save_flags);
+ }
+ else
+ {
+ unrecoverable_saving_error_message_area_response (message_area,
+ response_id,
+ tab);
+ }
+}
+
+static void
+document_saved (PlumaDocument *document,
+ const GError *error,
+ PlumaTab *tab)
+{
+ GtkWidget *emsg;
+
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_SAVING);
+
+ g_return_if_fail (tab->priv->tmp_save_uri != NULL);
+ g_return_if_fail (tab->priv->tmp_encoding != NULL);
+ g_return_if_fail (tab->priv->auto_save_timeout <= 0);
+
+ g_timer_destroy (tab->priv->timer);
+ tab->priv->timer = NULL;
+ tab->priv->times_called = 0;
+
+ set_message_area (tab, NULL);
+
+ if (error != NULL)
+ {
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SAVING_ERROR);
+
+ if (error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_EXTERNALLY_MODIFIED)
+ {
+ /* This error is recoverable */
+ emsg = pluma_externally_modified_saving_error_message_area_new (
+ tab->priv->tmp_save_uri,
+ error);
+ g_return_if_fail (emsg != NULL);
+
+ set_message_area (tab, emsg);
+
+ g_signal_connect (emsg,
+ "response",
+ G_CALLBACK (externally_modified_error_message_area_response),
+ tab);
+ }
+ else if ((error->domain == PLUMA_DOCUMENT_ERROR &&
+ error->code == PLUMA_DOCUMENT_ERROR_CANT_CREATE_BACKUP) ||
+ (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_CANT_CREATE_BACKUP))
+ {
+ /* This error is recoverable */
+ emsg = pluma_no_backup_saving_error_message_area_new (
+ tab->priv->tmp_save_uri,
+ error);
+ g_return_if_fail (emsg != NULL);
+
+ set_message_area (tab, emsg);
+
+ g_signal_connect (emsg,
+ "response",
+ G_CALLBACK (no_backup_error_message_area_response),
+ tab);
+ }
+ else if (error->domain == PLUMA_DOCUMENT_ERROR ||
+ (error->domain == G_IO_ERROR &&
+ error->code != G_IO_ERROR_INVALID_DATA &&
+ error->code != G_IO_ERROR_PARTIAL_INPUT))
+ {
+ /* These errors are _NOT_ recoverable */
+ _pluma_recent_remove (PLUMA_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))),
+ tab->priv->tmp_save_uri);
+
+ emsg = pluma_unrecoverable_saving_error_message_area_new (tab->priv->tmp_save_uri,
+ error);
+ g_return_if_fail (emsg != NULL);
+
+ set_message_area (tab, emsg);
+
+ g_signal_connect (emsg,
+ "response",
+ G_CALLBACK (unrecoverable_saving_error_message_area_response),
+ tab);
+ }
+ else
+ {
+ /* This error is recoverable */
+ g_return_if_fail (error->domain == G_CONVERT_ERROR ||
+ error->domain == G_IO_ERROR);
+
+ emsg = pluma_conversion_error_while_saving_message_area_new (
+ tab->priv->tmp_save_uri,
+ tab->priv->tmp_encoding,
+ error);
+
+ set_message_area (tab, emsg);
+
+ g_signal_connect (emsg,
+ "response",
+ G_CALLBACK (recoverable_saving_error_message_area_response),
+ tab);
+ }
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ pluma_message_area_set_default_response (PLUMA_MESSAGE_AREA (emsg),
+ GTK_RESPONSE_CANCEL);
+#else
+ gtk_info_bar_set_default_response (GTK_INFO_BAR (emsg),
+ GTK_RESPONSE_CANCEL);
+#endif
+
+ gtk_widget_show (emsg);
+ }
+ else
+ {
+ gchar *mime = pluma_document_get_mime_type (document);
+
+ _pluma_recent_add (PLUMA_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))),
+ tab->priv->tmp_save_uri,
+ mime);
+ g_free (mime);
+
+ if (tab->priv->print_preview != NULL)
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW);
+ else
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_NORMAL);
+
+ tab->priv->ask_if_externally_modified = TRUE;
+
+ end_saving (tab);
+ }
+}
+
+static void
+externally_modified_notification_message_area_response (GtkWidget *message_area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ PlumaView *view;
+
+ set_message_area (tab, NULL);
+ view = pluma_tab_get_view (tab);
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ _pluma_tab_revert (tab);
+ }
+ else
+ {
+ tab->priv->ask_if_externally_modified = FALSE;
+
+ /* go back to normal state */
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_NORMAL);
+ }
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+display_externally_modified_notification (PlumaTab *tab)
+{
+ GtkWidget *message_area;
+ PlumaDocument *doc;
+ gchar *uri;
+ gboolean document_modified;
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ /* uri cannot be NULL, we're here because
+ * the file we're editing changed on disk */
+ uri = pluma_document_get_uri (doc);
+ g_return_if_fail (uri != NULL);
+
+ document_modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER(doc));
+ message_area = pluma_externally_modified_message_area_new (uri, document_modified);
+ g_free (uri);
+
+ tab->priv->message_area = NULL;
+ set_message_area (tab, message_area);
+ gtk_widget_show (message_area);
+
+ g_signal_connect (message_area,
+ "response",
+ G_CALLBACK (externally_modified_notification_message_area_response),
+ tab);
+}
+
+static gboolean
+view_focused_in (GtkWidget *widget,
+ GdkEventFocus *event,
+ PlumaTab *tab)
+{
+ PlumaDocument *doc;
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), FALSE);
+
+ /* we try to detect file changes only in the normal state */
+ if (tab->priv->state != PLUMA_TAB_STATE_NORMAL)
+ {
+ return FALSE;
+ }
+
+ /* we already asked, don't bug the user again */
+ if (!tab->priv->ask_if_externally_modified)
+ {
+ return FALSE;
+ }
+
+ doc = pluma_tab_get_document (tab);
+
+ /* If file was never saved or is remote we do not check */
+ if (!pluma_document_is_local (doc))
+ {
+ return FALSE;
+ }
+
+ if (_pluma_document_check_externally_modified (doc))
+ {
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION);
+
+ display_externally_modified_notification (tab);
+
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static GMountOperation *
+tab_mount_operation_factory (PlumaDocument *doc,
+ gpointer userdata)
+{
+ PlumaTab *tab = PLUMA_TAB (userdata);
+ GtkWidget *window;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (tab));
+ return gtk_mount_operation_new (GTK_WINDOW (window));
+}
+
+static void
+pluma_tab_init (PlumaTab *tab)
+{
+ GtkWidget *sw;
+ PlumaDocument *doc;
+ PlumaLockdownMask lockdown;
+
+ tab->priv = PLUMA_TAB_GET_PRIVATE (tab);
+
+ tab->priv->state = PLUMA_TAB_STATE_NORMAL;
+
+ tab->priv->not_editable = FALSE;
+
+ tab->priv->save_flags = 0;
+
+ tab->priv->ask_if_externally_modified = TRUE;
+
+ /* Create the scrolled window */
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ tab->priv->view_scrolled_window = sw;
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ /* Manage auto save data */
+ lockdown = pluma_app_get_lockdown (pluma_app_get_default ());
+ tab->priv->auto_save = pluma_prefs_manager_get_auto_save () &&
+ !(lockdown & PLUMA_LOCKDOWN_SAVE_TO_DISK);
+ tab->priv->auto_save = (tab->priv->auto_save != FALSE);
+
+ tab->priv->auto_save_interval = pluma_prefs_manager_get_auto_save_interval ();
+ if (tab->priv->auto_save_interval <= 0)
+ tab->priv->auto_save_interval = GPM_DEFAULT_AUTO_SAVE_INTERVAL;
+
+ /* Create the view */
+ doc = pluma_document_new ();
+ g_object_set_data (G_OBJECT (doc), PLUMA_TAB_KEY, tab);
+
+ _pluma_document_set_mount_operation_factory (doc,
+ tab_mount_operation_factory,
+ tab);
+
+ tab->priv->view = pluma_view_new (doc);
+ g_object_unref (doc);
+ gtk_widget_show (tab->priv->view);
+ g_object_set_data (G_OBJECT (tab->priv->view), PLUMA_TAB_KEY, tab);
+
+ gtk_box_pack_end (GTK_BOX (tab), sw, TRUE, TRUE, 0);
+ gtk_container_add (GTK_CONTAINER (sw), tab->priv->view);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_widget_show (sw);
+
+ g_signal_connect (doc,
+ "notify::uri",
+ G_CALLBACK (document_uri_notify_handler),
+ tab);
+ g_signal_connect (doc,
+ "notify::shortname",
+ G_CALLBACK (document_shortname_notify_handler),
+ tab);
+ g_signal_connect (doc,
+ "modified_changed",
+ G_CALLBACK (document_modified_changed),
+ tab);
+ g_signal_connect (doc,
+ "loading",
+ G_CALLBACK (document_loading),
+ tab);
+ g_signal_connect (doc,
+ "loaded",
+ G_CALLBACK (document_loaded),
+ tab);
+ g_signal_connect (doc,
+ "saving",
+ G_CALLBACK (document_saving),
+ tab);
+ g_signal_connect (doc,
+ "saved",
+ G_CALLBACK (document_saved),
+ tab);
+
+ g_signal_connect_after (tab->priv->view,
+ "focus-in-event",
+ G_CALLBACK (view_focused_in),
+ tab);
+
+ g_signal_connect_after (tab->priv->view,
+ "realize",
+ G_CALLBACK (view_realized),
+ tab);
+}
+
+GtkWidget *
+_pluma_tab_new (void)
+{
+ return GTK_WIDGET (g_object_new (PLUMA_TYPE_TAB, NULL));
+}
+
+/* Whether create is TRUE, creates a new empty document if location does
+ not refer to an existing file */
+GtkWidget *
+_pluma_tab_new_from_uri (const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create)
+{
+ PlumaTab *tab;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ tab = PLUMA_TAB (_pluma_tab_new ());
+
+ _pluma_tab_load (tab,
+ uri,
+ encoding,
+ line_pos,
+ create);
+
+ return GTK_WIDGET (tab);
+}
+
+/**
+ * pluma_tab_get_view:
+ * @tab: a #PlumaTab
+ *
+ * Gets the #PlumaView inside @tab.
+ *
+ * Returns: the #PlumaView inside @tab
+ */
+PlumaView *
+pluma_tab_get_view (PlumaTab *tab)
+{
+ return PLUMA_VIEW (tab->priv->view);
+}
+
+/**
+ * pluma_tab_get_document:
+ * @tab: a #PlumaTab
+ *
+ * Gets the #PlumaDocument associated to @tab.
+ *
+ * Returns: the #PlumaDocument associated to @tab
+ */
+PlumaDocument *
+pluma_tab_get_document (PlumaTab *tab)
+{
+ return PLUMA_DOCUMENT (gtk_text_view_get_buffer (
+ GTK_TEXT_VIEW (tab->priv->view)));
+}
+
+#define MAX_DOC_NAME_LENGTH 40
+
+gchar *
+_pluma_tab_get_name (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ gchar *name;
+ gchar *docname;
+ gchar *tab_name;
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), NULL);
+
+ doc = pluma_tab_get_document (tab);
+
+ name = pluma_document_get_short_name_for_display (doc);
+
+ /* Truncate the name so it doesn't get insanely wide. */
+ docname = pluma_utils_str_middle_truncate (name, MAX_DOC_NAME_LENGTH);
+
+ if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ {
+ tab_name = g_strdup_printf ("*%s", docname);
+ }
+ else
+ {
+ #if 0
+ if (pluma_document_get_readonly (doc))
+ {
+ tab_name = g_strdup_printf ("%s [%s]", docname,
+ /*Read only*/ _("RO"));
+ }
+ else
+ {
+ tab_name = g_strdup_printf ("%s", docname);
+ }
+#endif
+ tab_name = g_strdup (docname);
+ }
+
+ g_free (docname);
+ g_free (name);
+
+ return tab_name;
+}
+
+gchar *
+_pluma_tab_get_tooltips (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ gchar *tip;
+ gchar *uri;
+ gchar *ruri;
+ gchar *ruri_markup;
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), NULL);
+
+ doc = pluma_tab_get_document (tab);
+
+ uri = pluma_document_get_uri_for_display (doc);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ ruri = pluma_utils_replace_home_dir_with_tilde (uri);
+ g_free (uri);
+
+ ruri_markup = g_markup_printf_escaped ("<i>%s</i>", ruri);
+
+ switch (tab->priv->state)
+ {
+ gchar *content_type;
+ gchar *mime_type;
+ gchar *content_description;
+ gchar *content_full_description;
+ gchar *encoding;
+ const PlumaEncoding *enc;
+
+ case PLUMA_TAB_STATE_LOADING_ERROR:
+ tip = g_strdup_printf (_("Error opening file %s"),
+ ruri_markup);
+ break;
+
+ case PLUMA_TAB_STATE_REVERTING_ERROR:
+ tip = g_strdup_printf (_("Error reverting file %s"),
+ ruri_markup);
+ break;
+
+ case PLUMA_TAB_STATE_SAVING_ERROR:
+ tip = g_strdup_printf (_("Error saving file %s"),
+ ruri_markup);
+ break;
+ default:
+ content_type = pluma_document_get_content_type (doc);
+ mime_type = pluma_document_get_mime_type (doc);
+ content_description = g_content_type_get_description (content_type);
+
+ if (content_description == NULL)
+ content_full_description = g_strdup (mime_type);
+ else
+ content_full_description = g_strdup_printf ("%s (%s)",
+ content_description, mime_type);
+
+ g_free (content_type);
+ g_free (mime_type);
+ g_free (content_description);
+
+ enc = pluma_document_get_encoding (doc);
+
+ if (enc == NULL)
+ encoding = g_strdup (_("Unicode (UTF-8)"));
+ else
+ encoding = pluma_encoding_to_string (enc);
+
+ tip = g_markup_printf_escaped ("<b>%s</b> %s\n\n"
+ "<b>%s</b> %s\n"
+ "<b>%s</b> %s",
+ _("Name:"), ruri,
+ _("MIME Type:"), content_full_description,
+ _("Encoding:"), encoding);
+
+ g_free (encoding);
+ g_free (content_full_description);
+
+ break;
+ }
+
+ g_free (ruri);
+ g_free (ruri_markup);
+
+ return tip;
+}
+
+static GdkPixbuf *
+resize_icon (GdkPixbuf *pixbuf,
+ gint size)
+{
+ gint width, height;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ /* if the icon is larger than the nominal size, scale down */
+ if (MAX (width, height) > size)
+ {
+ GdkPixbuf *scaled_pixbuf;
+
+ if (width > height)
+ {
+ height = height * size / width;
+ width = size;
+ }
+ else
+ {
+ width = width * size / height;
+ height = size;
+ }
+
+ scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
+ width,
+ height,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scaled_pixbuf;
+ }
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+get_stock_icon (GtkIconTheme *theme,
+ const gchar *stock,
+ gint size)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gtk_icon_theme_load_icon (theme, stock, size, 0, NULL);
+ if (pixbuf == NULL)
+ return NULL;
+
+ return resize_icon (pixbuf, size);
+}
+
+static GdkPixbuf *
+get_icon (GtkIconTheme *theme,
+ GFile *location,
+ gint size)
+{
+ GdkPixbuf *pixbuf;
+ GtkIconInfo *icon_info;
+ GFileInfo *info;
+ GIcon *gicon;
+
+ if (location == NULL)
+ return get_stock_icon (theme, GTK_STOCK_FILE, size);
+
+ /* FIXME: Doing a sync stat is bad, this should be fixed */
+ info = g_file_query_info (location,
+ G_FILE_ATTRIBUTE_STANDARD_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (info == NULL)
+ return get_stock_icon (theme, GTK_STOCK_FILE, size);
+
+ gicon = g_file_info_get_icon (info);
+
+ if (gicon == NULL)
+ {
+ g_object_unref (info);
+ return get_stock_icon (theme, GTK_STOCK_FILE, size);
+ }
+
+ icon_info = gtk_icon_theme_lookup_by_gicon (theme, gicon, size, 0);
+ g_object_unref (info);
+
+ if (icon_info == NULL)
+ return get_stock_icon (theme, GTK_STOCK_FILE, size);
+
+ pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
+ gtk_icon_info_free (icon_info);
+
+ if (pixbuf == NULL)
+ return get_stock_icon (theme, GTK_STOCK_FILE, size);
+
+ return resize_icon (pixbuf, size);
+}
+
+/* FIXME: add support for theme changed. I think it should be as easy as
+ call g_object_notify (tab, "name") when the icon theme changes */
+GdkPixbuf *
+_pluma_tab_get_icon (PlumaTab *tab)
+{
+ GdkPixbuf *pixbuf;
+ GtkIconTheme *theme;
+ GdkScreen *screen;
+ gint icon_size;
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), NULL);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (tab));
+
+ theme = gtk_icon_theme_get_for_screen (screen);
+ g_return_val_if_fail (theme != NULL, NULL);
+
+ gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (GTK_WIDGET (tab)),
+ GTK_ICON_SIZE_MENU,
+ NULL,
+ &icon_size);
+
+ switch (tab->priv->state)
+ {
+ case PLUMA_TAB_STATE_LOADING:
+ pixbuf = get_stock_icon (theme,
+ GTK_STOCK_OPEN,
+ icon_size);
+ break;
+
+ case PLUMA_TAB_STATE_REVERTING:
+ pixbuf = get_stock_icon (theme,
+ GTK_STOCK_REVERT_TO_SAVED,
+ icon_size);
+ break;
+
+ case PLUMA_TAB_STATE_SAVING:
+ pixbuf = get_stock_icon (theme,
+ GTK_STOCK_SAVE,
+ icon_size);
+ break;
+
+ case PLUMA_TAB_STATE_PRINTING:
+ pixbuf = get_stock_icon (theme,
+ GTK_STOCK_PRINT,
+ icon_size);
+ break;
+
+ case PLUMA_TAB_STATE_PRINT_PREVIEWING:
+ case PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW:
+ pixbuf = get_stock_icon (theme,
+ GTK_STOCK_PRINT_PREVIEW,
+ icon_size);
+ break;
+
+ case PLUMA_TAB_STATE_LOADING_ERROR:
+ case PLUMA_TAB_STATE_REVERTING_ERROR:
+ case PLUMA_TAB_STATE_SAVING_ERROR:
+ case PLUMA_TAB_STATE_GENERIC_ERROR:
+ pixbuf = get_stock_icon (theme,
+ GTK_STOCK_DIALOG_ERROR,
+ icon_size);
+ break;
+
+ case PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION:
+ pixbuf = get_stock_icon (theme,
+ GTK_STOCK_DIALOG_WARNING,
+ icon_size);
+ break;
+
+ default:
+ {
+ GFile *location;
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+
+ location = pluma_document_get_location (doc);
+ pixbuf = get_icon (theme, location, icon_size);
+
+ if (location)
+ g_object_unref (location);
+ }
+ }
+
+ return pixbuf;
+}
+
+/**
+ * pluma_tab_get_from_document:
+ * @doc: a #PlumaDocument
+ *
+ * Gets the #PlumaTab associated with @doc.
+ *
+ * Returns: the #PlumaTab associated with @doc
+ */
+PlumaTab *
+pluma_tab_get_from_document (PlumaDocument *doc)
+{
+ gpointer res;
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ res = g_object_get_data (G_OBJECT (doc), PLUMA_TAB_KEY);
+
+ return (res != NULL) ? PLUMA_TAB (res) : NULL;
+}
+
+void
+_pluma_tab_load (PlumaTab *tab,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create)
+{
+ PlumaDocument *doc;
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_NORMAL);
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_LOADING);
+
+ tab->priv->tmp_line_pos = line_pos;
+ tab->priv->tmp_encoding = encoding;
+
+ if (tab->priv->auto_save_timeout > 0)
+ remove_auto_save_timeout (tab);
+
+ pluma_document_load (doc,
+ uri,
+ encoding,
+ line_pos,
+ create);
+}
+
+void
+_pluma_tab_revert (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ gchar *uri;
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail ((tab->priv->state == PLUMA_TAB_STATE_NORMAL) ||
+ (tab->priv->state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION));
+
+ if (tab->priv->state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
+ {
+ set_message_area (tab, NULL);
+ }
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_REVERTING);
+
+ uri = pluma_document_get_uri (doc);
+ g_return_if_fail (uri != NULL);
+
+ tab->priv->tmp_line_pos = 0;
+ tab->priv->tmp_encoding = pluma_document_get_encoding (doc);
+
+ if (tab->priv->auto_save_timeout > 0)
+ remove_auto_save_timeout (tab);
+
+ pluma_document_load (doc,
+ uri,
+ tab->priv->tmp_encoding,
+ 0,
+ FALSE);
+
+ g_free (uri);
+}
+
+void
+_pluma_tab_save (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ PlumaDocumentSaveFlags save_flags;
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail ((tab->priv->state == PLUMA_TAB_STATE_NORMAL) ||
+ (tab->priv->state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) ||
+ (tab->priv->state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW));
+ g_return_if_fail (tab->priv->tmp_save_uri == NULL);
+ g_return_if_fail (tab->priv->tmp_encoding == NULL);
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (!pluma_document_is_untitled (doc));
+
+ if (tab->priv->state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
+ {
+ /* We already told the user about the external
+ * modification: hide the message area and set
+ * the save flag.
+ */
+
+ set_message_area (tab, NULL);
+ save_flags = tab->priv->save_flags | PLUMA_DOCUMENT_SAVE_IGNORE_MTIME;
+ }
+ else
+ {
+ save_flags = tab->priv->save_flags;
+ }
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SAVING);
+
+ /* uri used in error messages, will be freed in document_saved */
+ tab->priv->tmp_save_uri = pluma_document_get_uri (doc);
+ tab->priv->tmp_encoding = pluma_document_get_encoding (doc);
+
+ if (tab->priv->auto_save_timeout > 0)
+ remove_auto_save_timeout (tab);
+
+ pluma_document_save (doc, save_flags);
+}
+
+static gboolean
+pluma_tab_auto_save (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+
+ pluma_debug (DEBUG_TAB);
+
+ g_return_val_if_fail (tab->priv->tmp_save_uri == NULL, FALSE);
+ g_return_val_if_fail (tab->priv->tmp_encoding == NULL, FALSE);
+
+ doc = pluma_tab_get_document (tab);
+
+ g_return_val_if_fail (!pluma_document_is_untitled (doc), FALSE);
+ g_return_val_if_fail (!pluma_document_get_readonly (doc), FALSE);
+
+ g_return_val_if_fail (tab->priv->auto_save_timeout > 0, FALSE);
+ g_return_val_if_fail (tab->priv->auto_save, FALSE);
+ g_return_val_if_fail (tab->priv->auto_save_interval > 0, FALSE);
+
+ if (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER(doc)))
+ {
+ pluma_debug_message (DEBUG_TAB, "Document not modified");
+
+ return TRUE;
+ }
+
+ if ((tab->priv->state != PLUMA_TAB_STATE_NORMAL) &&
+ (tab->priv->state != PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW))
+ {
+ /* Retry after 30 seconds */
+ guint timeout;
+
+ pluma_debug_message (DEBUG_TAB, "Retry after 30 seconds");
+
+ /* Add a new timeout */
+ timeout = g_timeout_add_seconds (30,
+ (GSourceFunc) pluma_tab_auto_save,
+ tab);
+
+ tab->priv->auto_save_timeout = timeout;
+
+ /* Returns FALSE so the old timeout is "destroyed" */
+ return FALSE;
+ }
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SAVING);
+
+ /* uri used in error messages, will be freed in document_saved */
+ tab->priv->tmp_save_uri = pluma_document_get_uri (doc);
+ tab->priv->tmp_encoding = pluma_document_get_encoding (doc);
+
+ /* Set auto_save_timeout to 0 since the timeout is going to be destroyed */
+ tab->priv->auto_save_timeout = 0;
+
+ /* Since we are autosaving, we need to preserve the backup that was produced
+ the last time the user "manually" saved the file. In the case a recoverable
+ error happens while saving, the last backup is not preserved since the user
+ expressed his willing of saving the file */
+ pluma_document_save (doc, tab->priv->save_flags | PLUMA_DOCUMENT_SAVE_PRESERVE_BACKUP);
+
+ pluma_debug_message (DEBUG_TAB, "Done");
+
+ /* Returns FALSE so the old timeout is "destroyed" */
+ return FALSE;
+}
+
+void
+_pluma_tab_save_as (PlumaTab *tab,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentNewlineType newline_type)
+{
+ PlumaDocument *doc;
+ PlumaDocumentSaveFlags save_flags;
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail ((tab->priv->state == PLUMA_TAB_STATE_NORMAL) ||
+ (tab->priv->state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) ||
+ (tab->priv->state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW));
+ g_return_if_fail (encoding != NULL);
+
+ g_return_if_fail (tab->priv->tmp_save_uri == NULL);
+ g_return_if_fail (tab->priv->tmp_encoding == NULL);
+
+ doc = pluma_tab_get_document (tab);
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+
+ /* reset the save flags, when saving as */
+ tab->priv->save_flags = 0;
+
+ if (tab->priv->state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
+ {
+ /* We already told the user about the external
+ * modification: hide the message area and set
+ * the save flag.
+ */
+
+ set_message_area (tab, NULL);
+ save_flags = tab->priv->save_flags | PLUMA_DOCUMENT_SAVE_IGNORE_MTIME;
+ }
+ else
+ {
+ save_flags = tab->priv->save_flags;
+ }
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SAVING);
+
+ /* uri used in error messages... strdup because errors are async
+ * and the string can go away, will be freed in document_saved */
+ tab->priv->tmp_save_uri = g_strdup (uri);
+ tab->priv->tmp_encoding = encoding;
+
+ if (tab->priv->auto_save_timeout > 0)
+ remove_auto_save_timeout (tab);
+
+ /* FIXME: this should behave the same as encoding, setting it here
+ makes it persistent (if save fails, it's remembered). It's not
+ a very big deal, but would be nice to have them follow the
+ same pattern. This can be changed once we break API for 3.0 */
+ pluma_document_set_newline_type (doc, newline_type);
+ pluma_document_save_as (doc, uri, encoding, tab->priv->save_flags);
+}
+
+#define PLUMA_PAGE_SETUP_KEY "pluma-page-setup-key"
+#define PLUMA_PRINT_SETTINGS_KEY "pluma-print-settings-key"
+
+static GtkPageSetup *
+get_page_setup (PlumaTab *tab)
+{
+ gpointer data;
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+
+ data = g_object_get_data (G_OBJECT (doc),
+ PLUMA_PAGE_SETUP_KEY);
+
+ if (data == NULL)
+ {
+ return _pluma_app_get_default_page_setup (pluma_app_get_default());
+ }
+ else
+ {
+ return gtk_page_setup_copy (GTK_PAGE_SETUP (data));
+ }
+}
+
+static GtkPrintSettings *
+get_print_settings (PlumaTab *tab)
+{
+ gpointer data;
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+
+ data = g_object_get_data (G_OBJECT (doc),
+ PLUMA_PRINT_SETTINGS_KEY);
+
+ if (data == NULL)
+ {
+ return _pluma_app_get_default_print_settings (pluma_app_get_default());
+ }
+ else
+ {
+ return gtk_print_settings_copy (GTK_PRINT_SETTINGS (data));
+ }
+}
+
+/* FIXME: show the message area only if the operation will be "long" */
+static void
+printing_cb (PlumaPrintJob *job,
+ PlumaPrintJobStatus status,
+ PlumaTab *tab)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area));
+
+ gtk_widget_show (tab->priv->message_area);
+
+ pluma_progress_message_area_set_text (PLUMA_PROGRESS_MESSAGE_AREA (tab->priv->message_area),
+ pluma_print_job_get_status_string (job));
+
+ pluma_progress_message_area_set_fraction (PLUMA_PROGRESS_MESSAGE_AREA (tab->priv->message_area),
+ pluma_print_job_get_progress (job));
+}
+
+static void
+store_print_settings (PlumaTab *tab,
+ PlumaPrintJob *job)
+{
+ PlumaDocument *doc;
+ GtkPrintSettings *settings;
+ GtkPageSetup *page_setup;
+
+ doc = pluma_tab_get_document (tab);
+
+ settings = pluma_print_job_get_print_settings (job);
+
+ /* clear n-copies settings since we do not want to
+ * persist that one */
+ gtk_print_settings_unset (settings,
+ GTK_PRINT_SETTINGS_N_COPIES);
+
+ /* remember settings for this document */
+ g_object_set_data_full (G_OBJECT (doc),
+ PLUMA_PRINT_SETTINGS_KEY,
+ g_object_ref (settings),
+ (GDestroyNotify)g_object_unref);
+
+ /* make them the default */
+ _pluma_app_set_default_print_settings (pluma_app_get_default (),
+ settings);
+
+ page_setup = pluma_print_job_get_page_setup (job);
+
+ /* remember page setup for this document */
+ g_object_set_data_full (G_OBJECT (doc),
+ PLUMA_PAGE_SETUP_KEY,
+ g_object_ref (page_setup),
+ (GDestroyNotify)g_object_unref);
+
+ /* make it the default */
+ _pluma_app_set_default_page_setup (pluma_app_get_default (),
+ page_setup);
+}
+
+static void
+done_printing_cb (PlumaPrintJob *job,
+ PlumaPrintJobResult result,
+ const GError *error,
+ PlumaTab *tab)
+{
+ PlumaView *view;
+
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_PRINT_PREVIEWING ||
+ tab->priv->state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW ||
+ tab->priv->state == PLUMA_TAB_STATE_PRINTING);
+
+ if (tab->priv->state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ /* print preview has been destroyed... */
+ tab->priv->print_preview = NULL;
+ }
+ else
+ {
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area));
+
+ set_message_area (tab, NULL); /* destroy the message area */
+ }
+
+ // TODO: check status and error
+
+ if (result == PLUMA_PRINT_JOB_RESULT_OK)
+ {
+ store_print_settings (tab, job);
+ }
+
+#if 0
+ if (tab->priv->print_preview != NULL)
+ {
+ /* If we were printing while showing the print preview,
+ see bug #352658 */
+ gtk_widget_destroy (tab->priv->print_preview);
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_PRINTING);
+ }
+#endif
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_NORMAL);
+
+ view = pluma_tab_get_view (tab);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+
+ g_object_unref (tab->priv->print_job);
+ tab->priv->print_job = NULL;
+}
+
+#if 0
+static void
+print_preview_destroyed (GtkWidget *preview,
+ PlumaTab *tab)
+{
+ tab->priv->print_preview = NULL;
+
+ if (tab->priv->state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ PlumaView *view;
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_NORMAL);
+
+ view = pluma_tab_get_view (tab);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+ }
+ else
+ {
+ /* This should happen only when printing while showing the print
+ * preview. In this case let us continue whithout changing
+ * the state and show the document. See bug #352658 */
+ gtk_widget_show (tab->priv->view_scrolled_window);
+
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_PRINTING);
+ }
+}
+#endif
+
+static void
+show_preview_cb (PlumaPrintJob *job,
+ PlumaPrintPreview *preview,
+ PlumaTab *tab)
+{
+// g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_PRINT_PREVIEWING);
+ g_return_if_fail (tab->priv->print_preview == NULL);
+
+ set_message_area (tab, NULL); /* destroy the message area */
+
+ tab->priv->print_preview = GTK_WIDGET (preview);
+ gtk_box_pack_end (GTK_BOX (tab),
+ tab->priv->print_preview,
+ TRUE,
+ TRUE,
+ 0);
+ gtk_widget_show (tab->priv->print_preview);
+ gtk_widget_grab_focus (tab->priv->print_preview);
+
+/* when the preview gets destroyed we get "done" signal
+ g_signal_connect (tab->priv->print_preview,
+ "destroy",
+ G_CALLBACK (print_preview_destroyed),
+ tab);
+*/
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW);
+}
+
+#if 0
+
+static void
+set_print_preview (PlumaTab *tab,
+ GtkWidget *print_preview)
+{
+ if (tab->priv->print_preview == print_preview)
+ return;
+
+ if (tab->priv->print_preview != NULL)
+ gtk_widget_destroy (tab->priv->print_preview);
+
+ tab->priv->print_preview = print_preview;
+
+ gtk_box_pack_end (GTK_BOX (tab),
+ tab->priv->print_preview,
+ TRUE,
+ TRUE,
+ 0);
+
+ gtk_widget_grab_focus (tab->priv->print_preview);
+
+ g_signal_connect (tab->priv->print_preview,
+ "destroy",
+ G_CALLBACK (print_preview_destroyed),
+ tab);
+}
+
+static void
+preview_finished_cb (GtkSourcePrintJob *pjob, PlumaTab *tab)
+{
+ MatePrintJob *gjob;
+ GtkWidget *preview = NULL;
+
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area));
+ set_message_area (tab, NULL); /* destroy the message area */
+
+ gjob = gtk_source_print_job_get_print_job (pjob);
+
+ preview = pluma_print_job_preview_new (gjob);
+ g_object_unref (gjob);
+
+ set_print_preview (tab, preview);
+
+ gtk_widget_show (preview);
+ g_object_unref (pjob);
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW);
+}
+
+
+#endif
+
+static void
+print_cancelled (GtkWidget *area,
+ gint response_id,
+ PlumaTab *tab)
+{
+ g_return_if_fail (PLUMA_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area));
+
+ pluma_print_job_cancel (tab->priv->print_job);
+
+ g_debug ("print_cancelled");
+}
+
+static void
+show_printing_message_area (PlumaTab *tab, gboolean preview)
+{
+ GtkWidget *area;
+
+ if (preview)
+ area = pluma_progress_message_area_new (GTK_STOCK_PRINT_PREVIEW,
+ "",
+ TRUE);
+ else
+ area = pluma_progress_message_area_new (GTK_STOCK_PRINT,
+ "",
+ TRUE);
+
+ g_signal_connect (area,
+ "response",
+ G_CALLBACK (print_cancelled),
+ tab);
+
+ set_message_area (tab, area);
+}
+
+#if !GTK_CHECK_VERSION (2, 17, 4)
+
+static void
+page_setup_done_cb (GtkPageSetup *setup,
+ PlumaTab *tab)
+{
+ if (setup != NULL)
+ {
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+
+ /* remember it for this document */
+ g_object_set_data_full (G_OBJECT (doc),
+ PLUMA_PAGE_SETUP_KEY,
+ g_object_ref (setup),
+ (GDestroyNotify)g_object_unref);
+
+ /* make it the default */
+ _pluma_app_set_default_page_setup (pluma_app_get_default(),
+ setup);
+ }
+}
+
+void
+_pluma_tab_page_setup (PlumaTab *tab)
+{
+ GtkPageSetup *setup;
+ GtkPrintSettings *settings;
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ setup = get_page_setup (tab);
+ settings = get_print_settings (tab);
+
+ gtk_print_run_page_setup_dialog_async (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))),
+ setup,
+ settings,
+ (GtkPageSetupDoneFunc) page_setup_done_cb,
+ tab);
+
+ /* CHECK: should we unref setup and settings? */
+}
+
+#endif
+
+static void
+pluma_tab_print_or_print_preview (PlumaTab *tab,
+ GtkPrintOperationAction print_action)
+{
+ PlumaView *view;
+ gboolean is_preview;
+ GtkPageSetup *setup;
+ GtkPrintSettings *settings;
+ GtkPrintOperationResult res;
+ GError *error = NULL;
+
+ g_return_if_fail (tab->priv->print_job == NULL);
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_NORMAL);
+
+ view = pluma_tab_get_view (tab);
+
+ is_preview = (print_action == GTK_PRINT_OPERATION_ACTION_PREVIEW);
+
+ tab->priv->print_job = pluma_print_job_new (view);
+ g_object_add_weak_pointer (G_OBJECT (tab->priv->print_job),
+ (gpointer *) &tab->priv->print_job);
+
+ show_printing_message_area (tab, is_preview);
+
+ g_signal_connect (tab->priv->print_job,
+ "printing",
+ G_CALLBACK (printing_cb),
+ tab);
+ g_signal_connect (tab->priv->print_job,
+ "show-preview",
+ G_CALLBACK (show_preview_cb),
+ tab);
+ g_signal_connect (tab->priv->print_job,
+ "done",
+ G_CALLBACK (done_printing_cb),
+ tab);
+
+ if (is_preview)
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_PRINT_PREVIEWING);
+ else
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_PRINTING);
+
+ setup = get_page_setup (tab);
+ settings = get_print_settings (tab);
+
+ res = pluma_print_job_print (tab->priv->print_job,
+ print_action,
+ setup,
+ settings,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))),
+ &error);
+
+ // TODO: manage res in the correct way
+ if (res == GTK_PRINT_OPERATION_RESULT_ERROR)
+ {
+ /* FIXME: go in error state */
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_NORMAL);
+ g_warning ("Async print preview failed (%s)", error->message);
+ g_object_unref (tab->priv->print_job);
+ g_error_free (error);
+ }
+}
+
+void
+_pluma_tab_print (PlumaTab *tab)
+{
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ /* FIXME: currently we can have just one printoperation going on
+ * at a given time, so before starting the print we close the preview.
+ * Would be nice to handle it properly though */
+ if (tab->priv->state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW)
+ {
+ gtk_widget_destroy (tab->priv->print_preview);
+ }
+
+ pluma_tab_print_or_print_preview (tab,
+ GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
+}
+
+void
+_pluma_tab_print_preview (PlumaTab *tab)
+{
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ pluma_tab_print_or_print_preview (tab,
+ GTK_PRINT_OPERATION_ACTION_PREVIEW);
+}
+
+void
+_pluma_tab_mark_for_closing (PlumaTab *tab)
+{
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail (tab->priv->state == PLUMA_TAB_STATE_NORMAL);
+
+ pluma_tab_set_state (tab, PLUMA_TAB_STATE_CLOSING);
+}
+
+gboolean
+_pluma_tab_can_close (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ PlumaTabState ts;
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), FALSE);
+
+ ts = pluma_tab_get_state (tab);
+
+ /* if we are loading or reverting, the tab can be closed */
+ if ((ts == PLUMA_TAB_STATE_LOADING) ||
+ (ts == PLUMA_TAB_STATE_LOADING_ERROR) ||
+ (ts == PLUMA_TAB_STATE_REVERTING) ||
+ (ts == PLUMA_TAB_STATE_REVERTING_ERROR)) /* CHECK: I'm not sure this is the right behavior for REVERTING ERROR */
+ return TRUE;
+
+ /* Do not close tab with saving errors */
+ if (ts == PLUMA_TAB_STATE_SAVING_ERROR)
+ return FALSE;
+
+ doc = pluma_tab_get_document (tab);
+
+ /* TODO: we need to save the file also if it has been externally
+ modified - Paolo (Oct 10, 2005) */
+
+ return (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)) &&
+ !pluma_document_get_deleted (doc));
+}
+
+/**
+ * pluma_tab_get_auto_save_enabled:
+ * @tab: a #PlumaTab
+ *
+ * Gets the current state for the autosave feature
+ *
+ * Return value: %TRUE if the autosave is enabled, else %FALSE
+ **/
+gboolean
+pluma_tab_get_auto_save_enabled (PlumaTab *tab)
+{
+ pluma_debug (DEBUG_TAB);
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), FALSE);
+
+ return tab->priv->auto_save;
+}
+
+/**
+ * pluma_tab_set_auto_save_enabled:
+ * @tab: a #PlumaTab
+ * @enable: enable (%TRUE) or disable (%FALSE) auto save
+ *
+ * Enables or disables the autosave feature. It does not install an
+ * autosave timeout if the document is new or is read-only
+ **/
+void
+pluma_tab_set_auto_save_enabled (PlumaTab *tab,
+ gboolean enable)
+{
+ PlumaDocument *doc = NULL;
+ PlumaLockdownMask lockdown;
+
+ pluma_debug (DEBUG_TAB);
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ /* Force disabling when lockdown is active */
+ lockdown = pluma_app_get_lockdown (pluma_app_get_default());
+ if (lockdown & PLUMA_LOCKDOWN_SAVE_TO_DISK)
+ enable = FALSE;
+
+ doc = pluma_tab_get_document (tab);
+
+ if (tab->priv->auto_save == enable)
+ return;
+
+ tab->priv->auto_save = enable;
+
+ if (enable &&
+ (tab->priv->auto_save_timeout <=0) &&
+ !pluma_document_is_untitled (doc) &&
+ !pluma_document_get_readonly (doc))
+ {
+ if ((tab->priv->state != PLUMA_TAB_STATE_LOADING) &&
+ (tab->priv->state != PLUMA_TAB_STATE_SAVING) &&
+ (tab->priv->state != PLUMA_TAB_STATE_REVERTING) &&
+ (tab->priv->state != PLUMA_TAB_STATE_LOADING_ERROR) &&
+ (tab->priv->state != PLUMA_TAB_STATE_SAVING_ERROR) &&
+ (tab->priv->state != PLUMA_TAB_STATE_REVERTING_ERROR))
+ {
+ install_auto_save_timeout (tab);
+ }
+ /* else: the timeout will be installed when loading/saving/reverting
+ will terminate */
+
+ return;
+ }
+
+ if (!enable && (tab->priv->auto_save_timeout > 0))
+ {
+ remove_auto_save_timeout (tab);
+
+ return;
+ }
+
+ g_return_if_fail ((!enable && (tab->priv->auto_save_timeout <= 0)) ||
+ pluma_document_is_untitled (doc) || pluma_document_get_readonly (doc));
+}
+
+/**
+ * pluma_tab_get_auto_save_interval:
+ * @tab: a #PlumaTab
+ *
+ * Gets the current interval for the autosaves
+ *
+ * Return value: the value of the autosave
+ **/
+gint
+pluma_tab_get_auto_save_interval (PlumaTab *tab)
+{
+ pluma_debug (DEBUG_TAB);
+
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), 0);
+
+ return tab->priv->auto_save_interval;
+}
+
+/**
+ * pluma_tab_set_auto_save_interval:
+ * @tab: a #PlumaTab
+ * @interval: the new interval
+ *
+ * Sets the interval for the autosave feature. It does nothing if the
+ * interval is the same as the one already present. It removes the old
+ * interval timeout and adds a new one with the autosave passed as
+ * argument.
+ **/
+void
+pluma_tab_set_auto_save_interval (PlumaTab *tab,
+ gint interval)
+{
+ PlumaDocument *doc = NULL;
+
+ pluma_debug (DEBUG_TAB);
+
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ doc = pluma_tab_get_document(tab);
+
+ g_return_if_fail (PLUMA_IS_DOCUMENT (doc));
+ g_return_if_fail (interval > 0);
+
+ if (tab->priv->auto_save_interval == interval)
+ return;
+
+ tab->priv->auto_save_interval = interval;
+
+ if (!tab->priv->auto_save)
+ return;
+
+ if (tab->priv->auto_save_timeout > 0)
+ {
+ g_return_if_fail (!pluma_document_is_untitled (doc));
+ g_return_if_fail (!pluma_document_get_readonly (doc));
+
+ remove_auto_save_timeout (tab);
+
+ install_auto_save_timeout (tab);
+ }
+}
+
+void
+pluma_tab_set_info_bar (PlumaTab *tab,
+ GtkWidget *info_bar)
+{
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail (info_bar == NULL || GTK_IS_WIDGET (info_bar));
+
+ /* FIXME: this can cause problems with the tab state machine */
+ set_message_area (tab, info_bar);
+}
diff --git a/pluma/pluma-tab.h b/pluma/pluma-tab.h
new file mode 100755
index 00000000..28b5f5f6
--- /dev/null
+++ b/pluma/pluma-tab.h
@@ -0,0 +1,165 @@
+/*
+ * pluma-tab.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_TAB_H__
+#define __PLUMA_TAB_H__
+
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-view.h>
+#include <pluma/pluma-document.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ PLUMA_TAB_STATE_NORMAL = 0,
+ PLUMA_TAB_STATE_LOADING,
+ PLUMA_TAB_STATE_REVERTING,
+ PLUMA_TAB_STATE_SAVING,
+ PLUMA_TAB_STATE_PRINTING,
+ PLUMA_TAB_STATE_PRINT_PREVIEWING,
+ PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW,
+ PLUMA_TAB_STATE_GENERIC_NOT_EDITABLE,
+ PLUMA_TAB_STATE_LOADING_ERROR,
+ PLUMA_TAB_STATE_REVERTING_ERROR,
+ PLUMA_TAB_STATE_SAVING_ERROR,
+ PLUMA_TAB_STATE_GENERIC_ERROR,
+ PLUMA_TAB_STATE_CLOSING,
+ PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION,
+ PLUMA_TAB_NUM_OF_STATES /* This is not a valid state */
+} PlumaTabState;
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_TAB (pluma_tab_get_type())
+#define PLUMA_TAB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_TAB, PlumaTab))
+#define PLUMA_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_TAB, PlumaTabClass))
+#define PLUMA_IS_TAB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_TAB))
+#define PLUMA_IS_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_TAB))
+#define PLUMA_TAB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_TAB, PlumaTabClass))
+
+/* Private structure type */
+typedef struct _PlumaTabPrivate PlumaTabPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaTab PlumaTab;
+
+struct _PlumaTab
+{
+ GtkVBox vbox;
+
+ /*< private > */
+ PlumaTabPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaTabClass PlumaTabClass;
+
+struct _PlumaTabClass
+{
+ GtkVBoxClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType pluma_tab_get_type (void) G_GNUC_CONST;
+
+PlumaView *pluma_tab_get_view (PlumaTab *tab);
+
+/* This is only an helper function */
+PlumaDocument *pluma_tab_get_document (PlumaTab *tab);
+
+PlumaTab *pluma_tab_get_from_document (PlumaDocument *doc);
+
+PlumaTabState pluma_tab_get_state (PlumaTab *tab);
+
+gboolean pluma_tab_get_auto_save_enabled
+ (PlumaTab *tab);
+
+void pluma_tab_set_auto_save_enabled
+ (PlumaTab *tab,
+ gboolean enable);
+
+gint pluma_tab_get_auto_save_interval
+ (PlumaTab *tab);
+
+void pluma_tab_set_auto_save_interval
+ (PlumaTab *tab,
+ gint interval);
+
+void pluma_tab_set_info_bar (PlumaTab *tab,
+ GtkWidget *info_bar);
+/*
+ * Non exported methods
+ */
+GtkWidget *_pluma_tab_new (void);
+
+/* Whether create is TRUE, creates a new empty document if location does
+ not refer to an existing file */
+GtkWidget *_pluma_tab_new_from_uri (const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create);
+gchar *_pluma_tab_get_name (PlumaTab *tab);
+gchar *_pluma_tab_get_tooltips (PlumaTab *tab);
+GdkPixbuf *_pluma_tab_get_icon (PlumaTab *tab);
+void _pluma_tab_load (PlumaTab *tab,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create);
+void _pluma_tab_revert (PlumaTab *tab);
+void _pluma_tab_save (PlumaTab *tab);
+void _pluma_tab_save_as (PlumaTab *tab,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ PlumaDocumentNewlineType newline_type);
+
+void _pluma_tab_print (PlumaTab *tab);
+void _pluma_tab_print_preview (PlumaTab *tab);
+
+void _pluma_tab_mark_for_closing (PlumaTab *tab);
+
+gboolean _pluma_tab_can_close (PlumaTab *tab);
+
+#if !GTK_CHECK_VERSION (2, 17, 4)
+void _pluma_tab_page_setup (PlumaTab *tab);
+#endif
+
+G_END_DECLS
+
+#endif /* __PLUMA_TAB_H__ */
diff --git a/pluma/pluma-ui.h b/pluma/pluma-ui.h
new file mode 100755
index 00000000..560e794e
--- /dev/null
+++ b/pluma/pluma-ui.h
@@ -0,0 +1,188 @@
+/*
+ * pluma-ui.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_UI_H__
+#define __PLUMA_UI_H__
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "pluma-commands.h"
+
+G_BEGIN_DECLS
+
+static const GtkActionEntry pluma_always_sensitive_menu_entries[] =
+{
+ /* Toplevel */
+ { "File", NULL, N_("_File") },
+ { "Edit", NULL, N_("_Edit") },
+ { "View", NULL, N_("_View") },
+ { "Search", NULL, N_("_Search") },
+ { "Tools", NULL, N_("_Tools") },
+ { "Documents", NULL, N_("_Documents") },
+ { "Help", NULL, N_("_Help") },
+
+ /* File menu */
+ { "FileNew", GTK_STOCK_NEW, NULL, "<control>N",
+ N_("Create a new document"), G_CALLBACK (_pluma_cmd_file_new) },
+ { "FileOpen", GTK_STOCK_OPEN, N_("_Open..."), "<control>O",
+ N_("Open a file"), G_CALLBACK (_pluma_cmd_file_open) },
+
+ /* Edit menu */
+ { "EditPreferences", GTK_STOCK_PREFERENCES, N_("Pr_eferences"), NULL,
+ N_("Configure the application"), G_CALLBACK (_pluma_cmd_edit_preferences) },
+
+ /* Help menu */
+ {"HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1",
+ N_("Open the pluma manual"), G_CALLBACK (_pluma_cmd_help_contents) },
+ { "HelpAbout", GTK_STOCK_ABOUT, NULL, NULL,
+ N_("About this application"), G_CALLBACK (_pluma_cmd_help_about) },
+
+ /* Fullscreen toolbar */
+ { "LeaveFullscreen", GTK_STOCK_LEAVE_FULLSCREEN, NULL,
+ NULL, N_("Leave fullscreen mode"),
+ G_CALLBACK (_pluma_cmd_view_leave_fullscreen_mode) }
+};
+
+static const GtkActionEntry pluma_menu_entries[] =
+{
+ /* File menu */
+ { "FileSave", GTK_STOCK_SAVE, NULL, "<control>S",
+ N_("Save the current file"), G_CALLBACK (_pluma_cmd_file_save) },
+ { "FileSaveAs", GTK_STOCK_SAVE_AS, N_("Save _As..."), "<shift><control>S",
+ N_("Save the current file with a different name"), G_CALLBACK (_pluma_cmd_file_save_as) },
+ { "FileRevert", GTK_STOCK_REVERT_TO_SAVED, NULL, NULL,
+ N_("Revert to a saved version of the file"), G_CALLBACK (_pluma_cmd_file_revert) },
+#if !GTK_CHECK_VERSION (2, 17, 4)
+ { "FilePageSetup", GTK_STOCK_PAGE_SETUP, N_("Page Set_up..."), NULL,
+ N_("Set up the page settings"), G_CALLBACK (_pluma_cmd_file_page_setup) },
+#endif
+ { "FilePrintPreview", GTK_STOCK_PRINT_PREVIEW, N_("Print Previe_w"),"<control><shift>P",
+ N_("Print preview"), G_CALLBACK (_pluma_cmd_file_print_preview) },
+ { "FilePrint", GTK_STOCK_PRINT, N_("_Print..."), "<control>P",
+ N_("Print the current page"), G_CALLBACK (_pluma_cmd_file_print) },
+
+ /* Edit menu */
+ { "EditUndo", GTK_STOCK_UNDO, NULL, "<control>Z",
+ N_("Undo the last action"), G_CALLBACK (_pluma_cmd_edit_undo) },
+ { "EditRedo", GTK_STOCK_REDO, NULL, "<shift><control>Z",
+ N_("Redo the last undone action"), G_CALLBACK (_pluma_cmd_edit_redo) },
+ { "EditCut", GTK_STOCK_CUT, NULL, "<control>X",
+ N_("Cut the selection"), G_CALLBACK (_pluma_cmd_edit_cut) },
+ { "EditCopy", GTK_STOCK_COPY, NULL, "<control>C",
+ N_("Copy the selection"), G_CALLBACK (_pluma_cmd_edit_copy) },
+ { "EditPaste", GTK_STOCK_PASTE, NULL, "<control>V",
+ N_("Paste the clipboard"), G_CALLBACK (_pluma_cmd_edit_paste) },
+ { "EditDelete", GTK_STOCK_DELETE, NULL, NULL,
+ N_("Delete the selected text"), G_CALLBACK (_pluma_cmd_edit_delete) },
+ { "EditSelectAll", GTK_STOCK_SELECT_ALL, N_("Select _All"), "<control>A",
+ N_("Select the entire document"), G_CALLBACK (_pluma_cmd_edit_select_all) },
+
+ /* View menu */
+ { "ViewHighlightMode", NULL, N_("_Highlight Mode") },
+
+ /* Search menu */
+ { "SearchFind", GTK_STOCK_FIND, N_("_Find..."), "<control>F",
+ N_("Search for text"), G_CALLBACK (_pluma_cmd_search_find) },
+ { "SearchFindNext", NULL, N_("Find Ne_xt"), "<control>G",
+ N_("Search forwards for the same text"), G_CALLBACK (_pluma_cmd_search_find_next) },
+ { "SearchFindPrevious", NULL, N_("Find Pre_vious"), "<shift><control>G",
+ N_("Search backwards for the same text"), G_CALLBACK (_pluma_cmd_search_find_prev) },
+#ifndef OS_OSX
+ { "SearchReplace", GTK_STOCK_FIND_AND_REPLACE, N_("_Replace..."), "<control>H",
+ N_("Search for and replace text"), G_CALLBACK (_pluma_cmd_search_replace) },
+#else
+ { "SearchReplace", GTK_STOCK_FIND_AND_REPLACE, N_("_Replace..."), "<control><alt>F",
+ N_("Search for and replace text"), G_CALLBACK (_pluma_cmd_search_replace) },
+#endif
+ { "SearchClearHighlight", NULL, N_("_Clear Highlight"), "<shift><control>K",
+ N_("Clear highlighting of search matches"), G_CALLBACK (_pluma_cmd_search_clear_highlight) },
+ { "SearchGoToLine", GTK_STOCK_JUMP_TO, N_("Go to _Line..."), "<control>I",
+ N_("Go to a specific line"), G_CALLBACK (_pluma_cmd_search_goto_line) },
+ { "SearchIncrementalSearch", GTK_STOCK_FIND, N_("_Incremental Search..."), "<control>K",
+ N_("Incrementally search for text"), G_CALLBACK (_pluma_cmd_search_incremental_search) },
+
+ /* Documents menu */
+ { "FileSaveAll", GTK_STOCK_SAVE, N_("_Save All"), "<shift><control>L",
+ N_("Save all open files"), G_CALLBACK (_pluma_cmd_file_save_all) },
+ { "FileCloseAll", GTK_STOCK_CLOSE, N_("_Close All"), "<shift><control>W",
+ N_("Close all open files"), G_CALLBACK (_pluma_cmd_file_close_all) },
+ { "DocumentsPreviousDocument", NULL, N_("_Previous Document"), "<alt><control>Page_Up",
+ N_("Activate previous document"), G_CALLBACK (_pluma_cmd_documents_previous_document) },
+ { "DocumentsNextDocument", NULL, N_("_Next Document"), "<alt><control>Page_Down",
+ N_("Activate next document"), G_CALLBACK (_pluma_cmd_documents_next_document) },
+ { "DocumentsMoveToNewWindow", NULL, N_("_Move to New Window"), NULL,
+ N_("Move the current document to a new window"), G_CALLBACK (_pluma_cmd_documents_move_to_new_window) }
+};
+
+/* separate group, needs to be sensitive on OS X even when there are no tabs */
+static const GtkActionEntry pluma_close_menu_entries[] =
+{
+ { "FileClose", GTK_STOCK_CLOSE, NULL, "<control>W",
+ N_("Close the current file"), G_CALLBACK (_pluma_cmd_file_close) }
+};
+
+/* separate group, should be sensitive even when there are no tabs */
+static const GtkActionEntry pluma_quit_menu_entries[] =
+{
+ { "FileQuit", GTK_STOCK_QUIT, NULL, "<control>Q",
+ N_("Quit the program"), G_CALLBACK (_pluma_cmd_file_quit) }
+};
+
+static const GtkToggleActionEntry pluma_always_sensitive_toggle_menu_entries[] =
+{
+ { "ViewToolbar", NULL, N_("_Toolbar"), NULL,
+ N_("Show or hide the toolbar in the current window"),
+ G_CALLBACK (_pluma_cmd_view_show_toolbar), TRUE },
+ { "ViewStatusbar", NULL, N_("_Statusbar"), NULL,
+ N_("Show or hide the statusbar in the current window"),
+ G_CALLBACK (_pluma_cmd_view_show_statusbar), TRUE },
+ { "ViewFullscreen", GTK_STOCK_FULLSCREEN, NULL, "F11",
+ N_("Edit text in fullscreen"),
+ G_CALLBACK (_pluma_cmd_view_toggle_fullscreen_mode), FALSE }
+};
+
+/* separate group, should be always sensitive except when there are no panes */
+static const GtkToggleActionEntry pluma_panes_toggle_menu_entries[] =
+{
+ { "ViewSidePane", NULL, N_("Side _Pane"), "F9",
+ N_("Show or hide the side pane in the current window"),
+ G_CALLBACK (_pluma_cmd_view_show_side_pane), FALSE },
+ { "ViewBottomPane", NULL, N_("_Bottom Pane"), "<control>F9",
+ N_("Show or hide the bottom pane in the current window"),
+ G_CALLBACK (_pluma_cmd_view_show_bottom_pane), FALSE }
+};
+
+G_END_DECLS
+
+#endif /* __PLUMA_UI_H__ */
diff --git a/pluma/pluma-ui.xml b/pluma/pluma-ui.xml
new file mode 100755
index 00000000..471ea5f3
--- /dev/null
+++ b/pluma/pluma-ui.xml
@@ -0,0 +1,203 @@
+<!--
+ * pluma-ui.xml
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ *
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+-->
+
+<ui>
+
+ <menubar name="MenuBar">
+ <menu name="FileMenu" action="File">
+ <menuitem name="FileNewMenu" action="FileNew"/>
+ <placeholder name="FileOps_1"/>
+ <menuitem name="FileOpenMenu" action="FileOpen"/>
+ <placeholder name="FileOps_2"/>
+ <separator/>
+ <menuitem name="FileSaveMenu" action="FileSave"/>
+ <menuitem name="FileSaveAsMenu" action="FileSaveAs"/>
+ <placeholder name="FileOps_3"/>
+ <menuitem name="FileRevertMenu" action="FileRevert"/>
+ <placeholder name="FileOps_4"/>
+ <separator/>
+ <placeholder name="FileOps_5"/>
+ <menuitem name="FilePrintPreviewMenu" action="FilePrintPreview"/>
+ <menuitem name="FilePrintMenu" action="FilePrint"/>
+ <placeholder name="FileRecentsPlaceholder">
+ <separator/>
+ </placeholder>
+ <separator/>
+ <menuitem name="FileCloseMenu" action="FileClose"/>
+ <menuitem name="FileQuitMenu" action="FileQuit"/>
+ </menu>
+
+ <menu name="EditMenu" action="Edit">
+ <menuitem name="EditUndoMenu" action="EditUndo"/>
+ <menuitem name="EditRedoMenu" action="EditRedo"/>
+ <separator/>
+ <menuitem name="EditCutMenu" action="EditCut"/>
+ <menuitem name="EditCopyMenu" action="EditCopy"/>
+ <menuitem name="EditPasteMenu" action="EditPaste"/>
+ <menuitem name="EditDeleteMenu" action="EditDelete"/>
+ <placeholder name="EditOps_1" />
+ <separator/>
+ <placeholder name="EditOps_2" />
+ <menuitem name="EditSelectAllMenu" action="EditSelectAll"/>
+ <placeholder name="EditOps_3" />
+ <separator/>
+ <placeholder name="EditOps_4" />
+ <separator/>
+ <placeholder name="EditOps_5" />
+ <separator/>
+ <placeholder name="EditOps_6" />
+ <separator/>
+ <menuitem name="EditPreferencesMenu" action="EditPreferences"/>
+ </menu>
+
+ <menu name="ViewMenu" action="View">
+ <menuitem name="ViewToolbarMenu" action="ViewToolbar"/>
+ <menuitem name="ViewStatusbarMenu" action="ViewStatusbar"/>
+ <menuitem name="ViewSidePaneMenu" action="ViewSidePane"/>
+ <menuitem name="ViewBottomPaneMenu" action="ViewBottomPane"/>
+ <separator/>
+ <menuitem name="ViewFullscreenMenu" action="ViewFullscreen"/>
+ <separator/>
+ <menu name="ViewHighlightModeMenu" action="ViewHighlightMode">
+ <placeholder name="LanguagesMenuPlaceholder">
+ </placeholder>
+ </menu>
+ </menu>
+
+ <menu name="SearchMenu" action="Search">
+ <menuitem name="SearchFindMenu" action="SearchFind"/>
+ <menuitem name="SearchFindNextMenu" action="SearchFindNext"/>
+ <menuitem name="SearchFindPreviousMenu" action="SearchFindPrevious"/>
+ <menuitem name="SearchIncrementalSearchMenu" action="SearchIncrementalSearch"/>
+ <placeholder name="SearchOps_1" />
+ <separator/>
+ <placeholder name="SearchOps_2" />
+ <separator/>
+ <menuitem name="SearchReplaceMenu" action="SearchReplace"/>
+ <placeholder name="SearchOps_3" />
+ <separator/>
+ <placeholder name="SearchOps_4" />
+ <separator/>
+ <menuitem name="SearchClearHighlight" action="SearchClearHighlight"/>
+ <placeholder name="SearchOps_5" />
+ <separator/>
+ <placeholder name="SearchOps_6" />
+ <separator/>
+ <menuitem name="SearchGoToLineMenu" action="SearchGoToLine"/>
+ <placeholder name="SearchOps_7" />
+ <separator/>
+ <placeholder name="SearchOps_8" />
+ </menu>
+
+ <menu name="ToolsMenu" action="Tools">
+ <placeholder name="ToolsOps_1" />
+ <separator/>
+ <placeholder name="ToolsOps_2" />
+ <separator/>
+ <placeholder name="ToolsOps_3" />
+ <separator/>
+ <placeholder name="ToolsOps_4" />
+ <separator/>
+ <placeholder name="ToolsOps_5" />
+ </menu>
+
+ <placeholder name="ExtraMenu_1" />
+
+ <menu name="DocumentsMenu" action="Documents">
+ <menuitem action="FileSaveAll" />
+ <menuitem action="FileCloseAll" />
+ <placeholder name="DocumentsOps_1" />
+ <separator/>
+ <placeholder name="DocumentsOps_2" />
+ <separator/>
+ <placeholder name="DocumentsOps_3" />
+ <menuitem action="DocumentsPreviousDocument" />
+ <menuitem action="DocumentsNextDocument" />
+ <separator/>
+ <menuitem action="DocumentsMoveToNewWindow"/>
+ <placeholder name="DocumentsListPlaceholder">
+ <separator/>
+ </placeholder>
+ </menu>
+
+ <menu name="HelpMenu" action="Help">
+ <menuitem name="HelpContentsMenu" action="HelpContents"/>
+ <menuitem name="HelpAboutMenu" action="HelpAbout"/>
+ </menu>
+ </menubar>
+
+ <toolbar name="ToolBar">
+ <toolitem action="FileNew"/>
+ <toolitem action="FileSave"/>
+ <separator/>
+ <toolitem action="FilePrint"/>
+ <separator/>
+ <toolitem action="EditUndo"/>
+ <toolitem action="EditRedo"/>
+ <separator/>
+ <toolitem action="EditCut"/>
+ <toolitem action="EditCopy"/>
+ <toolitem action="EditPaste"/>
+ <separator/>
+ <toolitem action="SearchFind"/>
+ <toolitem action="SearchReplace"/>
+ </toolbar>
+
+ <toolbar name="FullscreenToolBar">
+ <toolitem action="FileNew"/>
+ <toolitem action="FileSave"/>
+ <separator/>
+ <toolitem action="FilePrint"/>
+ <separator/>
+ <toolitem action="EditUndo"/>
+ <toolitem action="EditRedo"/>
+ <separator/>
+ <toolitem action="EditCut"/>
+ <toolitem action="EditCopy"/>
+ <toolitem action="EditPaste"/>
+ <separator/>
+ <toolitem action="SearchFind"/>
+ <toolitem action="SearchReplace"/>
+ <separator expand="true"/>
+ <toolitem action="LeaveFullscreen"/>
+ </toolbar>
+
+ <popup name="NotebookPopup" action="NotebookPopupAction">
+ <menuitem action="DocumentsMoveToNewWindow"/>
+ <separator/>
+ <menuitem action="FileSave"/>
+ <menuitem action="FileSaveAs"/>
+ <separator/>
+ <menuitem action="FilePrint"/>
+ <separator/>
+ <placeholder name="NotebookPupupOps_1"/>
+ <separator/>
+ <menuitem name="FileCloseMenu" action="FileClose"/>
+ </popup>
+
+</ui>
diff --git a/pluma/pluma-utils.c b/pluma/pluma-utils.c
new file mode 100755
index 00000000..62ca854f
--- /dev/null
+++ b/pluma/pluma-utils.c
@@ -0,0 +1,1546 @@
+/*
+ * pluma-utils.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2003-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "pluma-utils.h"
+
+#include "pluma-document.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-debug.h"
+
+/* For the workspace/viewport stuff */
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+
+#define STDIN_DELAY_MICROSECONDS 100000
+
+/* Returns true if uri is a file: uri and is not a chained uri */
+gboolean
+pluma_utils_uri_has_file_scheme (const gchar *uri)
+{
+ GFile *gfile;
+ gboolean res;
+
+ gfile = g_file_new_for_uri (uri);
+ res = g_file_has_uri_scheme (gfile, "file");
+
+ g_object_unref (gfile);
+ return res;
+}
+
+/* FIXME: we should check for chained URIs */
+gboolean
+pluma_utils_uri_has_writable_scheme (const gchar *uri)
+{
+ GFile *gfile;
+ gchar *scheme;
+ GSList *writable_schemes;
+ gboolean res;
+
+ gfile = g_file_new_for_uri (uri);
+ scheme = g_file_get_uri_scheme (gfile);
+
+ g_return_val_if_fail (scheme != NULL, FALSE);
+
+ g_object_unref (gfile);
+
+ writable_schemes = pluma_prefs_manager_get_writable_vfs_schemes ();
+
+ /* CHECK: should we use g_ascii_strcasecmp? - Paolo (Nov 6, 2005) */
+ res = (g_slist_find_custom (writable_schemes,
+ scheme,
+ (GCompareFunc)strcmp) != NULL);
+
+ g_slist_foreach (writable_schemes, (GFunc)g_free, NULL);
+ g_slist_free (writable_schemes);
+
+ g_free (scheme);
+
+ return res;
+}
+
+static void
+widget_get_origin (GtkWidget *widget, gint *x, gint *y)
+
+{
+ GdkWindow *window;
+
+ window = gtk_widget_get_window (widget);
+ gdk_window_get_origin (window, x, y);
+}
+
+void
+pluma_utils_menu_position_under_widget (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ GtkRequisition requisition;
+
+ widget = GTK_WIDGET (user_data);
+ widget_get_origin (widget, x, y);
+
+ gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ {
+ *x += widget->allocation.x + widget->allocation.width - requisition.width;
+ }
+ else
+ {
+ *x += widget->allocation.x;
+ }
+
+ *y += widget->allocation.y + widget->allocation.height;
+
+ *push_in = TRUE;
+}
+
+void
+pluma_utils_menu_position_under_tree_view (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GtkTreeView *tree = GTK_TREE_VIEW (user_data);
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (tree);
+ g_return_if_fail (model != NULL);
+
+ selection = gtk_tree_view_get_selection (tree);
+ g_return_if_fail (selection != NULL);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ GtkTreePath *path;
+ GdkRectangle rect;
+
+ widget_get_origin (GTK_WIDGET (tree), x, y);
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_get_cell_area (tree, path,
+ gtk_tree_view_get_column (tree, 0), /* FIXME 0 for RTL ? */
+ &rect);
+ gtk_tree_path_free (path);
+
+ *x += rect.x;
+ *y += rect.y + rect.height;
+
+ if (gtk_widget_get_direction (GTK_WIDGET (tree)) == GTK_TEXT_DIR_RTL)
+ {
+ GtkRequisition requisition;
+ gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
+ *x += rect.width - requisition.width;
+ }
+ }
+ else
+ {
+ /* no selection -> regular "under widget" positioning */
+ pluma_utils_menu_position_under_widget (menu,
+ x, y, push_in,
+ tree);
+ }
+}
+
+/* FIXME: remove this with gtk 2.12, it has gdk_color_to_string */
+gchar *
+pluma_gdk_color_to_string (GdkColor color)
+{
+ return g_strdup_printf ("#%04x%04x%04x",
+ color.red,
+ color.green,
+ color.blue);
+}
+
+GtkWidget *
+pluma_gtk_button_new_with_stock_icon (const gchar *label,
+ const gchar *stock_id)
+{
+ GtkWidget *button;
+
+ button = gtk_button_new_with_mnemonic (label);
+ gtk_button_set_image (GTK_BUTTON (button),
+ gtk_image_new_from_stock (stock_id,
+ GTK_ICON_SIZE_BUTTON));
+
+ return button;
+}
+
+GtkWidget *
+pluma_dialog_add_button (GtkDialog *dialog,
+ const gchar *text,
+ const gchar *stock_id,
+ gint response_id)
+{
+ GtkWidget *button;
+
+ g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
+ g_return_val_if_fail (text != NULL, NULL);
+ g_return_val_if_fail (stock_id != NULL, NULL);
+
+ button = pluma_gtk_button_new_with_stock_icon (text, stock_id);
+ g_return_val_if_fail (button != NULL, NULL);
+
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+
+ gtk_widget_show (button);
+
+ gtk_dialog_add_action_widget (dialog, button, response_id);
+
+ return button;
+}
+
+/*
+ * n: len of the string in bytes
+ */
+gboolean
+g_utf8_caselessnmatch (const char *s1, const char *s2, gssize n1, gssize n2)
+{
+ gchar *casefold;
+ gchar *normalized_s1;
+ gchar *normalized_s2;
+ gint len_s1;
+ gint len_s2;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (s1 != NULL, FALSE);
+ g_return_val_if_fail (s2 != NULL, FALSE);
+ g_return_val_if_fail (n1 > 0, FALSE);
+ g_return_val_if_fail (n2 > 0, FALSE);
+
+ casefold = g_utf8_casefold (s1, n1);
+ normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ casefold = g_utf8_casefold (s2, n2);
+ normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ len_s1 = strlen (normalized_s1);
+ len_s2 = strlen (normalized_s2);
+
+ if (len_s1 < len_s2)
+ goto finally_2;
+
+ ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
+
+finally_2:
+ g_free (normalized_s1);
+ g_free (normalized_s2);
+
+ return ret;
+}
+
+/**
+ * pluma_utils_set_atk_name_description
+ * @widget : The Gtk widget for which name/description to be set
+ * @name : Atk name string
+ * @description : Atk description string
+ * Description : This function sets up name and description
+ * for a specified gtk widget.
+ */
+void
+pluma_utils_set_atk_name_description (GtkWidget *widget,
+ const gchar *name,
+ const gchar *description)
+{
+ AtkObject *aobj;
+
+ aobj = gtk_widget_get_accessible (widget);
+
+ if (!(GTK_IS_ACCESSIBLE (aobj)))
+ return;
+
+ if(name)
+ atk_object_set_name (aobj, name);
+
+ if(description)
+ atk_object_set_description (aobj, description);
+}
+
+/**
+ * pluma_set_atk__relation
+ * @obj1,@obj2 : specified widgets.
+ * @rel_type : the type of relation to set up.
+ * Description : This function establishes atk relation
+ * between 2 specified widgets.
+ */
+void
+pluma_utils_set_atk_relation (GtkWidget *obj1,
+ GtkWidget *obj2,
+ AtkRelationType rel_type )
+{
+ AtkObject *atk_obj1, *atk_obj2;
+ AtkRelationSet *relation_set;
+ AtkObject *targets[1];
+ AtkRelation *relation;
+
+ atk_obj1 = gtk_widget_get_accessible (obj1);
+ atk_obj2 = gtk_widget_get_accessible (obj2);
+
+ if (!(GTK_IS_ACCESSIBLE (atk_obj1)) || !(GTK_IS_ACCESSIBLE (atk_obj2)))
+ return;
+
+ relation_set = atk_object_ref_relation_set (atk_obj1);
+ targets[0] = atk_obj2;
+
+ relation = atk_relation_new (targets, 1, rel_type);
+ atk_relation_set_add (relation_set, relation);
+
+ g_object_unref (G_OBJECT (relation));
+}
+
+gboolean
+pluma_utils_uri_exists (const gchar* text_uri)
+{
+ GFile *gfile;
+ gboolean res;
+
+ g_return_val_if_fail (text_uri != NULL, FALSE);
+
+ pluma_debug_message (DEBUG_UTILS, "text_uri: %s", text_uri);
+
+ gfile = g_file_new_for_uri (text_uri);
+ res = g_file_query_exists (gfile, NULL);
+
+ g_object_unref (gfile);
+
+ pluma_debug_message (DEBUG_UTILS, res ? "TRUE" : "FALSE");
+
+ return res;
+}
+
+gchar *
+pluma_utils_escape_search_text (const gchar* text)
+{
+ GString *str;
+ gint length;
+ const gchar *p;
+ const gchar *end;
+
+ if (text == NULL)
+ return NULL;
+
+ pluma_debug_message (DEBUG_SEARCH, "Text: %s", text);
+
+ length = strlen (text);
+
+ /* no escape when typing.
+ * The short circuit works only for ascii, but we only
+ * care about not escaping a single '\' */
+ if (length == 1)
+ return g_strdup (text);
+
+ str = g_string_new ("");
+
+ p = text;
+ end = text + length;
+
+ while (p != end)
+ {
+ const gchar *next;
+ next = g_utf8_next_char (p);
+
+ switch (*p)
+ {
+ case '\n':
+ g_string_append (str, "\\n");
+ break;
+ case '\r':
+ g_string_append (str, "\\r");
+ break;
+ case '\t':
+ g_string_append (str, "\\t");
+ break;
+ case '\\':
+ g_string_append (str, "\\\\");
+ break;
+ default:
+ g_string_append_len (str, p, next - p);
+ break;
+ }
+
+ p = next;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+gchar *
+pluma_utils_unescape_search_text (const gchar *text)
+{
+ GString *str;
+ gint length;
+ gboolean drop_prev = FALSE;
+ const gchar *cur;
+ const gchar *end;
+ const gchar *prev;
+
+ if (text == NULL)
+ return NULL;
+
+ length = strlen (text);
+
+ str = g_string_new ("");
+
+ cur = text;
+ end = text + length;
+ prev = NULL;
+
+ while (cur != end)
+ {
+ const gchar *next;
+ next = g_utf8_next_char (cur);
+
+ if (prev && (*prev == '\\'))
+ {
+ switch (*cur)
+ {
+ case 'n':
+ str = g_string_append (str, "\n");
+ break;
+ case 'r':
+ str = g_string_append (str, "\r");
+ break;
+ case 't':
+ str = g_string_append (str, "\t");
+ break;
+ case '\\':
+ str = g_string_append (str, "\\");
+ drop_prev = TRUE;
+ break;
+ default:
+ str = g_string_append (str, "\\");
+ str = g_string_append_len (str, cur, next - cur);
+ break;
+ }
+ }
+ else if (*cur != '\\')
+ {
+ str = g_string_append_len (str, cur, next - cur);
+ }
+ else if ((next == end) && (*cur == '\\'))
+ {
+ str = g_string_append (str, "\\");
+ }
+
+ if (!drop_prev)
+ {
+ prev = cur;
+ }
+ else
+ {
+ prev = NULL;
+ drop_prev = FALSE;
+ }
+
+ cur = next;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+void
+pluma_warning (GtkWindow *parent, const gchar *format, ...)
+{
+ va_list args;
+ gchar *str;
+ GtkWidget *dialog;
+ GtkWindowGroup *wg = NULL;
+
+ g_return_if_fail (format != NULL);
+
+ if (parent != NULL)
+ wg = gtk_window_get_group (parent);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ dialog = gtk_message_dialog_new_with_markup (
+ parent,
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", str);
+
+ g_free (str);
+
+ if (wg != NULL)
+ gtk_window_group_add_window (wg, GTK_WINDOW (dialog));
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_widget_show (dialog);
+}
+
+/*
+ * Doubles underscore to avoid spurious menu accels.
+ */
+gchar *
+pluma_utils_escape_underscores (const gchar* text,
+ gssize length)
+{
+ GString *str;
+ const gchar *p;
+ const gchar *end;
+
+ g_return_val_if_fail (text != NULL, NULL);
+
+ if (length < 0)
+ length = strlen (text);
+
+ str = g_string_sized_new (length);
+
+ p = text;
+ end = text + length;
+
+ while (p != end)
+ {
+ const gchar *next;
+ next = g_utf8_next_char (p);
+
+ switch (*p)
+ {
+ case '_':
+ g_string_append (str, "__");
+ break;
+ default:
+ g_string_append_len (str, p, next - p);
+ break;
+ }
+
+ p = next;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+/* the following functions are taken from eel */
+
+static gchar *
+pluma_utils_str_truncate (const gchar *string,
+ guint truncate_length,
+ gboolean middle)
+{
+ GString *truncated;
+ guint length;
+ guint n_chars;
+ guint num_left_chars;
+ guint right_offset;
+ guint delimiter_length;
+ const gchar *delimiter = "\342\200\246";
+
+ g_return_val_if_fail (string != NULL, NULL);
+
+ length = strlen (string);
+
+ g_return_val_if_fail (g_utf8_validate (string, length, NULL), NULL);
+
+ /* It doesnt make sense to truncate strings to less than
+ * the size of the delimiter plus 2 characters (one on each
+ * side)
+ */
+ delimiter_length = g_utf8_strlen (delimiter, -1);
+ if (truncate_length < (delimiter_length + 2)) {
+ return g_strdup (string);
+ }
+
+ n_chars = g_utf8_strlen (string, length);
+
+ /* Make sure the string is not already small enough. */
+ if (n_chars <= truncate_length) {
+ return g_strdup (string);
+ }
+
+ /* Find the 'middle' where the truncation will occur. */
+ if (middle)
+ {
+ num_left_chars = (truncate_length - delimiter_length) / 2;
+ right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
+
+ truncated = g_string_new_len (string,
+ g_utf8_offset_to_pointer (string, num_left_chars) - string);
+ g_string_append (truncated, delimiter);
+ g_string_append (truncated, g_utf8_offset_to_pointer (string, right_offset));
+ }
+ else
+ {
+ num_left_chars = truncate_length - delimiter_length;
+ truncated = g_string_new_len (string,
+ g_utf8_offset_to_pointer (string, num_left_chars) - string);
+ g_string_append (truncated, delimiter);
+ }
+
+ return g_string_free (truncated, FALSE);
+}
+
+gchar *
+pluma_utils_str_middle_truncate (const gchar *string,
+ guint truncate_length)
+{
+ return pluma_utils_str_truncate (string, truncate_length, TRUE);
+}
+
+gchar *
+pluma_utils_str_end_truncate (const gchar *string,
+ guint truncate_length)
+{
+ return pluma_utils_str_truncate (string, truncate_length, FALSE);
+}
+
+gchar *
+pluma_utils_make_valid_utf8 (const char *name)
+{
+ GString *string;
+ const char *remainder, *invalid;
+ int remaining_bytes, valid_bytes;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ string = NULL;
+ remainder = name;
+ remaining_bytes = strlen (name);
+
+ while (remaining_bytes != 0) {
+ if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
+ break;
+ }
+ valid_bytes = invalid - remainder;
+
+ if (string == NULL) {
+ string = g_string_sized_new (remaining_bytes);
+ }
+ g_string_append_len (string, remainder, valid_bytes);
+ /* append U+FFFD REPLACEMENT CHARACTER */
+ g_string_append (string, "\357\277\275");
+
+ remaining_bytes -= valid_bytes + 1;
+ remainder = invalid + 1;
+ }
+
+ if (string == NULL) {
+ return g_strdup (name);
+ }
+
+ g_string_append (string, remainder);
+
+ g_assert (g_utf8_validate (string->str, -1, NULL));
+
+ return g_string_free (string, FALSE);
+}
+
+/* Note that this function replace home dir with ~ */
+gchar *
+pluma_utils_uri_get_dirname (const gchar *uri)
+{
+ gchar *res;
+ gchar *str;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ /* CHECK: does it work with uri chaining? - Paolo */
+ str = g_path_get_dirname (uri);
+ g_return_val_if_fail (str != NULL, g_strdup ("."));
+
+ if ((strlen (str) == 1) && (*str == '.'))
+ {
+ g_free (str);
+
+ return NULL;
+ }
+
+ res = pluma_utils_replace_home_dir_with_tilde (str);
+
+ g_free (str);
+
+ return res;
+}
+
+/**
+ * pluma_utils_location_get_dirname_for_display
+ * @file: the location
+ *
+ * Returns a string suitable to be displayed in the UI indicating
+ * the name of the directory where the file is located.
+ * For remote files it may also contain the hostname etc.
+ * For local files it tries to replace the home dir with ~.
+ *
+ * Returns: a string to display the dirname
+ */
+gchar *
+pluma_utils_location_get_dirname_for_display (GFile *location)
+{
+ gchar *uri;
+ gchar *res;
+ GMount *mount;
+
+ g_return_val_if_fail (location != NULL, NULL);
+
+ /* we use the parse name, that is either the local path
+ * or an uri but which is utf8 safe */
+ uri = g_file_get_parse_name (location);
+
+ /* FIXME: this is sync... is it a problem? */
+ mount = g_file_find_enclosing_mount (location, NULL, NULL);
+ if (mount != NULL)
+ {
+ gchar *mount_name;
+ gchar *path;
+
+ mount_name = g_mount_get_name (mount);
+ g_object_unref (mount);
+
+ /* obtain the "path" patrt of the uri */
+ if (pluma_utils_decode_uri (uri,
+ NULL, NULL,
+ NULL, NULL,
+ &path))
+ {
+ gchar *dirname;
+
+ dirname = pluma_utils_uri_get_dirname (path);
+ res = g_strdup_printf ("%s %s", mount_name, dirname);
+
+ g_free (path);
+ g_free (dirname);
+ g_free (mount_name);
+ }
+ else
+ {
+ res = mount_name;
+ }
+ }
+ else
+ {
+ /* fallback for local files or uris without mounts */
+ res = pluma_utils_uri_get_dirname (uri);
+ }
+
+ g_free (uri);
+
+ return res;
+}
+
+gchar *
+pluma_utils_replace_home_dir_with_tilde (const gchar *uri)
+{
+ gchar *tmp;
+ gchar *home;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ /* Note that g_get_home_dir returns a const string */
+ tmp = (gchar *)g_get_home_dir ();
+
+ if (tmp == NULL)
+ return g_strdup (uri);
+
+ home = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
+ if (home == NULL)
+ return g_strdup (uri);
+
+ if (strcmp (uri, home) == 0)
+ {
+ g_free (home);
+
+ return g_strdup ("~");
+ }
+
+ tmp = home;
+ home = g_strdup_printf ("%s/", tmp);
+ g_free (tmp);
+
+ if (g_str_has_prefix (uri, home))
+ {
+ gchar *res;
+
+ res = g_strdup_printf ("~/%s", uri + strlen (home));
+
+ g_free (home);
+
+ return res;
+ }
+
+ g_free (home);
+
+ return g_strdup (uri);
+}
+
+/* the following two functions are courtesy of galeon */
+
+/**
+ * pluma_utils_get_current_workspace: Get the current workspace
+ *
+ * Get the currently visible workspace for the #GdkScreen.
+ *
+ * If the X11 window property isn't found, 0 (the first workspace)
+ * is returned.
+ */
+guint
+pluma_utils_get_current_workspace (GdkScreen *screen)
+{
+#ifdef GDK_WINDOWING_X11
+ GdkWindow *root_win;
+ GdkDisplay *display;
+ Atom type;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ guint *current_desktop;
+ gint err, result;
+ guint ret = 0;
+
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), 0);
+
+ root_win = gdk_screen_get_root_window (screen);
+ display = gdk_screen_get_display (screen);
+
+ gdk_error_trap_push ();
+ result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (root_win),
+ gdk_x11_get_xatom_by_name_for_display (display, "_NET_CURRENT_DESKTOP"),
+ 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (gpointer) &current_desktop);
+ err = gdk_error_trap_pop ();
+
+ if (err != Success || result != Success)
+ return ret;
+
+ if (type == XA_CARDINAL && format == 32 && nitems > 0)
+ ret = current_desktop[0];
+
+ XFree (current_desktop);
+ return ret;
+#else
+ /* FIXME: on mac etc proably there are native APIs
+ * to get the current workspace etc */
+ return 0;
+#endif
+}
+
+/**
+ * pluma_utils_get_window_workspace: Get the workspace the window is on
+ *
+ * This function gets the workspace that the #GtkWindow is visible on,
+ * it returns PLUMA_ALL_WORKSPACES if the window is sticky, or if
+ * the window manager doesn support this function
+ */
+guint
+pluma_utils_get_window_workspace (GtkWindow *gtkwindow)
+{
+#ifdef GDK_WINDOWING_X11
+ GdkWindow *window;
+ GdkDisplay *display;
+ Atom type;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ guint *workspace;
+ gint err, result;
+ guint ret = PLUMA_ALL_WORKSPACES;
+
+ g_return_val_if_fail (GTK_IS_WINDOW (gtkwindow), 0);
+ g_return_val_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (gtkwindow)), 0);
+
+ window = gtk_widget_get_window (GTK_WIDGET (gtkwindow));
+ display = gdk_drawable_get_display (window);
+
+ gdk_error_trap_push ();
+ result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (window),
+ gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_DESKTOP"),
+ 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (gpointer) &workspace);
+ err = gdk_error_trap_pop ();
+
+ if (err != Success || result != Success)
+ return ret;
+
+ if (type == XA_CARDINAL && format == 32 && nitems > 0)
+ ret = workspace[0];
+
+ XFree (workspace);
+ return ret;
+#else
+ /* FIXME: on mac etc proably there are native APIs
+ * to get the current workspace etc */
+ return 0;
+#endif
+}
+
+/**
+ * pluma_utils_get_current_viewport: Get the current viewport origin
+ *
+ * Get the currently visible viewport origin for the #GdkScreen.
+ *
+ * If the X11 window property isn't found, (0, 0) is returned.
+ */
+void
+pluma_utils_get_current_viewport (GdkScreen *screen,
+ gint *x,
+ gint *y)
+{
+#ifdef GDK_WINDOWING_X11
+ GdkWindow *root_win;
+ GdkDisplay *display;
+ Atom type;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ gulong *coordinates;
+ gint err, result;
+
+ g_return_if_fail (GDK_IS_SCREEN (screen));
+ g_return_if_fail (x != NULL && y != NULL);
+
+ /* Default values for the viewport origin */
+ *x = 0;
+ *y = 0;
+
+ root_win = gdk_screen_get_root_window (screen);
+ display = gdk_screen_get_display (screen);
+
+ gdk_error_trap_push ();
+ result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (root_win),
+ gdk_x11_get_xatom_by_name_for_display (display, "_NET_DESKTOP_VIEWPORT"),
+ 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (void*) &coordinates);
+ err = gdk_error_trap_pop ();
+
+ if (err != Success || result != Success)
+ return;
+
+ if (type != XA_CARDINAL || format != 32 || nitems < 2)
+ {
+ XFree (coordinates);
+ return;
+ }
+
+ *x = coordinates[0];
+ *y = coordinates[1];
+ XFree (coordinates);
+#else
+ /* FIXME: on mac etc proably there are native APIs
+ * to get the current workspace etc */
+ *x = 0;
+ *y = 0;
+#endif
+}
+
+void
+pluma_utils_activate_url (GtkAboutDialog *about,
+ const gchar *url,
+ gpointer data)
+{
+ gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (about)), url, GDK_CURRENT_TIME, NULL);
+}
+
+static gboolean
+is_valid_scheme_character (gchar c)
+{
+ return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
+}
+
+static gboolean
+has_valid_scheme (const gchar *uri)
+{
+ const gchar *p;
+
+ p = uri;
+
+ if (!is_valid_scheme_character (*p)) {
+ return FALSE;
+ }
+
+ do {
+ p++;
+ } while (is_valid_scheme_character (*p));
+
+ return *p == ':';
+}
+
+gboolean
+pluma_utils_is_valid_uri (const gchar *uri)
+{
+ const guchar *p;
+
+ if (uri == NULL)
+ return FALSE;
+
+ if (!has_valid_scheme (uri))
+ return FALSE;
+
+ /* We expect to have a fully valid set of characters */
+ for (p = (const guchar *)uri; *p; p++) {
+ if (*p == '%')
+ {
+ ++p;
+ if (!g_ascii_isxdigit (*p))
+ return FALSE;
+
+ ++p;
+ if (!g_ascii_isxdigit (*p))
+ return FALSE;
+ }
+ else
+ {
+ if (*p <= 32 || *p >= 128)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GtkWidget *
+handle_builder_error (const gchar *message, ...)
+{
+ GtkWidget *label;
+ gchar *msg;
+ gchar *msg_plain;
+ va_list args;
+
+ va_start (args, message);
+ msg_plain = g_strdup_vprintf (message, args);
+ va_end (args);
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+
+ msg = g_strconcat ("<span size=\"large\" weight=\"bold\">",
+ msg_plain, "</span>\n\n",
+ _("Please check your installation."),
+ NULL);
+
+ gtk_label_set_markup (GTK_LABEL (label), msg);
+
+ g_free (msg_plain);
+ g_free (msg);
+
+ gtk_misc_set_padding (GTK_MISC (label), 5, 5);
+
+ return label;
+}
+
+/**
+ * pluma_utils_get_ui_objects:
+ * @filename: the path to the gtk builder file
+ * @root_objects: a NULL terminated list of root objects to load or NULL to
+ * load all objects
+ * @error_widget: a pointer were a #GtkLabel
+ * @object_name: the name of the first object
+ * @...: a pointer were the first object is returned, followed by more
+ * name / object pairs and terminated by NULL.
+ *
+ * This function gets the requested objects from a GtkBuilder ui file. In case
+ * of error it returns FALSE and sets error_widget to a GtkLabel containing
+ * the error message to display.
+ *
+ * Returns FALSE if an error occurs, TRUE on success.
+ */
+gboolean
+pluma_utils_get_ui_objects (const gchar *filename,
+ gchar **root_objects,
+ GtkWidget **error_widget,
+ const gchar *object_name,
+ ...)
+{
+
+ GtkBuilder *builder;
+ va_list args;
+ const gchar *name;
+ GError *error = NULL;
+ gchar *filename_markup;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (error_widget != NULL, FALSE);
+ g_return_val_if_fail (object_name != NULL, FALSE);
+
+ filename_markup = g_markup_printf_escaped ("<i>%s</i>", filename);
+ *error_widget = NULL;
+
+ builder = gtk_builder_new ();
+
+ if (root_objects != NULL)
+ {
+ gtk_builder_add_objects_from_file (builder,
+ filename,
+ root_objects,
+ &error);
+ }
+ else
+ {
+ gtk_builder_add_from_file (builder,
+ filename,
+ &error);
+ }
+
+ if (error != NULL)
+ {
+ *error_widget = handle_builder_error (_("Unable to open UI file %s. Error: %s"),
+ filename_markup,
+ error->message);
+ g_error_free (error);
+ g_free (filename_markup);
+ g_object_unref (builder);
+
+ return FALSE;
+ }
+
+ va_start (args, object_name);
+ for (name = object_name; name; name = va_arg (args, const gchar *) )
+ {
+ GObject **gobj;
+
+ gobj = va_arg (args, GObject **);
+ *gobj = gtk_builder_get_object (builder, name);
+
+ if (!*gobj)
+ {
+ *error_widget = handle_builder_error (_("Unable to find the object '%s' inside file %s."),
+ name,
+ filename_markup),
+ ret = FALSE;
+ break;
+ }
+
+ /* we return a new ref for the root objects,
+ * the others are already reffed by their parent root object */
+ if (root_objects != NULL)
+ {
+ gint i;
+
+ for (i = 0; root_objects[i] != NULL; ++i)
+ {
+ if ((strcmp (name, root_objects[i]) == 0))
+ {
+ g_object_ref (*gobj);
+ }
+ }
+ }
+ }
+ va_end (args);
+
+ g_free (filename_markup);
+ g_object_unref (builder);
+
+ return ret;
+}
+
+gchar *
+pluma_utils_make_canonical_uri_from_shell_arg (const gchar *str)
+{
+ GFile *gfile;
+ gchar *uri;
+
+ g_return_val_if_fail (str != NULL, NULL);
+ g_return_val_if_fail (*str != '\0', NULL);
+
+ /* Note for the future:
+ * FIXME: is still still relevant?
+ *
+ * <federico> paolo: and flame whoever tells
+ * you that file:///mate/test_files/hëllò
+ * doesn't work --- that's not a valid URI
+ *
+ * <paolo> federico: well, another solution that
+ * does not requires patch to _from_shell_args
+ * is to check that the string returned by it
+ * contains only ASCII chars
+ * <federico> paolo: hmmmm, isn't there
+ * mate_vfs_is_uri_valid() or something?
+ * <paolo>: I will use pluma_utils_is_valid_uri ()
+ *
+ */
+
+ gfile = g_file_new_for_commandline_arg (str);
+ uri = g_file_get_uri (gfile);
+ g_object_unref (gfile);
+
+ if (pluma_utils_is_valid_uri (uri))
+ return uri;
+
+ g_free (uri);
+ return NULL;
+}
+
+/**
+ * pluma_utils_file_has_parent:
+ * @gfile: the GFile to check the parent for
+ *
+ * Return TRUE if the specified gfile has a parent (is not the root), FALSE
+ * otherwise
+ */
+gboolean
+pluma_utils_file_has_parent (GFile *gfile)
+{
+ GFile *parent;
+ gboolean ret;
+
+ parent = g_file_get_parent (gfile);
+ ret = parent != NULL;
+
+ if (parent)
+ g_object_unref (parent);
+
+ return ret;
+}
+
+/**
+ * pluma_utils_basename_for_display:
+ * @uri: uri for which the basename should be displayed
+ *
+ * Return the basename of a file suitable for display to users.
+ */
+gchar *
+pluma_utils_basename_for_display (gchar const *uri)
+{
+ gchar *name;
+ GFile *gfile;
+ gchar *hn;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ gfile = g_file_new_for_uri (uri);
+
+ /* First, try to query the display name, but only on local files */
+ if (g_file_has_uri_scheme (gfile, "file"))
+ {
+ GFileInfo *info;
+ info = g_file_query_info (gfile,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (info)
+ {
+ /* Simply get the display name to use as the basename */
+ name = g_strdup (g_file_info_get_display_name (info));
+ g_object_unref (info);
+ }
+ else
+ {
+ /* This is a local file, and therefore we will use
+ * g_filename_display_basename on the local path */
+ gchar *local_path;
+
+ local_path = g_file_get_path (gfile);
+ name = g_filename_display_basename (local_path);
+ g_free (local_path);
+ }
+ }
+ else if (pluma_utils_file_has_parent (gfile) || !pluma_utils_decode_uri (uri, NULL, NULL, &hn, NULL, NULL))
+ {
+ /* For remote files with a parent (so not just http://foo.com)
+ or remote file for which the decoding of the host name fails,
+ use the _parse_name and take basename of that */
+ gchar *parse_name;
+ gchar *base;
+
+ parse_name = g_file_get_parse_name (gfile);
+ base = g_filename_display_basename (parse_name);
+ name = g_uri_unescape_string (base, NULL);
+
+ g_free (base);
+ g_free (parse_name);
+ }
+ else
+ {
+ /* display '/ on <host>' using the decoded host */
+ gchar *hn_utf8;
+
+ if (hn != NULL)
+ hn_utf8 = pluma_utils_make_valid_utf8 (hn);
+ else
+ /* we should never get here */
+ hn_utf8 = g_strdup ("?");
+
+ /* Translators: '/ on <remote-share>' */
+ name = g_strdup_printf (_("/ on %s"), hn_utf8);
+
+ g_free (hn_utf8);
+ g_free (hn);
+ }
+
+ g_object_unref (gfile);
+
+ return name;
+}
+
+/**
+ * pluma_utils_uri_for_display:
+ * @uri: uri to be displayed.
+ *
+ * Filter, modify, unescape and change @uri to make it appropriate
+ * for display to users.
+ *
+ * This function is a convenient wrapper for g_file_get_parse_name
+ *
+ * Return value: a string which represents @uri and can be displayed.
+ */
+gchar *
+pluma_utils_uri_for_display (const gchar *uri)
+{
+ GFile *gfile;
+ gchar *parse_name;
+
+ gfile = g_file_new_for_uri (uri);
+ parse_name = g_file_get_parse_name (gfile);
+ g_object_unref (gfile);
+
+ return parse_name;
+}
+
+/**
+ * pluma_utils_drop_get_uris:
+ * @selection_data: the #GtkSelectionData from drag_data_received
+ * @info: the info from drag_data_received
+ *
+ * Create a list of valid uri's from a uri-list drop.
+ *
+ * Return value: a string array which will hold the uris or NULL if there
+ * were no valid uris. g_strfreev should be used when the
+ * string array is no longer used
+ */
+gchar **
+pluma_utils_drop_get_uris (GtkSelectionData *selection_data)
+{
+ gchar **uris;
+ gint i;
+ gint p = 0;
+ gchar **uri_list;
+
+ uris = g_uri_list_extract_uris ((gchar *) gtk_selection_data_get_data (selection_data));
+ uri_list = g_new0(gchar *, g_strv_length (uris) + 1);
+
+ for (i = 0; uris[i] != NULL; i++)
+ {
+ gchar *uri;
+
+ uri = pluma_utils_make_canonical_uri_from_shell_arg (uris[i]);
+
+ /* Silently ignore malformed URI/filename */
+ if (uri != NULL)
+ uri_list[p++] = uri;
+ }
+
+ if (*uri_list == NULL)
+ {
+ g_free(uri_list);
+ return NULL;
+ }
+
+ return uri_list;
+}
+
+static void
+null_ptr (gchar **ptr)
+{
+ if (ptr)
+ *ptr = NULL;
+}
+
+/**
+ * pluma_utils_decode_uri:
+ * @uri: the uri to decode
+ * @scheme: return value pointer for the uri's scheme (e.g. http, sftp, ...)
+ * @user: return value pointer for the uri user info
+ * @port: return value pointer for the uri port
+ * @host: return value pointer for the uri host
+ * @path: return value pointer for the uri path
+ *
+ * Parse and break an uri apart in its individual components like the uri
+ * scheme, user info, port, host and path. The return value pointer can be
+ * NULL to ignore certain parts of the uri. If the function returns TRUE, then
+ * all return value pointers should be freed using g_free
+ *
+ * Return value: TRUE if the uri could be properly decoded, FALSE otherwise.
+ */
+gboolean
+pluma_utils_decode_uri (const gchar *uri,
+ gchar **scheme,
+ gchar **user,
+ gchar **host,
+ gchar **port,
+ gchar **path
+)
+{
+ /* Largely copied from glib/gio/gdummyfile.c:_g_decode_uri. This
+ * functionality should be in glib/gio, but for now we implement it
+ * ourselves (see bug #546182) */
+
+ const char *p, *in, *hier_part_start, *hier_part_end;
+ char *out;
+ char c;
+
+ /* From RFC 3986 Decodes:
+ * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ */
+
+ p = uri;
+
+ null_ptr (scheme);
+ null_ptr (user);
+ null_ptr (port);
+ null_ptr (host);
+ null_ptr (path);
+
+ /* Decode scheme:
+ * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ */
+
+ if (!g_ascii_isalpha (*p))
+ return FALSE;
+
+ while (1)
+ {
+ c = *p++;
+
+ if (c == ':')
+ break;
+
+ if (!(g_ascii_isalnum(c) ||
+ c == '+' ||
+ c == '-' ||
+ c == '.'))
+ return FALSE;
+ }
+
+ if (scheme)
+ {
+ *scheme = g_malloc (p - uri);
+ out = *scheme;
+
+ for (in = uri; in < p - 1; in++)
+ *out++ = g_ascii_tolower (*in);
+
+ *out = '\0';
+ }
+
+ hier_part_start = p;
+ hier_part_end = p + strlen (p);
+
+ if (hier_part_start[0] == '/' && hier_part_start[1] == '/')
+ {
+ const char *authority_start, *authority_end;
+ const char *userinfo_start, *userinfo_end;
+ const char *host_start, *host_end;
+ const char *port_start;
+
+ authority_start = hier_part_start + 2;
+ /* authority is always followed by / or nothing */
+ authority_end = memchr (authority_start, '/', hier_part_end - authority_start);
+
+ if (authority_end == NULL)
+ authority_end = hier_part_end;
+
+ /* 3.2:
+ * authority = [ userinfo "@" ] host [ ":" port ]
+ */
+
+ userinfo_end = memchr (authority_start, '@', authority_end - authority_start);
+
+ if (userinfo_end)
+ {
+ userinfo_start = authority_start;
+
+ if (user)
+ *user = g_uri_unescape_segment (userinfo_start, userinfo_end, NULL);
+
+ if (user && *user == NULL)
+ {
+ if (scheme)
+ g_free (*scheme);
+
+ return FALSE;
+ }
+
+ host_start = userinfo_end + 1;
+ }
+ else
+ host_start = authority_start;
+
+ port_start = memchr (host_start, ':', authority_end - host_start);
+
+ if (port_start)
+ {
+ host_end = port_start++;
+
+ if (port)
+ *port = g_strndup (port_start, authority_end - port_start);
+ }
+ else
+ host_end = authority_end;
+
+ if (host)
+ *host = g_strndup (host_start, host_end - host_start);
+
+ hier_part_start = authority_end;
+ }
+
+ if (path)
+ *path = g_uri_unescape_segment (hier_part_start, hier_part_end, "/");
+
+ return TRUE;
+}
diff --git a/pluma/pluma-utils.h b/pluma/pluma-utils.h
new file mode 100755
index 00000000..98d2aec1
--- /dev/null
+++ b/pluma/pluma-utils.h
@@ -0,0 +1,162 @@
+/*
+ * pluma-utils.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002 - 2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_UTILS_H__
+#define __PLUMA_UTILS_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <atk/atk.h>
+#include <pluma/pluma-encodings.h>
+
+G_BEGIN_DECLS
+
+/* useful macro */
+#define GBOOLEAN_TO_POINTER(i) (GINT_TO_POINTER ((i) ? 2 : 1))
+#define GPOINTER_TO_BOOLEAN(i) ((gboolean) ((GPOINTER_TO_INT(i) == 2) ? TRUE : FALSE))
+
+#define IS_VALID_BOOLEAN(v) (((v == TRUE) || (v == FALSE)) ? TRUE : FALSE)
+
+enum { PLUMA_ALL_WORKSPACES = 0xffffffff };
+
+gboolean pluma_utils_uri_has_writable_scheme (const gchar *uri);
+gboolean pluma_utils_uri_has_file_scheme (const gchar *uri);
+
+void pluma_utils_menu_position_under_widget (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data);
+
+void pluma_utils_menu_position_under_tree_view
+ (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data);
+
+gchar *pluma_gdk_color_to_string (GdkColor color);
+
+GtkWidget *pluma_gtk_button_new_with_stock_icon (const gchar *label,
+ const gchar *stock_id);
+
+GtkWidget *pluma_dialog_add_button (GtkDialog *dialog,
+ const gchar *text,
+ const gchar *stock_id,
+ gint response_id);
+
+gchar *pluma_utils_escape_underscores (const gchar *text,
+ gssize length);
+
+gchar *pluma_utils_str_middle_truncate (const gchar *string,
+ guint truncate_length);
+
+gchar *pluma_utils_str_end_truncate (const gchar *string,
+ guint truncate_length);
+
+gboolean g_utf8_caselessnmatch (const char *s1,
+ const char *s2,
+ gssize n1,
+ gssize n2);
+
+void pluma_utils_set_atk_name_description (GtkWidget *widget,
+ const gchar *name,
+ const gchar *description);
+
+void pluma_utils_set_atk_relation (GtkWidget *obj1,
+ GtkWidget *obj2,
+ AtkRelationType rel_type);
+
+gboolean pluma_utils_uri_exists (const gchar* text_uri);
+
+gchar *pluma_utils_escape_search_text (const gchar *text);
+
+gchar *pluma_utils_unescape_search_text (const gchar *text);
+
+void pluma_warning (GtkWindow *parent,
+ const gchar *format,
+ ...) G_GNUC_PRINTF(2, 3);
+
+gchar *pluma_utils_make_valid_utf8 (const char *name);
+
+/* Note that this function replace home dir with ~ */
+gchar *pluma_utils_uri_get_dirname (const char *uri);
+
+gchar *pluma_utils_location_get_dirname_for_display
+ (GFile *location);
+
+gchar *pluma_utils_replace_home_dir_with_tilde (const gchar *uri);
+
+guint pluma_utils_get_current_workspace (GdkScreen *screen);
+
+guint pluma_utils_get_window_workspace (GtkWindow *gtkwindow);
+
+void pluma_utils_get_current_viewport (GdkScreen *screen,
+ gint *x,
+ gint *y);
+
+void pluma_utils_activate_url (GtkAboutDialog *about,
+ const gchar *url,
+ gpointer data);
+
+gboolean pluma_utils_is_valid_uri (const gchar *uri);
+
+gboolean pluma_utils_get_ui_objects (const gchar *filename,
+ gchar **root_objects,
+ GtkWidget **error_widget,
+ const gchar *object_name,
+ ...) G_GNUC_NULL_TERMINATED;
+
+gboolean pluma_utils_file_has_parent (GFile *gfile);
+
+/* Return NULL if str is not a valid URI and/or filename */
+gchar *pluma_utils_make_canonical_uri_from_shell_arg
+ (const gchar *str);
+
+gchar *pluma_utils_uri_for_display (const gchar *uri);
+gchar *pluma_utils_basename_for_display (const gchar *uri);
+gboolean pluma_utils_decode_uri (const gchar *uri,
+ gchar **scheme,
+ gchar **user,
+ gchar **port,
+ gchar **host,
+ gchar **path);
+
+
+/* Turns data from a drop into a list of well formatted uris */
+gchar **pluma_utils_drop_get_uris (GtkSelectionData *selection_data);
+
+G_END_DECLS
+
+#endif /* __PLUMA_UTILS_H__ */
+
+
diff --git a/pluma/pluma-view.c b/pluma/pluma-view.c
new file mode 100755
index 00000000..f909be74
--- /dev/null
+++ b/pluma/pluma-view.c
@@ -0,0 +1,2181 @@
+/*
+ * pluma-view.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2003-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gdk/gdkkeysyms.h>
+
+#include <glib/gi18n.h>
+
+#include "pluma-view.h"
+#include "pluma-debug.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-marshal.h"
+#include "pluma-utils.h"
+
+
+#define PLUMA_VIEW_SCROLL_MARGIN 0.02
+#define PLUMA_VIEW_SEARCH_DIALOG_TIMEOUT (30*1000) /* 30 seconds */
+
+#define MIN_SEARCH_COMPLETION_KEY_LEN 3
+
+#define PLUMA_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), PLUMA_TYPE_VIEW, PlumaViewPrivate))
+
+typedef enum
+{
+ GOTO_LINE,
+ SEARCH
+} SearchMode;
+
+enum
+{
+ TARGET_URI_LIST = 100
+};
+
+struct _PlumaViewPrivate
+{
+ SearchMode search_mode;
+
+ GtkTextIter start_search_iter;
+
+ /* used to restore the search state if an
+ * incremental search is cancelled
+ */
+ gchar *old_search_text;
+ guint old_search_flags;
+
+ /* used to remeber the state of the last
+ * incremental search (the document search
+ * state may be changed by the search dialog)
+ */
+ guint search_flags;
+ gboolean wrap_around;
+
+ GtkWidget *search_window;
+ GtkWidget *search_entry;
+
+ guint typeselect_flush_timeout;
+ guint search_entry_changed_id;
+
+ gboolean disable_popdown;
+
+ GtkTextBuffer *current_buffer;
+};
+
+/* The search entry completion is shared among all the views */
+GtkListStore *search_completion_model = NULL;
+
+static void pluma_view_destroy (GtkObject *object);
+static void pluma_view_finalize (GObject *object);
+static gint pluma_view_focus_out (GtkWidget *widget,
+ GdkEventFocus *event);
+static gboolean pluma_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint timestamp);
+static void pluma_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint timestamp);
+static gboolean pluma_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint timestamp);
+static gboolean pluma_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event);
+
+static gboolean start_interactive_search (PlumaView *view);
+static gboolean start_interactive_goto_line (PlumaView *view);
+static gboolean reset_searched_text (PlumaView *view);
+
+static void hide_search_window (PlumaView *view,
+ gboolean cancel);
+
+
+static gint pluma_view_expose (GtkWidget *widget,
+ GdkEventExpose *event);
+static void search_highlight_updated_cb (PlumaDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ PlumaView *view);
+
+static void pluma_view_delete_from_cursor (GtkTextView *text_view,
+ GtkDeleteType type,
+ gint count);
+
+G_DEFINE_TYPE(PlumaView, pluma_view, GTK_TYPE_SOURCE_VIEW)
+
+/* Signals */
+enum
+{
+ START_INTERACTIVE_SEARCH,
+ START_INTERACTIVE_GOTO_LINE,
+ RESET_SEARCHED_TEXT,
+ DROP_URIS,
+ LAST_SIGNAL
+};
+
+static guint view_signals [LAST_SIGNAL] = { 0 };
+
+typedef enum
+{
+ PLUMA_SEARCH_ENTRY_NORMAL,
+ PLUMA_SEARCH_ENTRY_NOT_FOUND
+} PlumaSearchEntryBgColor;
+
+static void
+document_read_only_notify_handler (PlumaDocument *document,
+ GParamSpec *pspec,
+ PlumaView *view)
+{
+ pluma_debug (DEBUG_VIEW);
+
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (view),
+ !pluma_document_get_readonly (document));
+}
+
+static void
+pluma_view_class_init (PlumaViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
+
+ GtkBindingSet *binding_set;
+
+ gtkobject_class->destroy = pluma_view_destroy;
+ object_class->finalize = pluma_view_finalize;
+
+ widget_class->focus_out_event = pluma_view_focus_out;
+ widget_class->expose_event = pluma_view_expose;
+
+ /*
+ * Override the gtk_text_view_drag_motion and drag_drop
+ * functions to get URIs
+ *
+ * If the mime type is text/uri-list, then we will accept
+ * the potential drop, or request the data (depending on the
+ * function).
+ *
+ * If the drag context has any other mime type, then pass the
+ * information onto the GtkTextView's standard handlers.
+ * (widget_class->function_name).
+ *
+ * See bug #89881 for details
+ */
+ widget_class->drag_motion = pluma_view_drag_motion;
+ widget_class->drag_data_received = pluma_view_drag_data_received;
+ widget_class->drag_drop = pluma_view_drag_drop;
+ widget_class->button_press_event = pluma_view_button_press_event;
+ klass->start_interactive_search = start_interactive_search;
+ klass->start_interactive_goto_line = start_interactive_goto_line;
+ klass->reset_searched_text = reset_searched_text;
+
+ text_view_class->delete_from_cursor = pluma_view_delete_from_cursor;
+
+ view_signals[START_INTERACTIVE_SEARCH] =
+ g_signal_new ("start_interactive_search",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaViewClass, start_interactive_search),
+ NULL, NULL,
+ pluma_marshal_BOOLEAN__NONE,
+ G_TYPE_BOOLEAN, 0);
+
+ view_signals[START_INTERACTIVE_GOTO_LINE] =
+ g_signal_new ("start_interactive_goto_line",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaViewClass, start_interactive_goto_line),
+ NULL, NULL,
+ pluma_marshal_BOOLEAN__NONE,
+ G_TYPE_BOOLEAN, 0);
+
+ view_signals[RESET_SEARCHED_TEXT] =
+ g_signal_new ("reset_searched_text",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaViewClass, reset_searched_text),
+ NULL, NULL,
+ pluma_marshal_BOOLEAN__NONE,
+ G_TYPE_BOOLEAN, 0);
+
+ /* A new signal DROP_URIS has been added to allow plugins to intercept
+ * the default dnd behaviour of 'text/uri-list'. PlumaView now handles
+ * dnd in the default handlers of drag_drop, drag_motion and
+ * drag_data_received. The view emits drop_uris from drag_data_received
+ * if valid uris have been dropped. Plugins should connect to
+ * drag_motion, drag_drop and drag_data_received to change this
+ * default behaviour. They should _NOT_ use this signal because this
+ * will not prevent pluma from loading the uri
+ */
+ view_signals[DROP_URIS] =
+ g_signal_new ("drop_uris",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PlumaViewClass, drop_uris),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, G_TYPE_STRV);
+
+ g_type_class_add_private (klass, sizeof (PlumaViewPrivate));
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_k,
+ GDK_CONTROL_MASK,
+ "start_interactive_search", 0);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_i,
+ GDK_CONTROL_MASK,
+ "start_interactive_goto_line", 0);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_k,
+ GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+ "reset_searched_text", 0);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_d,
+ GDK_CONTROL_MASK,
+ "delete_from_cursor", 2,
+ G_TYPE_ENUM, GTK_DELETE_PARAGRAPHS,
+ G_TYPE_INT, 1);
+}
+
+static void
+current_buffer_removed (PlumaView *view)
+{
+ if (view->priv->current_buffer)
+ {
+ g_signal_handlers_disconnect_by_func (view->priv->current_buffer,
+ document_read_only_notify_handler,
+ view);
+ g_signal_handlers_disconnect_by_func (view->priv->current_buffer,
+ search_highlight_updated_cb,
+ view);
+
+ g_object_unref (view->priv->current_buffer);
+ view->priv->current_buffer = NULL;
+ }
+}
+
+static void
+on_notify_buffer_cb (PlumaView *view,
+ GParamSpec *arg1,
+ gpointer userdata)
+{
+ GtkTextBuffer *buffer;
+
+ current_buffer_removed (view);
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ if (buffer == NULL || !PLUMA_IS_DOCUMENT (buffer))
+ return;
+
+ view->priv->current_buffer = g_object_ref (buffer);
+ g_signal_connect (buffer,
+ "notify::read-only",
+ G_CALLBACK (document_read_only_notify_handler),
+ view);
+
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (view),
+ !pluma_document_get_readonly (PLUMA_DOCUMENT (buffer)));
+
+ g_signal_connect (buffer,
+ "search_highlight_updated",
+ G_CALLBACK (search_highlight_updated_cb),
+ view);
+}
+
+static void
+pluma_view_init (PlumaView *view)
+{
+ GtkTargetList *tl;
+
+ pluma_debug (DEBUG_VIEW);
+
+ view->priv = PLUMA_VIEW_GET_PRIVATE (view);
+
+ /*
+ * Set tab, fonts, wrap mode, colors, etc. according
+ * to preferences
+ */
+ if (!pluma_prefs_manager_get_use_default_font ())
+ {
+ gchar *editor_font;
+
+ editor_font = pluma_prefs_manager_get_editor_font ();
+
+ pluma_view_set_font (view, FALSE, editor_font);
+
+ g_free (editor_font);
+ }
+ else
+ {
+ pluma_view_set_font (view, TRUE, NULL);
+ }
+
+ g_object_set (G_OBJECT (view),
+ "wrap_mode", pluma_prefs_manager_get_wrap_mode (),
+ "show_line_numbers", pluma_prefs_manager_get_display_line_numbers (),
+ "auto_indent", pluma_prefs_manager_get_auto_indent (),
+ "tab_width", pluma_prefs_manager_get_tabs_size (),
+ "insert_spaces_instead_of_tabs", pluma_prefs_manager_get_insert_spaces (),
+ "show_right_margin", pluma_prefs_manager_get_display_right_margin (),
+ "right_margin_position", pluma_prefs_manager_get_right_margin_position (),
+ "highlight_current_line", pluma_prefs_manager_get_highlight_current_line (),
+ "smart_home_end", pluma_prefs_manager_get_smart_home_end (),
+ "indent_on_tab", TRUE,
+ NULL);
+
+ view->priv->typeselect_flush_timeout = 0;
+ view->priv->wrap_around = TRUE;
+
+ /* Drag and drop support */
+ tl = gtk_drag_dest_get_target_list (GTK_WIDGET (view));
+
+ if (tl != NULL)
+ gtk_target_list_add_uri_targets (tl, TARGET_URI_LIST);
+
+ /* Act on buffer change */
+ g_signal_connect (view,
+ "notify::buffer",
+ G_CALLBACK (on_notify_buffer_cb),
+ NULL);
+}
+
+static void
+pluma_view_destroy (GtkObject *object)
+{
+ PlumaView *view;
+
+ view = PLUMA_VIEW (object);
+
+ if (view->priv->search_window != NULL)
+ {
+ gtk_widget_destroy (view->priv->search_window);
+ view->priv->search_window = NULL;
+ view->priv->search_entry = NULL;
+
+ if (view->priv->typeselect_flush_timeout != 0)
+ {
+ g_source_remove (view->priv->typeselect_flush_timeout);
+ view->priv->typeselect_flush_timeout = 0;
+ }
+ }
+
+ /* Disconnect notify buffer because the destroy of the textview will
+ set the buffer to NULL, and we call get_buffer in the notify which
+ would reinstate a GtkTextBuffer which we don't want */
+ current_buffer_removed (view);
+ g_signal_handlers_disconnect_by_func (view, on_notify_buffer_cb, NULL);
+
+ (* GTK_OBJECT_CLASS (pluma_view_parent_class)->destroy) (object);
+}
+
+static void
+pluma_view_finalize (GObject *object)
+{
+ PlumaView *view;
+
+ view = PLUMA_VIEW (object);
+
+ current_buffer_removed (view);
+
+ g_free (view->priv->old_search_text);
+
+ (* G_OBJECT_CLASS (pluma_view_parent_class)->finalize) (object);
+}
+
+static gint
+pluma_view_focus_out (GtkWidget *widget, GdkEventFocus *event)
+{
+ PlumaView *view = PLUMA_VIEW (widget);
+
+ gtk_widget_queue_draw (widget);
+
+ /* hide interactive search dialog */
+ if (view->priv->search_window != NULL)
+ hide_search_window (view, FALSE);
+
+ (* GTK_WIDGET_CLASS (pluma_view_parent_class)->focus_out_event) (widget, event);
+
+ return FALSE;
+}
+
+/**
+ * pluma_view_new:
+ * @doc: a #PlumaDocument
+ *
+ * Creates a new #PlumaView object displaying the @doc document.
+ * @doc cannot be NULL.
+ *
+ * Return value: a new #PlumaView
+ **/
+GtkWidget *
+pluma_view_new (PlumaDocument *doc)
+{
+ GtkWidget *view;
+
+ pluma_debug_message (DEBUG_VIEW, "START");
+
+ g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL);
+
+ view = GTK_WIDGET (g_object_new (PLUMA_TYPE_VIEW, "buffer", doc, NULL));
+
+ pluma_debug_message (DEBUG_VIEW, "END: %d", G_OBJECT (view)->ref_count);
+
+ gtk_widget_show_all (view);
+
+ return view;
+}
+
+void
+pluma_view_cut_clipboard (PlumaView *view)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ pluma_debug (DEBUG_VIEW);
+
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ g_return_if_fail (buffer != NULL);
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
+ GDK_SELECTION_CLIPBOARD);
+
+ /* FIXME: what is default editability of a buffer? */
+ gtk_text_buffer_cut_clipboard (buffer,
+ clipboard,
+ !pluma_document_get_readonly (
+ PLUMA_DOCUMENT (buffer)));
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ gtk_text_buffer_get_insert (buffer),
+ PLUMA_VIEW_SCROLL_MARGIN,
+ FALSE,
+ 0.0,
+ 0.0);
+}
+
+void
+pluma_view_copy_clipboard (PlumaView *view)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ pluma_debug (DEBUG_VIEW);
+
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ g_return_if_fail (buffer != NULL);
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
+ GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+
+ /* on copy do not scroll, we are already on screen */
+}
+
+void
+pluma_view_paste_clipboard (PlumaView *view)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ pluma_debug (DEBUG_VIEW);
+
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ g_return_if_fail (buffer != NULL);
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
+ GDK_SELECTION_CLIPBOARD);
+
+ /* FIXME: what is default editability of a buffer? */
+ gtk_text_buffer_paste_clipboard (buffer,
+ clipboard,
+ NULL,
+ !pluma_document_get_readonly (
+ PLUMA_DOCUMENT (buffer)));
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ gtk_text_buffer_get_insert (buffer),
+ PLUMA_VIEW_SCROLL_MARGIN,
+ FALSE,
+ 0.0,
+ 0.0);
+}
+
+/**
+ * pluma_view_delete_selection:
+ * @view: a #PlumaView
+ *
+ * Deletes the text currently selected in the #GtkTextBuffer associated
+ * to the view and scroll to the cursor position.
+ **/
+void
+pluma_view_delete_selection (PlumaView *view)
+{
+ GtkTextBuffer *buffer = NULL;
+
+ pluma_debug (DEBUG_VIEW);
+
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ g_return_if_fail (buffer != NULL);
+
+ /* FIXME: what is default editability of a buffer? */
+ gtk_text_buffer_delete_selection (buffer,
+ TRUE,
+ !pluma_document_get_readonly (
+ PLUMA_DOCUMENT (buffer)));
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ gtk_text_buffer_get_insert (buffer),
+ PLUMA_VIEW_SCROLL_MARGIN,
+ FALSE,
+ 0.0,
+ 0.0);
+}
+
+/**
+ * pluma_view_select_all:
+ * @view: a #PlumaView
+ *
+ * Selects all the text displayed in the @view.
+ **/
+void
+pluma_view_select_all (PlumaView *view)
+{
+ GtkTextBuffer *buffer = NULL;
+ GtkTextIter start, end;
+
+ pluma_debug (DEBUG_VIEW);
+
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ g_return_if_fail (buffer != NULL);
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ gtk_text_buffer_select_range (buffer, &start, &end);
+}
+
+/**
+ * pluma_view_scroll_to_cursor:
+ * @view: a #PlumaView
+ *
+ * Scrolls the @view to the cursor position.
+ **/
+void
+pluma_view_scroll_to_cursor (PlumaView *view)
+{
+ GtkTextBuffer* buffer = NULL;
+
+ pluma_debug (DEBUG_VIEW);
+
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ g_return_if_fail (buffer != NULL);
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ gtk_text_buffer_get_insert (buffer),
+ 0.25,
+ FALSE,
+ 0.0,
+ 0.0);
+}
+
+/**
+ * pluma_view_set_font:
+ * @view: a #PlumaView
+ * @def: whether to reset the default font
+ * @font_name: the name of the font to use
+ *
+ * If @def is #TRUE, resets the font of the @view to the default font
+ * otherwise sets it to @font_name.
+ **/
+void
+pluma_view_set_font (PlumaView *view,
+ gboolean def,
+ const gchar *font_name)
+{
+ PangoFontDescription *font_desc = NULL;
+
+ pluma_debug (DEBUG_VIEW);
+
+ g_return_if_fail (PLUMA_IS_VIEW (view));
+
+ if (def)
+ {
+ gchar *font;
+
+ font = pluma_prefs_manager_get_system_font ();
+ font_desc = pango_font_description_from_string (font);
+ g_free (font);
+ }
+ else
+ {
+ g_return_if_fail (font_name != NULL);
+
+ font_desc = pango_font_description_from_string (font_name);
+ }
+
+ g_return_if_fail (font_desc != NULL);
+
+ gtk_widget_modify_font (GTK_WIDGET (view), font_desc);
+
+ pango_font_description_free (font_desc);
+}
+
+static void
+add_search_completion_entry (const gchar *str)
+{
+ gchar *text;
+ gboolean valid;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ if (str == NULL)
+ return;
+
+ text = pluma_utils_unescape_search_text (str);
+
+ if (g_utf8_strlen (text, -1) < MIN_SEARCH_COMPLETION_KEY_LEN)
+ {
+ g_free (text);
+ return;
+ }
+
+ g_return_if_fail (GTK_IS_TREE_MODEL (search_completion_model));
+
+ model = GTK_TREE_MODEL (search_completion_model);
+
+ /* Get the first iter in the list */
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (valid)
+ {
+ /* Walk through the list, reading each row */
+ gchar *str_data;
+
+ gtk_tree_model_get (model,
+ &iter,
+ 0,
+ &str_data,
+ -1);
+
+ if (strcmp (text, str_data) == 0)
+ {
+ g_free (text);
+ g_free (str_data);
+ gtk_list_store_move_after (GTK_LIST_STORE (model),
+ &iter,
+ NULL);
+
+ return;
+ }
+
+ g_free (str_data);
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (model),
+ &iter,
+ 0,
+ text,
+ -1);
+
+ g_free (text);
+}
+
+static void
+set_entry_background (GtkWidget *entry,
+ PlumaSearchEntryBgColor col)
+{
+ if (col == PLUMA_SEARCH_ENTRY_NOT_FOUND)
+ {
+ GdkColor red;
+ GdkColor white;
+
+ /* FIXME: a11y and theme */
+
+ gdk_color_parse ("#FF6666", &red);
+ gdk_color_parse ("white", &white);
+
+ gtk_widget_modify_base (entry,
+ GTK_STATE_NORMAL,
+ &red);
+ gtk_widget_modify_text (entry,
+ GTK_STATE_NORMAL,
+ &white);
+ }
+ else /* reset */
+ {
+ gtk_widget_modify_base (entry,
+ GTK_STATE_NORMAL,
+ NULL);
+ gtk_widget_modify_text (entry,
+ GTK_STATE_NORMAL,
+ NULL);
+ }
+}
+
+static gboolean
+run_search (PlumaView *view,
+ const gchar *entry_text,
+ gboolean search_backward,
+ gboolean wrap_around,
+ gboolean typing)
+{
+ GtkTextIter start_iter;
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found = FALSE;
+ PlumaDocument *doc;
+
+ g_return_val_if_fail (view->priv->search_mode == SEARCH, FALSE);
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ start_iter = view->priv->start_search_iter;
+
+ if (*entry_text != '\0')
+ {
+ if (!search_backward)
+ {
+ if (!typing)
+ {
+ /* forward and _NOT_ typing */
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start_iter,
+ &match_end);
+
+ gtk_text_iter_order (&match_end, &start_iter);
+ }
+
+ /* run search */
+ found = pluma_document_search_forward (doc,
+ &start_iter,
+ NULL,
+ &match_start,
+ &match_end);
+ }
+ else if (!typing)
+ {
+ /* backward and not typing */
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start_iter,
+ &match_end);
+
+ /* run search */
+ found = pluma_document_search_backward (doc,
+ NULL,
+ &start_iter,
+ &match_start,
+ &match_end);
+ }
+ else
+ {
+ /* backward (while typing) */
+ g_return_val_if_reached (FALSE);
+
+ }
+
+ if (!found && wrap_around)
+ {
+ if (!search_backward)
+ found = pluma_document_search_forward (doc,
+ NULL,
+ NULL, /* FIXME: set the end_inter */
+ &match_start,
+ &match_end);
+ else
+ found = pluma_document_search_backward (doc,
+ NULL, /* FIXME: set the start_inter */
+ NULL,
+ &match_start,
+ &match_end);
+ }
+ }
+ else
+ {
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start_iter,
+ NULL);
+ }
+
+ if (found)
+ {
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc),
+ &match_start);
+
+ gtk_text_buffer_move_mark_by_name (GTK_TEXT_BUFFER (doc),
+ "selection_bound", &match_end);
+ }
+ else
+ {
+ if (typing)
+ {
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc),
+ &view->priv->start_search_iter);
+ }
+ }
+
+ if (found || (*entry_text == '\0'))
+ {
+ pluma_view_scroll_to_cursor (view);
+
+ set_entry_background (view->priv->search_entry,
+ PLUMA_SEARCH_ENTRY_NORMAL);
+ }
+ else
+ {
+ set_entry_background (view->priv->search_entry,
+ PLUMA_SEARCH_ENTRY_NOT_FOUND);
+ }
+
+ return found;
+}
+
+/* Cut and paste from gtkwindow.c */
+static void
+send_focus_change (GtkWidget *widget,
+ gboolean in)
+{
+ GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
+
+ g_object_ref (widget);
+
+ if (in)
+ GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
+ else
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
+
+ fevent->focus_change.type = GDK_FOCUS_CHANGE;
+ fevent->focus_change.window = g_object_ref (widget->window);
+ fevent->focus_change.in = in;
+
+ gtk_widget_event (widget, fevent);
+
+ g_object_notify (G_OBJECT (widget), "has-focus");
+
+ g_object_unref (widget);
+ gdk_event_free (fevent);
+}
+
+static void
+hide_search_window (PlumaView *view, gboolean cancel)
+{
+ if (view->priv->disable_popdown)
+ return;
+
+ if (view->priv->search_entry_changed_id != 0)
+ {
+ g_signal_handler_disconnect (view->priv->search_entry,
+ view->priv->search_entry_changed_id);
+ view->priv->search_entry_changed_id = 0;
+ }
+
+ if (view->priv->typeselect_flush_timeout != 0)
+ {
+ g_source_remove (view->priv->typeselect_flush_timeout);
+ view->priv->typeselect_flush_timeout = 0;
+ }
+
+ /* send focus-in event */
+ send_focus_change (GTK_WIDGET (view->priv->search_entry), FALSE);
+ gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), TRUE);
+ gtk_widget_hide (view->priv->search_window);
+
+ if (cancel)
+ {
+ GtkTextBuffer *buffer;
+
+ buffer = GTK_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ gtk_text_buffer_place_cursor (buffer, &view->priv->start_search_iter);
+
+ pluma_view_scroll_to_cursor (view);
+ }
+
+ /* make sure a focus event is sent for the edit area */
+ send_focus_change (GTK_WIDGET (view), TRUE);
+}
+
+static gboolean
+search_entry_flush_timeout (PlumaView *view)
+{
+ GDK_THREADS_ENTER ();
+
+ view->priv->typeselect_flush_timeout = 0;
+ hide_search_window (view, FALSE);
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+update_search_window_position (PlumaView *view)
+{
+ gint x, y;
+ gint view_x, view_y;
+ GdkWindow *view_window = GTK_WIDGET (view)->window;
+
+ gtk_widget_realize (view->priv->search_window);
+
+ gdk_window_get_origin (view_window, &view_x, &view_y);
+
+ x = MAX (12, view_x + 12);
+ y = MAX (12, view_y - 12);
+
+ gtk_window_move (GTK_WINDOW (view->priv->search_window), x, y);
+}
+
+static gboolean
+search_window_delete_event (GtkWidget *widget,
+ GdkEventAny *event,
+ PlumaView *view)
+{
+ hide_search_window (view, FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+search_window_button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ PlumaView *view)
+{
+ hide_search_window (view, FALSE);
+
+ gtk_propagate_event (GTK_WIDGET (view), (GdkEvent *)event);
+
+ return FALSE;
+}
+
+static void
+search_again (PlumaView *view,
+ gboolean search_backward)
+{
+ const gchar *entry_text;
+
+ g_return_if_fail (view->priv->search_mode == SEARCH);
+
+ /* SEARCH mode */
+ /* renew the flush timeout */
+ if (view->priv->typeselect_flush_timeout != 0)
+ {
+ g_source_remove (view->priv->typeselect_flush_timeout);
+ view->priv->typeselect_flush_timeout =
+ g_timeout_add (PLUMA_VIEW_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc)search_entry_flush_timeout,
+ view);
+ }
+
+ entry_text = gtk_entry_get_text (GTK_ENTRY (view->priv->search_entry));
+
+ add_search_completion_entry (entry_text);
+
+ run_search (view,
+ entry_text,
+ search_backward,
+ view->priv->wrap_around,
+ FALSE);
+}
+
+static gboolean
+search_window_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event,
+ PlumaView *view)
+{
+ gboolean retval = FALSE;
+
+ if (view->priv->search_mode == GOTO_LINE)
+ return retval;
+
+ /* SEARCH mode */
+ if (event->direction == GDK_SCROLL_UP)
+ {
+ search_again (view, TRUE);
+ retval = TRUE;
+ }
+ else if (event->direction == GDK_SCROLL_DOWN)
+ {
+ search_again (view, FALSE);
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+static gboolean
+search_window_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ PlumaView *view)
+{
+ gboolean retval = FALSE;
+ guint modifiers;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ /* Close window */
+ if (event->keyval == GDK_Tab)
+ {
+ hide_search_window (view, FALSE);
+ retval = TRUE;
+ }
+
+ /* Close window and cancel the search */
+ if (event->keyval == GDK_Escape)
+ {
+ if (view->priv->search_mode == SEARCH)
+ {
+ PlumaDocument *doc;
+
+ /* restore document search so that Find Next does the right thing */
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ pluma_document_set_search_text (doc,
+ view->priv->old_search_text,
+ view->priv->old_search_flags);
+
+ }
+
+ hide_search_window (view, TRUE);
+ retval = TRUE;
+ }
+
+ if (view->priv->search_mode == GOTO_LINE)
+ return retval;
+
+ /* SEARCH mode */
+
+ /* select previous matching iter */
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+ {
+ search_again (view, TRUE);
+ retval = TRUE;
+ }
+
+ if (((event->state & modifiers) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) &&
+ (event->keyval == GDK_g || event->keyval == GDK_G))
+ {
+ search_again (view, TRUE);
+ retval = TRUE;
+ }
+
+ /* select next matching iter */
+ if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
+ {
+ search_again (view, FALSE);
+ retval = TRUE;
+ }
+
+ if (((event->state & modifiers) == GDK_CONTROL_MASK) &&
+ (event->keyval == GDK_g || event->keyval == GDK_G))
+ {
+ search_again (view, FALSE);
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+static void
+search_entry_activate (GtkEntry *entry,
+ PlumaView *view)
+{
+ hide_search_window (view, FALSE);
+}
+
+static void
+wrap_around_menu_item_toggled (GtkCheckMenuItem *checkmenuitem,
+ PlumaView *view)
+{
+ view->priv->wrap_around = gtk_check_menu_item_get_active (checkmenuitem);
+}
+
+static void
+match_entire_word_menu_item_toggled (GtkCheckMenuItem *checkmenuitem,
+ PlumaView *view)
+{
+ PLUMA_SEARCH_SET_ENTIRE_WORD (view->priv->search_flags,
+ gtk_check_menu_item_get_active (checkmenuitem));
+}
+
+static void
+match_case_menu_item_toggled (GtkCheckMenuItem *checkmenuitem,
+ PlumaView *view)
+{
+ PLUMA_SEARCH_SET_CASE_SENSITIVE (view->priv->search_flags,
+ gtk_check_menu_item_get_active (checkmenuitem));
+}
+
+static gboolean
+real_search_enable_popdown (gpointer data)
+{
+ PlumaView *view = (PlumaView *)data;
+
+ GDK_THREADS_ENTER ();
+
+ view->priv->disable_popdown = FALSE;
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+search_enable_popdown (GtkWidget *widget,
+ PlumaView *view)
+{
+ g_timeout_add (200, real_search_enable_popdown, view);
+
+ /* renew the flush timeout */
+ if (view->priv->typeselect_flush_timeout != 0)
+ g_source_remove (view->priv->typeselect_flush_timeout);
+
+ view->priv->typeselect_flush_timeout =
+ g_timeout_add (PLUMA_VIEW_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc)search_entry_flush_timeout,
+ view);
+}
+
+static void
+search_entry_populate_popup (GtkEntry *entry,
+ GtkMenu *menu,
+ PlumaView *view)
+{
+ GtkWidget *menu_item;
+
+ view->priv->disable_popdown = TRUE;
+ g_signal_connect (menu, "hide",
+ G_CALLBACK (search_enable_popdown), view);
+
+ if (view->priv->search_mode == GOTO_LINE)
+ return;
+
+ /* separator */
+ menu_item = gtk_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ /* create "Wrap Around" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Wrap Around"));
+ g_signal_connect (G_OBJECT (menu_item), "toggled",
+ G_CALLBACK (wrap_around_menu_item_toggled),
+ view);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
+ view->priv->wrap_around);
+ gtk_widget_show (menu_item);
+
+ /* create "Match Entire Word Only" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("Match _Entire Word Only"));
+ g_signal_connect (G_OBJECT (menu_item), "toggled",
+ G_CALLBACK (match_entire_word_menu_item_toggled),
+ view);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
+ PLUMA_SEARCH_IS_ENTIRE_WORD (view->priv->search_flags));
+ gtk_widget_show (menu_item);
+
+ /* create "Match Case" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Match Case"));
+ g_signal_connect (G_OBJECT (menu_item), "toggled",
+ G_CALLBACK (match_case_menu_item_toggled),
+ view);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
+ PLUMA_SEARCH_IS_CASE_SENSITIVE (view->priv->search_flags));
+ gtk_widget_show (menu_item);
+}
+
+static void
+search_entry_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ PlumaView *view)
+{
+ if (view->priv->search_mode == GOTO_LINE)
+ {
+ gunichar c;
+ const gchar *p;
+ const gchar *end;
+ const gchar *next;
+
+ p = text;
+ end = text + length;
+
+ if (p == end)
+ return;
+
+ c = g_utf8_get_char (p);
+
+ if (((c == '-' || c == '+') && *position == 0) ||
+ (c == ':' && *position != 0))
+ {
+ gchar *s = NULL;
+
+ if (c == ':')
+ {
+ s = gtk_editable_get_chars (editable, 0, -1);
+ s = g_utf8_strchr (s, -1, ':');
+ }
+
+ if (s == NULL || s == p)
+ {
+ next = g_utf8_next_char (p);
+ p = next;
+ }
+
+ g_free (s);
+ }
+
+ while (p != end)
+ {
+ next = g_utf8_next_char (p);
+
+ c = g_utf8_get_char (p);
+
+ if (!g_unichar_isdigit (c)) {
+ g_signal_stop_emission_by_name (editable, "insert_text");
+ gtk_widget_error_bell (view->priv->search_entry);
+ break;
+ }
+
+ p = next;
+ }
+ }
+ else
+ {
+ /* SEARCH mode */
+ static gboolean insert_text = FALSE;
+ gchar *escaped_text;
+ gint new_len;
+
+ pluma_debug_message (DEBUG_SEARCH, "Text: %s", text);
+
+ /* To avoid recursive behavior */
+ if (insert_text)
+ return;
+
+ escaped_text = pluma_utils_escape_search_text (text);
+
+ pluma_debug_message (DEBUG_SEARCH, "Escaped Text: %s", escaped_text);
+
+ new_len = strlen (escaped_text);
+
+ if (new_len == length)
+ {
+ g_free (escaped_text);
+ return;
+ }
+
+ insert_text = TRUE;
+
+ g_signal_stop_emission_by_name (editable, "insert_text");
+
+ gtk_editable_insert_text (editable, escaped_text, new_len, position);
+
+ insert_text = FALSE;
+
+ g_free (escaped_text);
+ }
+}
+
+static void
+customize_for_search_mode (PlumaView *view)
+{
+ if (view->priv->search_mode == SEARCH)
+ {
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (view->priv->search_entry),
+ GTK_ENTRY_ICON_PRIMARY,
+ GTK_STOCK_FIND);
+
+ gtk_widget_set_tooltip_text (view->priv->search_entry,
+ _("String you want to search for"));
+ }
+ else
+ {
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (view->priv->search_entry),
+ GTK_ENTRY_ICON_PRIMARY,
+ GTK_STOCK_JUMP_TO);
+
+ gtk_widget_set_tooltip_text (view->priv->search_entry,
+ _("Line you want to move the cursor to"));
+ }
+}
+
+static gboolean
+completion_func (GtkEntryCompletion *completion,
+ const char *key,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *item = NULL;
+ gboolean ret = FALSE;
+ GtkTreeModel *model;
+ PlumaViewPrivate *priv = (PlumaViewPrivate *)data;
+ const gchar *real_key;
+
+ if (priv->search_mode == GOTO_LINE)
+ return FALSE;
+
+ real_key = gtk_entry_get_text (GTK_ENTRY (gtk_entry_completion_get_entry (completion)));
+
+ if (g_utf8_strlen (real_key, -1) <= MIN_SEARCH_COMPLETION_KEY_LEN)
+ return FALSE;
+
+ model = gtk_entry_completion_get_model (completion);
+ g_return_val_if_fail (gtk_tree_model_get_column_type (model, 0) == G_TYPE_STRING,
+ FALSE);
+
+ gtk_tree_model_get (model,
+ iter,
+ 0,
+ &item,
+ -1);
+
+ if (item == NULL)
+ return FALSE;
+
+ if (PLUMA_SEARCH_IS_CASE_SENSITIVE (priv->search_flags))
+ {
+ if (!strncmp (real_key, item, strlen (real_key)))
+ ret = TRUE;
+ }
+ else
+ {
+ gchar *normalized_string;
+ gchar *case_normalized_string;
+
+ normalized_string = g_utf8_normalize (item, -1, G_NORMALIZE_ALL);
+ case_normalized_string = g_utf8_casefold (normalized_string, -1);
+
+ if (!strncmp (key, case_normalized_string, strlen (key)))
+ ret = TRUE;
+
+ g_free (normalized_string);
+ g_free (case_normalized_string);
+
+ }
+
+ g_free (item);
+
+ return ret;
+}
+
+static void
+ensure_search_window (PlumaView *view)
+{
+ GtkWidget *frame;
+ GtkWidget *vbox;
+ GtkWidget *toplevel;
+ GtkEntryCompletion *completion;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+
+ if (view->priv->search_window != NULL)
+ {
+ if (GTK_WINDOW (toplevel)->group)
+ gtk_window_group_add_window (GTK_WINDOW (toplevel)->group,
+ GTK_WINDOW (view->priv->search_window));
+ else if (GTK_WINDOW (view->priv->search_window)->group)
+ gtk_window_group_remove_window (GTK_WINDOW (view->priv->search_window)->group,
+ GTK_WINDOW (view->priv->search_window));
+
+ customize_for_search_mode (view);
+
+ return;
+ }
+
+ view->priv->search_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ if (GTK_WINDOW (toplevel)->group)
+ gtk_window_group_add_window (GTK_WINDOW (toplevel)->group,
+ GTK_WINDOW (view->priv->search_window));
+
+ gtk_window_set_modal (GTK_WINDOW (view->priv->search_window), TRUE);
+
+ g_signal_connect (view->priv->search_window, "delete_event",
+ G_CALLBACK (search_window_delete_event),
+ view);
+ g_signal_connect (view->priv->search_window, "key_press_event",
+ G_CALLBACK (search_window_key_press_event),
+ view);
+ g_signal_connect (view->priv->search_window, "button_press_event",
+ G_CALLBACK (search_window_button_press_event),
+ view);
+ g_signal_connect (view->priv->search_window, "scroll_event",
+ G_CALLBACK (search_window_scroll_event),
+ view);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+ gtk_widget_show (frame);
+ gtk_container_add (GTK_CONTAINER (view->priv->search_window), frame);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
+
+ /* add entry */
+ view->priv->search_entry = gtk_entry_new ();
+ gtk_widget_show (view->priv->search_entry);
+
+ g_signal_connect (view->priv->search_entry, "populate_popup",
+ G_CALLBACK (search_entry_populate_popup),
+ view);
+ g_signal_connect (view->priv->search_entry, "activate",
+ G_CALLBACK (search_entry_activate),
+ view);
+ /* CHECK: do we really need to connect to preedit too? -- Paolo
+ g_signal_connect (GTK_ENTRY (view->priv->search_entry)->im_context, "preedit-changed",
+ G_CALLBACK (gtk_view_search_preedit_changed),
+ view);
+ */
+ g_signal_connect (view->priv->search_entry,
+ "insert_text",
+ G_CALLBACK (search_entry_insert_text),
+ view);
+
+ gtk_container_add (GTK_CONTAINER (vbox),
+ view->priv->search_entry);
+
+ if (search_completion_model == NULL)
+ {
+ /* Create a tree model and use it as the completion model */
+ search_completion_model = gtk_list_store_new (1, G_TYPE_STRING);
+ }
+
+ /* Create the completion object for the search entry */
+ completion = gtk_entry_completion_new ();
+ gtk_entry_completion_set_model (completion,
+ GTK_TREE_MODEL (search_completion_model));
+
+ /* Use model column 0 as the text column */
+ gtk_entry_completion_set_text_column (completion, 0);
+
+ gtk_entry_completion_set_minimum_key_length (completion,
+ MIN_SEARCH_COMPLETION_KEY_LEN);
+
+ gtk_entry_completion_set_popup_completion (completion, FALSE);
+ gtk_entry_completion_set_inline_completion (completion, TRUE);
+
+ gtk_entry_completion_set_match_func (completion,
+ completion_func,
+ view->priv,
+ NULL);
+
+ /* Assign the completion to the entry */
+ gtk_entry_set_completion (GTK_ENTRY (view->priv->search_entry),
+ completion);
+ g_object_unref (completion);
+
+ gtk_widget_realize (view->priv->search_entry);
+
+ customize_for_search_mode (view);
+}
+
+static gboolean
+get_selected_text (GtkTextBuffer *doc, gchar **selected_text, gint *len)
+{
+ GtkTextIter start, end;
+
+ g_return_val_if_fail (selected_text != NULL, FALSE);
+ g_return_val_if_fail (*selected_text == NULL, FALSE);
+
+ if (!gtk_text_buffer_get_selection_bounds (doc, &start, &end))
+ {
+ if (len != NULL)
+ len = 0;
+
+ return FALSE;
+ }
+
+ *selected_text = gtk_text_buffer_get_slice (doc, &start, &end, TRUE);
+
+ if (len != NULL)
+ *len = g_utf8_strlen (*selected_text, -1);
+
+ return TRUE;
+}
+
+static void
+init_search_entry (PlumaView *view)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ if (view->priv->search_mode == GOTO_LINE)
+ {
+ gint line;
+ gchar *line_str;
+
+ line = gtk_text_iter_get_line (&view->priv->start_search_iter);
+
+ line_str = g_strdup_printf ("%d", line + 1);
+
+ gtk_entry_set_text (GTK_ENTRY (view->priv->search_entry),
+ line_str);
+
+ g_free (line_str);
+
+ return;
+ }
+ else
+ {
+ /* SEARCH mode */
+ gboolean selection_exists;
+ gchar *find_text = NULL;
+ gchar *old_find_text;
+ guint old_find_flags = 0;
+ gint sel_len = 0;
+
+ g_free (view->priv->old_search_text);
+
+ old_find_text = pluma_document_get_search_text (PLUMA_DOCUMENT (buffer),
+ &old_find_flags);
+ if (old_find_text != NULL)
+ {
+ view->priv->old_search_text = old_find_text;
+ add_search_completion_entry (old_find_text);
+ }
+
+ if (old_find_flags != 0)
+ {
+ view->priv->old_search_flags = old_find_flags;
+ }
+
+ selection_exists = get_selected_text (buffer,
+ &find_text,
+ &sel_len);
+
+ if (selection_exists && (find_text != NULL) && (sel_len <= 160))
+ {
+ gtk_entry_set_text (GTK_ENTRY (view->priv->search_entry),
+ find_text);
+ }
+ else
+ {
+ gtk_entry_set_text (GTK_ENTRY (view->priv->search_entry),
+ "");
+ }
+
+ g_free (find_text);
+ }
+}
+
+static void
+search_init (GtkWidget *entry,
+ PlumaView *view)
+{
+ PlumaDocument *doc;
+ const gchar *entry_text;
+
+ /* renew the flush timeout */
+ if (view->priv->typeselect_flush_timeout != 0)
+ {
+ g_source_remove (view->priv->typeselect_flush_timeout);
+ view->priv->typeselect_flush_timeout =
+ g_timeout_add (PLUMA_VIEW_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc)search_entry_flush_timeout,
+ view);
+ }
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ entry_text = gtk_entry_get_text (GTK_ENTRY (entry));
+
+ if (view->priv->search_mode == SEARCH)
+ {
+ gchar *search_text;
+ guint search_flags;
+
+ search_text = pluma_document_get_search_text (doc, &search_flags);
+
+ if ((search_text == NULL) ||
+ (strcmp (search_text, entry_text) != 0) ||
+ search_flags != view->priv->search_flags)
+ {
+ pluma_document_set_search_text (doc,
+ entry_text,
+ view->priv->search_flags);
+ }
+
+ g_free (search_text);
+
+ run_search (view,
+ entry_text,
+ FALSE,
+ view->priv->wrap_around,
+ TRUE);
+ }
+ else
+ {
+ if (*entry_text != '\0')
+ {
+ gboolean moved, moved_offset;
+ gint line;
+ gint offset_line = 0;
+ gint line_offset = 0;
+ gchar **split_text = NULL;
+ const gchar *text;
+
+ split_text = g_strsplit (entry_text, ":", -1);
+
+ if (g_strv_length (split_text) > 1)
+ {
+ text = split_text[0];
+ }
+ else
+ {
+ text = entry_text;
+ }
+
+ if (*text == '-')
+ {
+ gint cur_line = gtk_text_iter_get_line (&view->priv->start_search_iter);
+
+ if (*(text + 1) != '\0')
+ offset_line = MAX (atoi (text + 1), 0);
+
+ line = MAX (cur_line - offset_line, 0);
+ }
+ else if (*entry_text == '+')
+ {
+ gint cur_line = gtk_text_iter_get_line (&view->priv->start_search_iter);
+
+ if (*(text + 1) != '\0')
+ offset_line = MAX (atoi (text + 1), 0);
+
+ line = cur_line + offset_line;
+ }
+ else
+ {
+ line = MAX (atoi (text) - 1, 0);
+ }
+
+ if (split_text[1] != NULL)
+ {
+ line_offset = atoi (split_text[1]);
+ }
+
+ g_strfreev (split_text);
+
+ moved = pluma_document_goto_line (doc, line);
+ moved_offset = pluma_document_goto_line_offset (doc, line,
+ line_offset);
+
+ pluma_view_scroll_to_cursor (view);
+
+ if (!moved || !moved_offset)
+ set_entry_background (view->priv->search_entry,
+ PLUMA_SEARCH_ENTRY_NOT_FOUND);
+ else
+ set_entry_background (view->priv->search_entry,
+ PLUMA_SEARCH_ENTRY_NORMAL);
+ }
+ }
+}
+
+static gboolean
+start_interactive_search_real (PlumaView *view)
+{
+ GtkTextBuffer *buffer;
+
+ if ((view->priv->search_window != NULL) &&
+ GTK_WIDGET_VISIBLE (view->priv->search_window))
+ return TRUE;
+
+ if (!GTK_WIDGET_HAS_FOCUS (view))
+ return FALSE;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ if (view->priv->search_mode == SEARCH)
+ gtk_text_buffer_get_selection_bounds (buffer, &view->priv->start_search_iter, NULL);
+ else
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &view->priv->start_search_iter,
+ gtk_text_buffer_get_insert (buffer));
+
+ ensure_search_window (view);
+
+ /* done, show it */
+ update_search_window_position (view);
+ gtk_widget_show (view->priv->search_window);
+
+ if (view->priv->search_entry_changed_id == 0)
+ {
+ view->priv->search_entry_changed_id =
+ g_signal_connect (view->priv->search_entry,
+ "changed",
+ G_CALLBACK (search_init),
+ view);
+ }
+
+ init_search_entry (view);
+
+ view->priv->typeselect_flush_timeout =
+ g_timeout_add (PLUMA_VIEW_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) search_entry_flush_timeout,
+ view);
+
+ gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
+ gtk_widget_grab_focus (view->priv->search_entry);
+
+ send_focus_change (view->priv->search_entry, TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+reset_searched_text (PlumaView *view)
+{
+ PlumaDocument *doc;
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ pluma_document_set_search_text (doc, "", PLUMA_SEARCH_DONT_SET_FLAGS);
+
+ return TRUE;
+}
+
+static gboolean
+start_interactive_search (PlumaView *view)
+{
+ view->priv->search_mode = SEARCH;
+
+ return start_interactive_search_real (view);
+}
+
+static gboolean
+start_interactive_goto_line (PlumaView *view)
+{
+ view->priv->search_mode = GOTO_LINE;
+
+ return start_interactive_search_real (view);
+}
+
+static gint
+pluma_view_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GtkTextView *text_view;
+ PlumaDocument *doc;
+
+ text_view = GTK_TEXT_VIEW (widget);
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (text_view));
+
+ if ((event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)) &&
+ pluma_document_get_enable_search_highlighting (doc))
+ {
+ GdkRectangle visible_rect;
+ GtkTextIter iter1, iter2;
+
+ gtk_text_view_get_visible_rect (text_view, &visible_rect);
+ gtk_text_view_get_line_at_y (text_view, &iter1,
+ visible_rect.y, NULL);
+ gtk_text_view_get_line_at_y (text_view, &iter2,
+ visible_rect.y
+ + visible_rect.height, NULL);
+ gtk_text_iter_forward_line (&iter2);
+
+ _pluma_document_search_region (doc,
+ &iter1,
+ &iter2);
+ }
+
+ return (* GTK_WIDGET_CLASS (pluma_view_parent_class)->expose_event)(widget, event);
+}
+
+static GdkAtom
+drag_get_uri_target (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ GdkAtom target;
+ GtkTargetList *tl;
+
+ tl = gtk_target_list_new (NULL, 0);
+ gtk_target_list_add_uri_targets (tl, 0);
+
+ target = gtk_drag_dest_find_target (widget, context, tl);
+ gtk_target_list_unref (tl);
+
+ return target;
+}
+
+static gboolean
+pluma_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint timestamp)
+{
+ gboolean result;
+
+ /* Chain up to allow textview to scroll and position dnd mark, note
+ * that this needs to be checked if gtksourceview or gtktextview
+ * changes drag_motion behaviour */
+ result = GTK_WIDGET_CLASS (pluma_view_parent_class)->drag_motion (widget, context, x, y, timestamp);
+
+ /* If this is a URL, deal with it here */
+ if (drag_get_uri_target (widget, context) != GDK_NONE)
+ {
+ gdk_drag_status (context, context->suggested_action, timestamp);
+ result = TRUE;
+ }
+
+ return result;
+}
+
+static void
+pluma_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint timestamp)
+{
+ gchar **uri_list;
+
+ /* If this is an URL emit DROP_URIS, otherwise chain up the signal */
+ if (info == TARGET_URI_LIST)
+ {
+ uri_list = pluma_utils_drop_get_uris (selection_data);
+
+ if (uri_list != NULL)
+ {
+ g_signal_emit (widget, view_signals[DROP_URIS], 0, uri_list);
+ g_strfreev (uri_list);
+
+ gtk_drag_finish (context, TRUE, FALSE, timestamp);
+ }
+ }
+ else
+ {
+ GTK_WIDGET_CLASS (pluma_view_parent_class)->drag_data_received (widget, context, x, y, selection_data, info, timestamp);
+ }
+}
+
+static gboolean
+pluma_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint timestamp)
+{
+ gboolean result;
+ GdkAtom target;
+
+ /* If this is a URL, just get the drag data */
+ target = drag_get_uri_target (widget, context);
+
+ if (target != GDK_NONE)
+ {
+ gtk_drag_get_data (widget, context, target, timestamp);
+ result = TRUE;
+ }
+ else
+ {
+ /* Chain up */
+ result = GTK_WIDGET_CLASS (pluma_view_parent_class)->drag_drop (widget, context, x, y, timestamp);
+ }
+
+ return result;
+}
+
+static void
+show_line_numbers_toggled (GtkMenu *menu,
+ PlumaView *view)
+{
+ gboolean show;
+
+ show = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu));
+
+ pluma_prefs_manager_set_display_line_numbers (show);
+}
+
+static GtkWidget *
+create_line_numbers_menu (GtkWidget *view)
+{
+ GtkWidget *menu;
+ GtkWidget *item;
+
+ menu = gtk_menu_new ();
+
+ item = gtk_check_menu_item_new_with_mnemonic (_("_Display line numbers"));
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
+ gtk_source_view_get_show_line_numbers (GTK_SOURCE_VIEW (view)));
+ g_signal_connect (item, "toggled",
+ G_CALLBACK (show_line_numbers_toggled), view);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ gtk_widget_show_all (menu);
+
+ return menu;
+}
+
+static void
+show_line_numbers_menu (GtkWidget *view,
+ GdkEventButton *event)
+{
+ GtkWidget *menu;
+
+ menu = create_line_numbers_menu (view);
+
+ gtk_menu_popup (GTK_MENU (menu),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ event->button,
+ event->time);
+}
+
+static gboolean
+pluma_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
+{
+ if ((event->type == GDK_BUTTON_PRESS) &&
+ (event->button == 3) &&
+ (event->window == gtk_text_view_get_window (GTK_TEXT_VIEW (widget),
+ GTK_TEXT_WINDOW_LEFT)))
+ {
+ show_line_numbers_menu (widget, event);
+
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (pluma_view_parent_class)->button_press_event (widget, event);
+}
+
+static void
+search_highlight_updated_cb (PlumaDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ PlumaView *view)
+{
+ GdkRectangle visible_rect;
+ GdkRectangle updated_rect;
+ GdkRectangle redraw_rect;
+ gint y;
+ gint height;
+ GtkTextView *text_view;
+
+ text_view = GTK_TEXT_VIEW (view);
+
+ g_return_if_fail (pluma_document_get_enable_search_highlighting (
+ PLUMA_DOCUMENT (gtk_text_view_get_buffer (text_view))));
+
+ /* get visible area */
+ gtk_text_view_get_visible_rect (text_view, &visible_rect);
+
+ /* get updated rectangle */
+ gtk_text_view_get_line_yrange (text_view, start, &y, &height);
+ updated_rect.y = y;
+ gtk_text_view_get_line_yrange (text_view, end, &y, &height);
+ updated_rect.height = y + height - updated_rect.y;
+ updated_rect.x = visible_rect.x;
+ updated_rect.width = visible_rect.width;
+
+ /* intersect both rectangles to see whether we need to queue a redraw */
+ if (gdk_rectangle_intersect (&updated_rect, &visible_rect, &redraw_rect))
+ {
+ GdkRectangle widget_rect;
+
+ gtk_text_view_buffer_to_window_coords (text_view,
+ GTK_TEXT_WINDOW_WIDGET,
+ redraw_rect.x,
+ redraw_rect.y,
+ &widget_rect.x,
+ &widget_rect.y);
+
+ widget_rect.width = redraw_rect.width;
+ widget_rect.height = redraw_rect.height;
+
+ gtk_widget_queue_draw_area (GTK_WIDGET (text_view),
+ widget_rect.x,
+ widget_rect.y,
+ widget_rect.width,
+ widget_rect.height);
+ }
+}
+
+/* There is no "official" way to reset the im context in GtkTextView */
+static void
+reset_im_context (GtkTextView *text_view)
+{
+ if (text_view->need_im_reset)
+ {
+ text_view->need_im_reset = FALSE;
+ gtk_im_context_reset (text_view->im_context);
+ }
+}
+
+static void
+delete_line (GtkTextView *text_view,
+ gint count)
+{
+ GtkTextIter start;
+ GtkTextIter end;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (text_view);
+
+ reset_im_context (text_view);
+
+ /* If there is a selection delete the selected lines and
+ * ignore count */
+ if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+ {
+ gtk_text_iter_order (&start, &end);
+
+ if (gtk_text_iter_starts_line (&end))
+ {
+ /* Do no delete the line with the cursor if the cursor
+ * is at the beginning of the line */
+ count = 0;
+ }
+ else
+ count = 1;
+ }
+
+ gtk_text_iter_set_line_offset (&start, 0);
+
+ if (count > 0)
+ {
+ gtk_text_iter_forward_lines (&end, count);
+
+ if (gtk_text_iter_is_end (&end))
+ {
+ if (gtk_text_iter_backward_line (&start) && !gtk_text_iter_ends_line (&start))
+ gtk_text_iter_forward_to_line_end (&start);
+ }
+ }
+ else if (count < 0)
+ {
+ if (!gtk_text_iter_ends_line (&end))
+ gtk_text_iter_forward_to_line_end (&end);
+
+ while (count < 0)
+ {
+ if (!gtk_text_iter_backward_line (&start))
+ break;
+
+ ++count;
+ }
+
+ if (count == 0)
+ {
+ if (!gtk_text_iter_ends_line (&start))
+ gtk_text_iter_forward_to_line_end (&start);
+ }
+ else
+ gtk_text_iter_forward_line (&end);
+ }
+
+ if (!gtk_text_iter_equal (&start, &end))
+ {
+ GtkTextIter cur = start;
+ gtk_text_iter_set_line_offset (&cur, 0);
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ gtk_text_buffer_place_cursor (buffer, &cur);
+
+ gtk_text_buffer_delete_interactive (buffer,
+ &start,
+ &end,
+ gtk_text_view_get_editable (text_view));
+
+ gtk_text_buffer_end_user_action (buffer);
+
+ gtk_text_view_scroll_mark_onscreen (text_view,
+ gtk_text_buffer_get_insert (buffer));
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
+}
+
+static void
+pluma_view_delete_from_cursor (GtkTextView *text_view,
+ GtkDeleteType type,
+ gint count)
+{
+ /* We override the standard handler for delete_from_cursor since
+ the GTK_DELETE_PARAGRAPHS case is not implemented as we like (i.e. it
+ does not remove the carriage return in the previous line)
+ */
+ switch (type)
+ {
+ case GTK_DELETE_PARAGRAPHS:
+ delete_line (text_view, count);
+ break;
+ default:
+ GTK_TEXT_VIEW_CLASS (pluma_view_parent_class)->delete_from_cursor(text_view, type, count);
+ break;
+ }
+}
diff --git a/pluma/pluma-view.h b/pluma/pluma-view.h
new file mode 100755
index 00000000..2d4757be
--- /dev/null
+++ b/pluma/pluma-view.h
@@ -0,0 +1,108 @@
+/*
+ * pluma-view.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __PLUMA_VIEW_H__
+#define __PLUMA_VIEW_H__
+
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-document.h>
+#include <gtksourceview/gtksourceview.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_VIEW (pluma_view_get_type ())
+#define PLUMA_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_VIEW, PlumaView))
+#define PLUMA_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_VIEW, PlumaViewClass))
+#define PLUMA_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_VIEW))
+#define PLUMA_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_VIEW))
+#define PLUMA_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_VIEW, PlumaViewClass))
+
+/* Private structure type */
+typedef struct _PlumaViewPrivate PlumaViewPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaView PlumaView;
+
+struct _PlumaView
+{
+ GtkSourceView view;
+
+ /*< private > */
+ PlumaViewPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaViewClass PlumaViewClass;
+
+struct _PlumaViewClass
+{
+ GtkSourceViewClass parent_class;
+
+ /* FIXME: Do we need placeholders ? */
+
+ /* Key bindings */
+ gboolean (* start_interactive_search) (PlumaView *view);
+ gboolean (* start_interactive_goto_line)(PlumaView *view);
+ gboolean (* reset_searched_text) (PlumaView *view);
+
+ void (* drop_uris) (PlumaView *view,
+ gchar **uri_list);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_view_get_type (void) G_GNUC_CONST;
+
+GtkWidget *pluma_view_new (PlumaDocument *doc);
+
+void pluma_view_cut_clipboard (PlumaView *view);
+void pluma_view_copy_clipboard (PlumaView *view);
+void pluma_view_paste_clipboard (PlumaView *view);
+void pluma_view_delete_selection (PlumaView *view);
+void pluma_view_select_all (PlumaView *view);
+
+void pluma_view_scroll_to_cursor (PlumaView *view);
+
+void pluma_view_set_font (PlumaView *view,
+ gboolean def,
+ const gchar *font_name);
+
+G_END_DECLS
+
+#endif /* __PLUMA_VIEW_H__ */
diff --git a/pluma/pluma-window-private.h b/pluma/pluma-window-private.h
new file mode 100755
index 00000000..059041c4
--- /dev/null
+++ b/pluma/pluma-window-private.h
@@ -0,0 +1,124 @@
+/*
+ * pluma-window-private.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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
+ * MERCHANWINDOWILITY 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_WINDOW_PRIVATE_H__
+#define __PLUMA_WINDOW_PRIVATE_H__
+
+#include "pluma/pluma-window.h"
+#include "pluma-prefs-manager.h"
+#include "pluma-message-bus.h"
+
+#ifdef OS_OSX
+#include <ige-mac-integration.h>
+#endif
+
+G_BEGIN_DECLS
+
+/* WindowPrivate is in a separate .h so that we can access it from pluma-commands */
+
+struct _PlumaWindowPrivate
+{
+ GtkWidget *notebook;
+
+ GtkWidget *side_panel;
+ GtkWidget *bottom_panel;
+
+ GtkWidget *hpaned;
+ GtkWidget *vpaned;
+
+ GtkWidget *tab_width_combo;
+ GtkWidget *language_combo;
+
+ PlumaMessageBus *message_bus;
+
+ /* Widgets for fullscreen mode */
+ GtkWidget *fullscreen_controls;
+ guint fullscreen_animation_timeout_id;
+ gboolean fullscreen_animation_enter;
+
+ /* statusbar and context ids for statusbar messages */
+ GtkWidget *statusbar;
+ guint generic_message_cid;
+ guint tip_message_cid;
+ guint tab_width_id;
+ guint spaces_instead_of_tabs_id;
+ guint language_changed_id;
+
+ /* Menus & Toolbars */
+ GtkUIManager *manager;
+ GtkActionGroup *action_group;
+ GtkActionGroup *always_sensitive_action_group;
+ GtkActionGroup *close_action_group;
+ GtkActionGroup *quit_action_group;
+ GtkActionGroup *panes_action_group;
+ GtkActionGroup *languages_action_group;
+ GtkActionGroup *documents_list_action_group;
+ guint documents_list_menu_ui_id;
+ GtkWidget *toolbar;
+ GtkWidget *toolbar_recent_menu;
+ GtkWidget *menubar;
+ PlumaToolbarSetting toolbar_style;
+
+ /* recent files */
+ GtkActionGroup *recents_action_group;
+ guint recents_menu_ui_id;
+ gulong recents_handler_id;
+
+ PlumaTab *active_tab;
+ gint num_tabs;
+
+ gint num_tabs_with_error;
+
+ gint width;
+ gint height;
+ GdkWindowState window_state;
+
+ gint side_panel_size;
+ gint bottom_panel_size;
+
+ PlumaWindowState state;
+
+ gint bottom_panel_item_removed_handler_id;
+
+ GtkWindowGroup *window_group;
+
+ GFile *default_location;
+
+#ifdef OS_OSX
+ IgeMacMenuGroup *mac_menu_group;
+#endif
+
+ gboolean removing_tabs : 1;
+ gboolean dispose_has_run : 1;
+};
+
+G_END_DECLS
+
+#endif /* __PLUMA_WINDOW_PRIVATE_H__ */
diff --git a/pluma/pluma-window.c b/pluma/pluma-window.c
new file mode 100755
index 00000000..2f2024ba
--- /dev/null
+++ b/pluma/pluma-window.c
@@ -0,0 +1,4798 @@
+/*
+ * pluma-window.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <time.h>
+#include <sys/types.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "pluma-ui.h"
+#include "pluma-window.h"
+#include "pluma-window-private.h"
+#include "pluma-app.h"
+#include "pluma-notebook.h"
+#include "pluma-statusbar.h"
+#include "pluma-utils.h"
+#include "pluma-commands.h"
+#include "pluma-debug.h"
+#include "pluma-language-manager.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-panel.h"
+#include "pluma-documents-panel.h"
+#include "pluma-plugins-engine.h"
+#include "pluma-enum-types.h"
+#include "pluma-dirs.h"
+#include "pluma-status-combo-box.h"
+
+#ifdef OS_OSX
+#include "osx/pluma-osx.h"
+#endif
+
+#define LANGUAGE_NONE (const gchar *)"LangNone"
+#define PLUMA_UIFILE "pluma-ui.xml"
+#define TAB_WIDTH_DATA "PlumaWindowTabWidthData"
+#define LANGUAGE_DATA "PlumaWindowLanguageData"
+#define FULLSCREEN_ANIMATION_SPEED 4
+
+#define PLUMA_WINDOW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object),\
+ PLUMA_TYPE_WINDOW, \
+ PlumaWindowPrivate))
+
+/* Signals */
+enum
+{
+ TAB_ADDED,
+ TAB_REMOVED,
+ TABS_REORDERED,
+ ACTIVE_TAB_CHANGED,
+ ACTIVE_TAB_STATE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum
+{
+ PROP_0,
+ PROP_STATE
+};
+
+enum
+{
+ TARGET_URI_LIST = 100
+};
+
+G_DEFINE_TYPE(PlumaWindow, pluma_window, GTK_TYPE_WINDOW)
+
+static void recent_manager_changed (GtkRecentManager *manager,
+ PlumaWindow *window);
+
+static void
+pluma_window_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PlumaWindow *window = PLUMA_WINDOW (object);
+
+ switch (prop_id)
+ {
+ case PROP_STATE:
+ g_value_set_enum (value,
+ pluma_window_get_state (window));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+save_panes_state (PlumaWindow *window)
+{
+ gint pane_page;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ if (pluma_prefs_manager_window_size_can_set ())
+ pluma_prefs_manager_set_window_size (window->priv->width,
+ window->priv->height);
+
+ if (pluma_prefs_manager_window_state_can_set ())
+ pluma_prefs_manager_set_window_state (window->priv->window_state);
+
+ if ((window->priv->side_panel_size > 0) &&
+ pluma_prefs_manager_side_panel_size_can_set ())
+ pluma_prefs_manager_set_side_panel_size (
+ window->priv->side_panel_size);
+
+ pane_page = _pluma_panel_get_active_item_id (PLUMA_PANEL (window->priv->side_panel));
+ if (pane_page != 0 &&
+ pluma_prefs_manager_side_panel_active_page_can_set ())
+ pluma_prefs_manager_set_side_panel_active_page (pane_page);
+
+ if ((window->priv->bottom_panel_size > 0) &&
+ pluma_prefs_manager_bottom_panel_size_can_set ())
+ pluma_prefs_manager_set_bottom_panel_size (
+ window->priv->bottom_panel_size);
+
+ pane_page = _pluma_panel_get_active_item_id (PLUMA_PANEL (window->priv->bottom_panel));
+ if (pane_page != 0 &&
+ pluma_prefs_manager_bottom_panel_active_page_can_set ())
+ pluma_prefs_manager_set_bottom_panel_active_page (pane_page);
+}
+
+#ifdef OS_OSX
+static GtkMenuItem *
+ui_manager_menu_item (GtkUIManager *uimanager,
+ const gchar *path)
+{
+ return GTK_MENU_ITEM (gtk_ui_manager_get_widget (uimanager, path));
+}
+
+static void
+add_mac_root_menu (PlumaWindow *window)
+{
+ if (window->priv->mac_menu_group != NULL)
+ {
+ return;
+ }
+
+ window->priv->mac_menu_group = ige_mac_menu_add_app_menu_group ();
+
+ ige_mac_menu_add_app_menu_item (window->priv->mac_menu_group,
+ ui_manager_menu_item (window->priv->manager, "/ui/MenuBar/HelpMenu/HelpAboutMenu"),
+ NULL);
+}
+
+static void
+remove_mac_root_menu (PlumaWindow *window)
+{
+ if (window->priv->mac_menu_group == NULL)
+ {
+ return;
+ }
+
+ ige_mac_menu_remove_app_menu_group (window->priv->mac_menu_group);
+ window->priv->mac_menu_group = NULL;
+}
+
+static gboolean
+pluma_window_focus_in_event (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ add_mac_root_menu (PLUMA_WINDOW (widget));
+ return GTK_WIDGET_CLASS (pluma_window_parent_class)->focus_in_event (widget, event);
+}
+
+static gboolean
+pluma_window_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ remove_mac_root_menu (PLUMA_WINDOW (widget));
+ return GTK_WIDGET_CLASS (pluma_window_parent_class)->focus_out_event (widget, event);
+}
+#endif
+
+static void
+pluma_window_dispose (GObject *object)
+{
+ PlumaWindow *window;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ window = PLUMA_WINDOW (object);
+
+ /* Stop tracking removal of panes otherwise we always
+ * end up with thinking we had no pane active, since they
+ * should all be removed below */
+ if (window->priv->bottom_panel_item_removed_handler_id != 0)
+ {
+ g_signal_handler_disconnect (window->priv->bottom_panel,
+ window->priv->bottom_panel_item_removed_handler_id);
+ window->priv->bottom_panel_item_removed_handler_id = 0;
+ }
+
+ /* First of all, force collection so that plugins
+ * really drop some of the references.
+ */
+ pluma_plugins_engine_garbage_collect (pluma_plugins_engine_get_default ());
+
+ /* save the panes position and make sure to deactivate plugins
+ * for this window, but only once */
+ if (!window->priv->dispose_has_run)
+ {
+ save_panes_state (window);
+
+ pluma_plugins_engine_deactivate_plugins (pluma_plugins_engine_get_default (),
+ window);
+ window->priv->dispose_has_run = TRUE;
+ }
+
+ if (window->priv->fullscreen_animation_timeout_id != 0)
+ {
+ g_source_remove (window->priv->fullscreen_animation_timeout_id);
+ window->priv->fullscreen_animation_timeout_id = 0;
+ }
+
+ if (window->priv->fullscreen_controls != NULL)
+ {
+ gtk_widget_destroy (window->priv->fullscreen_controls);
+
+ window->priv->fullscreen_controls = NULL;
+ }
+
+ if (window->priv->recents_handler_id != 0)
+ {
+ GtkRecentManager *recent_manager;
+
+ recent_manager = gtk_recent_manager_get_default ();
+ g_signal_handler_disconnect (recent_manager,
+ window->priv->recents_handler_id);
+ window->priv->recents_handler_id = 0;
+ }
+
+ if (window->priv->manager != NULL)
+ {
+ g_object_unref (window->priv->manager);
+ window->priv->manager = NULL;
+ }
+
+ if (window->priv->message_bus != NULL)
+ {
+ g_object_unref (window->priv->message_bus);
+ window->priv->message_bus = NULL;
+ }
+
+ if (window->priv->window_group != NULL)
+ {
+ g_object_unref (window->priv->window_group);
+ window->priv->window_group = NULL;
+ }
+
+ /* Now that there have broken some reference loops,
+ * force collection again.
+ */
+ pluma_plugins_engine_garbage_collect (pluma_plugins_engine_get_default ());
+
+#ifdef OS_OSX
+ remove_mac_root_menu (window);
+#endif
+
+ G_OBJECT_CLASS (pluma_window_parent_class)->dispose (object);
+}
+
+static void
+pluma_window_finalize (GObject *object)
+{
+ PlumaWindow *window;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ window = PLUMA_WINDOW (object);
+
+ if (window->priv->default_location != NULL)
+ g_object_unref (window->priv->default_location);
+
+ G_OBJECT_CLASS (pluma_window_parent_class)->finalize (object);
+}
+
+static gboolean
+pluma_window_window_state_event (GtkWidget *widget,
+ GdkEventWindowState *event)
+{
+ PlumaWindow *window = PLUMA_WINDOW (widget);
+
+ window->priv->window_state = event->new_window_state;
+
+ if (event->changed_mask &
+ (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN))
+ {
+ gboolean show;
+
+ show = !(event->new_window_state &
+ (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN));
+
+ _pluma_statusbar_set_has_resize_grip (PLUMA_STATUSBAR (window->priv->statusbar),
+ show);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+pluma_window_configure_event (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ PlumaWindow *window = PLUMA_WINDOW (widget);
+
+ window->priv->width = event->width;
+ window->priv->height = event->height;
+
+ return GTK_WIDGET_CLASS (pluma_window_parent_class)->configure_event (widget, event);
+}
+
+/*
+ * GtkWindow catches keybindings for the menu items _before_ passing them to
+ * the focused widget. This is unfortunate and means that pressing ctrl+V
+ * in an entry on a panel ends up pasting text in the TextView.
+ * Here we override GtkWindow's handler to do the same things that it
+ * does, but in the opposite order and then we chain up to the grand
+ * parent handler, skipping gtk_window_key_press_event.
+ */
+static gboolean
+pluma_window_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ static gpointer grand_parent_class = NULL;
+ GtkWindow *window = GTK_WINDOW (widget);
+ gboolean handled = FALSE;
+
+ if (grand_parent_class == NULL)
+ grand_parent_class = g_type_class_peek_parent (pluma_window_parent_class);
+
+ /* handle focus widget key events */
+ if (!handled)
+ handled = gtk_window_propagate_key_event (window, event);
+
+ /* handle mnemonics and accelerators */
+ if (!handled)
+ handled = gtk_window_activate_key (window, event);
+
+ /* Chain up, invokes binding set */
+ if (!handled)
+ handled = GTK_WIDGET_CLASS (grand_parent_class)->key_press_event (widget, event);
+
+ return handled;
+}
+
+static void
+pluma_window_tab_removed (PlumaWindow *window,
+ PlumaTab *tab)
+{
+ pluma_plugins_engine_garbage_collect (pluma_plugins_engine_get_default ());
+}
+
+static void
+pluma_window_class_init (PlumaWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ klass->tab_removed = pluma_window_tab_removed;
+
+ object_class->dispose = pluma_window_dispose;
+ object_class->finalize = pluma_window_finalize;
+ object_class->get_property = pluma_window_get_property;
+
+ widget_class->window_state_event = pluma_window_window_state_event;
+ widget_class->configure_event = pluma_window_configure_event;
+ widget_class->key_press_event = pluma_window_key_press_event;
+
+#ifdef OS_OSX
+ widget_class->focus_in_event = pluma_window_focus_in_event;
+ widget_class->focus_out_event = pluma_window_focus_out_event;
+#endif
+
+ signals[TAB_ADDED] =
+ g_signal_new ("tab_added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaWindowClass, tab_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_TAB);
+ signals[TAB_REMOVED] =
+ g_signal_new ("tab_removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaWindowClass, tab_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_TAB);
+ signals[TABS_REORDERED] =
+ g_signal_new ("tabs_reordered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaWindowClass, tabs_reordered),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ signals[ACTIVE_TAB_CHANGED] =
+ g_signal_new ("active_tab_changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaWindowClass, active_tab_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ PLUMA_TYPE_TAB);
+ signals[ACTIVE_TAB_STATE_CHANGED] =
+ g_signal_new ("active_tab_state_changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PlumaWindowClass, active_tab_state_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_object_class_install_property (object_class,
+ PROP_STATE,
+ g_param_spec_flags ("state",
+ "State",
+ "The window's state",
+ PLUMA_TYPE_WINDOW_STATE,
+ PLUMA_WINDOW_STATE_NORMAL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof(PlumaWindowPrivate));
+}
+
+static void
+menu_item_select_cb (GtkMenuItem *proxy,
+ PlumaWindow *window)
+{
+ GtkAction *action;
+ char *message;
+
+ action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (proxy));
+ g_return_if_fail (action != NULL);
+
+ g_object_get (G_OBJECT (action), "tooltip", &message, NULL);
+ if (message)
+ {
+ gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar),
+ window->priv->tip_message_cid, message);
+ g_free (message);
+ }
+}
+
+static void
+menu_item_deselect_cb (GtkMenuItem *proxy,
+ PlumaWindow *window)
+{
+ gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar),
+ window->priv->tip_message_cid);
+}
+
+static void
+connect_proxy_cb (GtkUIManager *manager,
+ GtkAction *action,
+ GtkWidget *proxy,
+ PlumaWindow *window)
+{
+ if (GTK_IS_MENU_ITEM (proxy))
+ {
+ g_signal_connect (proxy, "select",
+ G_CALLBACK (menu_item_select_cb), window);
+ g_signal_connect (proxy, "deselect",
+ G_CALLBACK (menu_item_deselect_cb), window);
+ }
+}
+
+static void
+disconnect_proxy_cb (GtkUIManager *manager,
+ GtkAction *action,
+ GtkWidget *proxy,
+ PlumaWindow *window)
+{
+ if (GTK_IS_MENU_ITEM (proxy))
+ {
+ g_signal_handlers_disconnect_by_func
+ (proxy, G_CALLBACK (menu_item_select_cb), window);
+ g_signal_handlers_disconnect_by_func
+ (proxy, G_CALLBACK (menu_item_deselect_cb), window);
+ }
+}
+
+static void
+apply_toolbar_style (PlumaWindow *window,
+ GtkWidget *toolbar)
+{
+ switch (window->priv->toolbar_style)
+ {
+ case PLUMA_TOOLBAR_SYSTEM:
+ pluma_debug_message (DEBUG_WINDOW, "PLUMA: SYSTEM");
+ gtk_toolbar_unset_style (
+ GTK_TOOLBAR (toolbar));
+ break;
+
+ case PLUMA_TOOLBAR_ICONS:
+ pluma_debug_message (DEBUG_WINDOW, "PLUMA: ICONS");
+ gtk_toolbar_set_style (
+ GTK_TOOLBAR (toolbar),
+ GTK_TOOLBAR_ICONS);
+ break;
+
+ case PLUMA_TOOLBAR_ICONS_AND_TEXT:
+ pluma_debug_message (DEBUG_WINDOW, "PLUMA: ICONS_AND_TEXT");
+ gtk_toolbar_set_style (
+ GTK_TOOLBAR (toolbar),
+ GTK_TOOLBAR_BOTH);
+ break;
+
+ case PLUMA_TOOLBAR_ICONS_BOTH_HORIZ:
+ pluma_debug_message (DEBUG_WINDOW, "PLUMA: ICONS_BOTH_HORIZ");
+ gtk_toolbar_set_style (
+ GTK_TOOLBAR (toolbar),
+ GTK_TOOLBAR_BOTH_HORIZ);
+ break;
+ }
+}
+
+/* Returns TRUE if toolbar is visible */
+static gboolean
+set_toolbar_style (PlumaWindow *window,
+ PlumaWindow *origin)
+{
+ gboolean visible;
+ PlumaToolbarSetting style;
+ GtkAction *action;
+
+ if (origin == NULL)
+ visible = pluma_prefs_manager_get_toolbar_visible ();
+ else
+ visible = GTK_WIDGET_VISIBLE (origin->priv->toolbar);
+
+ /* Set visibility */
+ if (visible)
+ gtk_widget_show (window->priv->toolbar);
+ else
+ gtk_widget_hide (window->priv->toolbar);
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "ViewToolbar");
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) != visible)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
+
+ /* Set style */
+ if (origin == NULL)
+ style = pluma_prefs_manager_get_toolbar_buttons_style ();
+ else
+ style = origin->priv->toolbar_style;
+
+ window->priv->toolbar_style = style;
+
+ apply_toolbar_style (window, window->priv->toolbar);
+
+ return visible;
+}
+
+static void
+update_next_prev_doc_sensitivity (PlumaWindow *window,
+ PlumaTab *tab)
+{
+ gint tab_number;
+ GtkNotebook *notebook;
+ GtkAction *action;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ notebook = GTK_NOTEBOOK (_pluma_window_get_notebook (window));
+
+ tab_number = gtk_notebook_page_num (notebook, GTK_WIDGET (tab));
+ g_return_if_fail (tab_number >= 0);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "DocumentsPreviousDocument");
+ gtk_action_set_sensitive (action, tab_number != 0);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "DocumentsNextDocument");
+ gtk_action_set_sensitive (action,
+ tab_number < gtk_notebook_get_n_pages (notebook) - 1);
+}
+
+static void
+update_next_prev_doc_sensitivity_per_window (PlumaWindow *window)
+{
+ PlumaTab *tab;
+ GtkAction *action;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ tab = pluma_window_get_active_tab (window);
+ if (tab != NULL)
+ {
+ update_next_prev_doc_sensitivity (window, tab);
+
+ return;
+ }
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "DocumentsPreviousDocument");
+ gtk_action_set_sensitive (action, FALSE);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "DocumentsNextDocument");
+ gtk_action_set_sensitive (action, FALSE);
+
+}
+
+static void
+received_clipboard_contents (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ PlumaWindow *window)
+{
+ gboolean sens;
+ GtkAction *action;
+
+ /* getting clipboard contents is async, so we need to
+ * get the current tab and its state */
+
+ if (window->priv->active_tab != NULL)
+ {
+ PlumaTabState state;
+ gboolean state_normal;
+
+ state = pluma_tab_get_state (window->priv->active_tab);
+ state_normal = (state == PLUMA_TAB_STATE_NORMAL);
+
+ sens = state_normal &&
+ gtk_selection_data_targets_include_text (selection_data);
+ }
+ else
+ {
+ sens = FALSE;
+ }
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditPaste");
+
+ gtk_action_set_sensitive (action, sens);
+
+ g_object_unref (window);
+}
+
+static void
+set_paste_sensitivity_according_to_clipboard (PlumaWindow *window,
+ GtkClipboard *clipboard)
+{
+ GdkDisplay *display;
+
+ display = gtk_clipboard_get_display (clipboard);
+
+ if (gdk_display_supports_selection_notification (display))
+ {
+ gtk_clipboard_request_contents (clipboard,
+ gdk_atom_intern_static_string ("TARGETS"),
+ (GtkClipboardReceivedFunc) received_clipboard_contents,
+ g_object_ref (window));
+ }
+ else
+ {
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditPaste");
+
+ /* XFIXES extension not availbale, make
+ * Paste always sensitive */
+ gtk_action_set_sensitive (action, TRUE);
+ }
+}
+
+static void
+set_sensitivity_according_to_tab (PlumaWindow *window,
+ PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ PlumaView *view;
+ GtkAction *action;
+ gboolean b;
+ gboolean state_normal;
+ gboolean editable;
+ PlumaTabState state;
+ GtkClipboard *clipboard;
+ PlumaLockdownMask lockdown;
+
+ g_return_if_fail (PLUMA_TAB (tab));
+
+ pluma_debug (DEBUG_WINDOW);
+
+ lockdown = pluma_app_get_lockdown (pluma_app_get_default ());
+
+ state = pluma_tab_get_state (tab);
+ state_normal = (state == PLUMA_TAB_STATE_NORMAL);
+
+ view = pluma_tab_get_view (tab);
+ editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (view));
+
+ doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (window),
+ GDK_SELECTION_CLIPBOARD);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FileSave");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ (state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) ||
+ (state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW)) &&
+ !pluma_document_get_readonly (doc) &&
+ !(lockdown & PLUMA_LOCKDOWN_SAVE_TO_DISK));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FileSaveAs");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ (state == PLUMA_TAB_STATE_SAVING_ERROR) ||
+ (state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) ||
+ (state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW)) &&
+ !(lockdown & PLUMA_LOCKDOWN_SAVE_TO_DISK));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FileRevert");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ (state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) &&
+ !pluma_document_is_untitled (doc));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FilePrintPreview");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ !(lockdown & PLUMA_LOCKDOWN_PRINTING));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FilePrint");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ (state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW)) &&
+ !(lockdown & PLUMA_LOCKDOWN_PRINTING));
+
+ action = gtk_action_group_get_action (window->priv->close_action_group,
+ "FileClose");
+
+ gtk_action_set_sensitive (action,
+ (state != PLUMA_TAB_STATE_CLOSING) &&
+ (state != PLUMA_TAB_STATE_SAVING) &&
+ (state != PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW) &&
+ (state != PLUMA_TAB_STATE_PRINTING) &&
+ (state != PLUMA_TAB_STATE_PRINT_PREVIEWING) &&
+ (state != PLUMA_TAB_STATE_SAVING_ERROR));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditUndo");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ gtk_source_buffer_can_undo (GTK_SOURCE_BUFFER (doc)));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditRedo");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ gtk_source_buffer_can_redo (GTK_SOURCE_BUFFER (doc)));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditCut");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ editable &&
+ gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc)));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditCopy");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) &&
+ gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc)));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditPaste");
+ if (state_normal && editable)
+ {
+ set_paste_sensitivity_according_to_clipboard (window,
+ clipboard);
+ }
+ else
+ {
+ gtk_action_set_sensitive (action, FALSE);
+ }
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditDelete");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ editable &&
+ gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc)));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchFind");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchIncrementalSearch");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchReplace");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ editable);
+
+ b = pluma_document_get_can_search_again (doc);
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchFindNext");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) && b);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchFindPrevious");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) && b);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchClearHighlight");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) && b);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchGoToLine");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "ViewHighlightMode");
+ gtk_action_set_sensitive (action,
+ (state != PLUMA_TAB_STATE_CLOSING) &&
+ pluma_prefs_manager_get_enable_syntax_highlighting ());
+
+ update_next_prev_doc_sensitivity (window, tab);
+
+ pluma_plugins_engine_update_plugins_ui (pluma_plugins_engine_get_default (),
+ window);
+}
+
+static void
+language_toggled (GtkToggleAction *action,
+ PlumaWindow *window)
+{
+ PlumaDocument *doc;
+ GtkSourceLanguage *lang;
+ const gchar *lang_id;
+
+ if (gtk_toggle_action_get_active (action) == FALSE)
+ return;
+
+ doc = pluma_window_get_active_document (window);
+ if (doc == NULL)
+ return;
+
+ lang_id = gtk_action_get_name (GTK_ACTION (action));
+
+ if (strcmp (lang_id, LANGUAGE_NONE) == 0)
+ {
+ /* Normal (no highlighting) */
+ lang = NULL;
+ }
+ else
+ {
+ lang = gtk_source_language_manager_get_language (
+ pluma_get_language_manager (),
+ lang_id);
+ if (lang == NULL)
+ {
+ g_warning ("Could not get language %s\n", lang_id);
+ }
+ }
+
+ pluma_document_set_language (doc, lang);
+}
+
+static gchar *
+escape_section_name (const gchar *name)
+{
+ gchar *ret;
+
+ ret = g_markup_escape_text (name, -1);
+
+ /* Replace '/' with '-' to avoid problems in xml paths */
+ g_strdelimit (ret, "/", '-');
+
+ return ret;
+}
+
+static void
+create_language_menu_item (GtkSourceLanguage *lang,
+ gint index,
+ guint ui_id,
+ PlumaWindow *window)
+{
+ GtkAction *section_action;
+ GtkRadioAction *action;
+ GtkAction *normal_action;
+ GSList *group;
+ const gchar *section;
+ gchar *escaped_section;
+ const gchar *lang_id;
+ const gchar *lang_name;
+ gchar *escaped_lang_name;
+ gchar *tip;
+ gchar *path;
+
+ section = gtk_source_language_get_section (lang);
+ escaped_section = escape_section_name (section);
+
+ /* check if the section submenu exists or create it */
+ section_action = gtk_action_group_get_action (window->priv->languages_action_group,
+ escaped_section);
+
+ if (section_action == NULL)
+ {
+ gchar *section_name;
+
+ section_name = pluma_utils_escape_underscores (section, -1);
+
+ section_action = gtk_action_new (escaped_section,
+ section_name,
+ NULL,
+ NULL);
+
+ g_free (section_name);
+
+ gtk_action_group_add_action (window->priv->languages_action_group,
+ section_action);
+ g_object_unref (section_action);
+
+ gtk_ui_manager_add_ui (window->priv->manager,
+ ui_id,
+ "/MenuBar/ViewMenu/ViewHighlightModeMenu/LanguagesMenuPlaceholder",
+ escaped_section,
+ escaped_section,
+ GTK_UI_MANAGER_MENU,
+ FALSE);
+ }
+
+ /* now add the language item to the section */
+ lang_name = gtk_source_language_get_name (lang);
+ lang_id = gtk_source_language_get_id (lang);
+
+ escaped_lang_name = pluma_utils_escape_underscores (lang_name, -1);
+
+ tip = g_strdup_printf (_("Use %s highlight mode"), lang_name);
+ path = g_strdup_printf ("/MenuBar/ViewMenu/ViewHighlightModeMenu/LanguagesMenuPlaceholder/%s",
+ escaped_section);
+
+ action = gtk_radio_action_new (lang_id,
+ escaped_lang_name,
+ tip,
+ NULL,
+ index);
+
+ g_free (escaped_lang_name);
+
+ /* Action is added with a NULL accel to make the accel overridable */
+ gtk_action_group_add_action_with_accel (window->priv->languages_action_group,
+ GTK_ACTION (action),
+ NULL);
+ g_object_unref (action);
+
+ /* add the action to the same radio group of the "Normal" action */
+ normal_action = gtk_action_group_get_action (window->priv->languages_action_group,
+ LANGUAGE_NONE);
+ group = gtk_radio_action_get_group (GTK_RADIO_ACTION (normal_action));
+ gtk_radio_action_set_group (action, group);
+
+ g_signal_connect (action,
+ "activate",
+ G_CALLBACK (language_toggled),
+ window);
+
+ gtk_ui_manager_add_ui (window->priv->manager,
+ ui_id,
+ path,
+ lang_id,
+ lang_id,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ g_free (path);
+ g_free (tip);
+ g_free (escaped_section);
+}
+
+static void
+create_languages_menu (PlumaWindow *window)
+{
+ GtkRadioAction *action_none;
+ GSList *languages;
+ GSList *l;
+ guint id;
+ gint i;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ /* add the "Plain Text" item before all the others */
+
+ /* Translators: "Plain Text" means that no highlight mode is selected in the
+ * "View->Highlight Mode" submenu and so syntax highlighting is disabled */
+ action_none = gtk_radio_action_new (LANGUAGE_NONE, _("Plain Text"),
+ _("Disable syntax highlighting"),
+ NULL,
+ -1);
+
+ gtk_action_group_add_action (window->priv->languages_action_group,
+ GTK_ACTION (action_none));
+ g_object_unref (action_none);
+
+ g_signal_connect (action_none,
+ "activate",
+ G_CALLBACK (language_toggled),
+ window);
+
+ id = gtk_ui_manager_new_merge_id (window->priv->manager);
+
+ gtk_ui_manager_add_ui (window->priv->manager,
+ id,
+ "/MenuBar/ViewMenu/ViewHighlightModeMenu/LanguagesMenuPlaceholder",
+ LANGUAGE_NONE,
+ LANGUAGE_NONE,
+ GTK_UI_MANAGER_MENUITEM,
+ TRUE);
+
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action_none), TRUE);
+
+ /* now add all the known languages */
+ languages = pluma_language_manager_list_languages_sorted (
+ pluma_get_language_manager (),
+ FALSE);
+
+ for (l = languages, i = 0; l != NULL; l = l->next, ++i)
+ {
+ create_language_menu_item (l->data,
+ i,
+ id,
+ window);
+ }
+
+ g_slist_free (languages);
+}
+
+static void
+update_languages_menu (PlumaWindow *window)
+{
+ PlumaDocument *doc;
+ GList *actions;
+ GList *l;
+ GtkAction *action;
+ GtkSourceLanguage *lang;
+ const gchar *lang_id;
+
+ doc = pluma_window_get_active_document (window);
+ if (doc == NULL)
+ return;
+
+ lang = pluma_document_get_language (doc);
+ if (lang != NULL)
+ lang_id = gtk_source_language_get_id (lang);
+ else
+ lang_id = LANGUAGE_NONE;
+
+ actions = gtk_action_group_list_actions (window->priv->languages_action_group);
+
+ /* prevent recursion */
+ for (l = actions; l != NULL; l = l->next)
+ {
+ g_signal_handlers_block_by_func (GTK_ACTION (l->data),
+ G_CALLBACK (language_toggled),
+ window);
+ }
+
+ action = gtk_action_group_get_action (window->priv->languages_action_group,
+ lang_id);
+
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+
+ for (l = actions; l != NULL; l = l->next)
+ {
+ g_signal_handlers_unblock_by_func (GTK_ACTION (l->data),
+ G_CALLBACK (language_toggled),
+ window);
+ }
+
+ g_list_free (actions);
+}
+
+void
+_pluma_recent_add (PlumaWindow *window,
+ const gchar *uri,
+ const gchar *mime)
+{
+ GtkRecentManager *recent_manager;
+ GtkRecentData *recent_data;
+
+ static gchar *groups[2] = {
+ "pluma",
+ NULL
+ };
+
+ recent_manager = gtk_recent_manager_get_default ();
+
+ recent_data = g_slice_new (GtkRecentData);
+
+ recent_data->display_name = NULL;
+ recent_data->description = NULL;
+ recent_data->mime_type = (gchar *) mime;
+ recent_data->app_name = (gchar *) g_get_application_name ();
+ recent_data->app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
+ recent_data->groups = groups;
+ recent_data->is_private = FALSE;
+
+ gtk_recent_manager_add_full (recent_manager,
+ uri,
+ recent_data);
+
+ g_free (recent_data->app_exec);
+
+ g_slice_free (GtkRecentData, recent_data);
+}
+
+void
+_pluma_recent_remove (PlumaWindow *window,
+ const gchar *uri)
+{
+ GtkRecentManager *recent_manager;
+
+ recent_manager = gtk_recent_manager_get_default ();
+
+ gtk_recent_manager_remove_item (recent_manager, uri, NULL);
+}
+
+static void
+open_recent_file (const gchar *uri,
+ PlumaWindow *window)
+{
+ GSList *uris = NULL;
+
+ uris = g_slist_prepend (uris, (gpointer) uri);
+
+ if (pluma_commands_load_uris (window, uris, NULL, 0) != 1)
+ {
+ _pluma_recent_remove (window, uri);
+ }
+
+ g_slist_free (uris);
+}
+
+static void
+recent_chooser_item_activated (GtkRecentChooser *chooser,
+ PlumaWindow *window)
+{
+ gchar *uri;
+
+ uri = gtk_recent_chooser_get_current_uri (chooser);
+
+ open_recent_file (uri, window);
+
+ g_free (uri);
+}
+
+static void
+recents_menu_activate (GtkAction *action,
+ PlumaWindow *window)
+{
+ GtkRecentInfo *info;
+ const gchar *uri;
+
+ info = g_object_get_data (G_OBJECT (action), "gtk-recent-info");
+ g_return_if_fail (info != NULL);
+
+ uri = gtk_recent_info_get_uri (info);
+
+ open_recent_file (uri, window);
+}
+
+static gint
+sort_recents_mru (GtkRecentInfo *a, GtkRecentInfo *b)
+{
+ return (gtk_recent_info_get_modified (b) - gtk_recent_info_get_modified (a));
+}
+
+static void update_recent_files_menu (PlumaWindow *window);
+
+static void
+recent_manager_changed (GtkRecentManager *manager,
+ PlumaWindow *window)
+{
+ /* regenerate the menu when the model changes */
+ update_recent_files_menu (window);
+}
+
+/*
+ * Manually construct the inline recents list in the File menu.
+ * Hopefully gtk 2.12 will add support for it.
+ */
+static void
+update_recent_files_menu (PlumaWindow *window)
+{
+ PlumaWindowPrivate *p = window->priv;
+ GtkRecentManager *recent_manager;
+ gint max_recents;
+ GList *actions, *l, *items;
+ GList *filtered_items = NULL;
+ gint i;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ max_recents = pluma_prefs_manager_get_max_recents ();
+
+ g_return_if_fail (p->recents_action_group != NULL);
+
+ if (p->recents_menu_ui_id != 0)
+ gtk_ui_manager_remove_ui (p->manager,
+ p->recents_menu_ui_id);
+
+ actions = gtk_action_group_list_actions (p->recents_action_group);
+ for (l = actions; l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_func (GTK_ACTION (l->data),
+ G_CALLBACK (recents_menu_activate),
+ window);
+ gtk_action_group_remove_action (p->recents_action_group,
+ GTK_ACTION (l->data));
+ }
+ g_list_free (actions);
+
+ p->recents_menu_ui_id = gtk_ui_manager_new_merge_id (p->manager);
+
+ recent_manager = gtk_recent_manager_get_default ();
+ items = gtk_recent_manager_get_items (recent_manager);
+
+ /* filter */
+ for (l = items; l != NULL; l = l->next)
+ {
+ GtkRecentInfo *info = l->data;
+
+ if (!gtk_recent_info_has_group (info, "pluma"))
+ continue;
+
+ filtered_items = g_list_prepend (filtered_items, info);
+ }
+
+ /* sort */
+ filtered_items = g_list_sort (filtered_items,
+ (GCompareFunc) sort_recents_mru);
+
+ i = 0;
+ for (l = filtered_items; l != NULL; l = l->next)
+ {
+ gchar *action_name;
+ const gchar *display_name;
+ gchar *escaped;
+ gchar *label;
+ gchar *uri;
+ gchar *ruri;
+ gchar *tip;
+ GtkAction *action;
+ GtkRecentInfo *info = l->data;
+
+ /* clamp */
+ if (i >= max_recents)
+ break;
+
+ i++;
+
+ action_name = g_strdup_printf ("recent-info-%d", i);
+
+ display_name = gtk_recent_info_get_display_name (info);
+ escaped = pluma_utils_escape_underscores (display_name, -1);
+ if (i >= 10)
+ label = g_strdup_printf ("%d. %s",
+ i,
+ escaped);
+ else
+ label = g_strdup_printf ("_%d. %s",
+ i,
+ escaped);
+ g_free (escaped);
+
+ /* gtk_recent_info_get_uri_display (info) is buggy and
+ * works only for local files */
+ uri = pluma_utils_uri_for_display (gtk_recent_info_get_uri (info));
+ ruri = pluma_utils_replace_home_dir_with_tilde (uri);
+ g_free (uri);
+
+ /* Translators: %s is a URI */
+ tip = g_strdup_printf (_("Open '%s'"), ruri);
+ g_free (ruri);
+
+ action = gtk_action_new (action_name,
+ label,
+ tip,
+ NULL);
+
+ g_object_set_data_full (G_OBJECT (action),
+ "gtk-recent-info",
+ gtk_recent_info_ref (info),
+ (GDestroyNotify) gtk_recent_info_unref);
+
+ g_signal_connect (action,
+ "activate",
+ G_CALLBACK (recents_menu_activate),
+ window);
+
+ gtk_action_group_add_action (p->recents_action_group,
+ action);
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (p->manager,
+ p->recents_menu_ui_id,
+ "/MenuBar/FileMenu/FileRecentsPlaceholder",
+ action_name,
+ action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ g_free (action_name);
+ g_free (label);
+ g_free (tip);
+ }
+
+ g_list_free (filtered_items);
+
+ g_list_foreach (items, (GFunc) gtk_recent_info_unref, NULL);
+ g_list_free (items);
+}
+
+static void
+set_non_homogeneus (GtkWidget *widget, gpointer data)
+{
+ gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
+}
+
+static void
+toolbar_visibility_changed (GtkWidget *toolbar,
+ PlumaWindow *window)
+{
+ gboolean visible;
+ GtkAction *action;
+
+ visible = GTK_WIDGET_VISIBLE (toolbar);
+
+ if (pluma_prefs_manager_toolbar_visible_can_set ())
+ pluma_prefs_manager_set_toolbar_visible (visible);
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "ViewToolbar");
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) != visible)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
+}
+
+static GtkWidget *
+setup_toolbar_open_button (PlumaWindow *window,
+ GtkWidget *toolbar)
+{
+ GtkRecentManager *recent_manager;
+ GtkRecentFilter *filter;
+ GtkWidget *toolbar_recent_menu;
+ GtkToolItem *open_button;
+ GtkAction *action;
+
+ recent_manager = gtk_recent_manager_get_default ();
+
+ /* recent files menu tool button */
+ toolbar_recent_menu = gtk_recent_chooser_menu_new_for_manager (recent_manager);
+
+ gtk_recent_chooser_set_local_only (GTK_RECENT_CHOOSER (toolbar_recent_menu),
+ FALSE);
+ gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (toolbar_recent_menu),
+ GTK_RECENT_SORT_MRU);
+ gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER (toolbar_recent_menu),
+ pluma_prefs_manager_get_max_recents ());
+
+ filter = gtk_recent_filter_new ();
+ gtk_recent_filter_add_group (filter, "pluma");
+ gtk_recent_chooser_set_filter (GTK_RECENT_CHOOSER (toolbar_recent_menu),
+ filter);
+
+ g_signal_connect (toolbar_recent_menu,
+ "item_activated",
+ G_CALLBACK (recent_chooser_item_activated),
+ window);
+
+ /* add the custom Open button to the toolbar */
+ open_button = gtk_menu_tool_button_new_from_stock (GTK_STOCK_OPEN);
+ gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (open_button),
+ toolbar_recent_menu);
+
+ gtk_tool_item_set_tooltip_text (open_button, _("Open a file"));
+ gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (open_button),
+ _("Open a recently used file"));
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "FileOpen");
+ g_object_set (action,
+ "is_important", TRUE,
+ "short_label", _("Open"),
+ NULL);
+ gtk_activatable_set_related_action (GTK_ACTIVATABLE (open_button),
+ action);
+
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
+ open_button,
+ 1);
+
+ return toolbar_recent_menu;
+}
+
+static void
+create_menu_bar_and_toolbar (PlumaWindow *window,
+ GtkWidget *main_box)
+{
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ GtkUIManager *manager;
+ GtkRecentManager *recent_manager;
+ GError *error = NULL;
+ gchar *ui_file;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ manager = gtk_ui_manager_new ();
+ window->priv->manager = manager;
+
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ gtk_ui_manager_get_accel_group (manager));
+
+ action_group = gtk_action_group_new ("PlumaWindowAlwaysSensitiveActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ pluma_always_sensitive_menu_entries,
+ G_N_ELEMENTS (pluma_always_sensitive_menu_entries),
+ window);
+ gtk_action_group_add_toggle_actions (action_group,
+ pluma_always_sensitive_toggle_menu_entries,
+ G_N_ELEMENTS (pluma_always_sensitive_toggle_menu_entries),
+ window);
+
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+ window->priv->always_sensitive_action_group = action_group;
+
+ action_group = gtk_action_group_new ("PlumaWindowActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ pluma_menu_entries,
+ G_N_ELEMENTS (pluma_menu_entries),
+ window);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+ window->priv->action_group = action_group;
+
+ /* set short labels to use in the toolbar */
+ action = gtk_action_group_get_action (action_group, "FileSave");
+ g_object_set (action, "short_label", _("Save"), NULL);
+ action = gtk_action_group_get_action (action_group, "FilePrint");
+ g_object_set (action, "short_label", _("Print"), NULL);
+ action = gtk_action_group_get_action (action_group, "SearchFind");
+ g_object_set (action, "short_label", _("Find"), NULL);
+ action = gtk_action_group_get_action (action_group, "SearchReplace");
+ g_object_set (action, "short_label", _("Replace"), NULL);
+
+ /* set which actions should have priority on the toolbar */
+ action = gtk_action_group_get_action (action_group, "FileSave");
+ g_object_set (action, "is_important", TRUE, NULL);
+ action = gtk_action_group_get_action (action_group, "EditUndo");
+ g_object_set (action, "is_important", TRUE, NULL);
+
+ action_group = gtk_action_group_new ("PlumaQuitWindowActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ pluma_quit_menu_entries,
+ G_N_ELEMENTS (pluma_quit_menu_entries),
+ window);
+
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+ window->priv->quit_action_group = action_group;
+
+ action_group = gtk_action_group_new ("PlumaCloseWindowActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ pluma_close_menu_entries,
+ G_N_ELEMENTS (pluma_close_menu_entries),
+ window);
+
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+ window->priv->close_action_group = action_group;
+
+ action_group = gtk_action_group_new ("PlumaWindowPanesActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_toggle_actions (action_group,
+ pluma_panes_toggle_menu_entries,
+ G_N_ELEMENTS (pluma_panes_toggle_menu_entries),
+ window);
+
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+ window->priv->panes_action_group = action_group;
+
+ /* now load the UI definition */
+ ui_file = pluma_dirs_get_ui_file (PLUMA_UIFILE);
+ gtk_ui_manager_add_ui_from_file (manager, ui_file, &error);
+ if (error != NULL)
+ {
+ g_warning ("Could not merge %s: %s", ui_file, error->message);
+ g_error_free (error);
+ }
+ g_free (ui_file);
+
+#if !GTK_CHECK_VERSION (2, 17, 4)
+ /* merge page setup menu manually since we cannot have conditional
+ * sections in pluma-ui.xml */
+ {
+ guint merge_id;
+ PlumaLockdownMask lockdown;
+
+ merge_id = gtk_ui_manager_new_merge_id (manager);
+ gtk_ui_manager_add_ui (manager,
+ merge_id,
+ "/MenuBar/FileMenu/FileOps_5",
+ "FilePageSetupMenu",
+ "FilePageSetup",
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ lockdown = pluma_app_get_lockdown (pluma_app_get_default ());
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FilePageSetup");
+ gtk_action_set_sensitive (action,
+ !(lockdown & PLUMA_LOCKDOWN_PRINT_SETUP));
+ }
+#endif
+
+ /* show tooltips in the statusbar */
+ g_signal_connect (manager,
+ "connect_proxy",
+ G_CALLBACK (connect_proxy_cb),
+ window);
+ g_signal_connect (manager,
+ "disconnect_proxy",
+ G_CALLBACK (disconnect_proxy_cb),
+ window);
+
+ /* recent files menu */
+ action_group = gtk_action_group_new ("RecentFilesActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ window->priv->recents_action_group = action_group;
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+
+ recent_manager = gtk_recent_manager_get_default ();
+ window->priv->recents_handler_id = g_signal_connect (recent_manager,
+ "changed",
+ G_CALLBACK (recent_manager_changed),
+ window);
+ update_recent_files_menu (window);
+
+ /* languages menu */
+ action_group = gtk_action_group_new ("LanguagesActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ window->priv->languages_action_group = action_group;
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+ create_languages_menu (window);
+
+ /* list of open documents menu */
+ action_group = gtk_action_group_new ("DocumentsListActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ window->priv->documents_list_action_group = action_group;
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ g_object_unref (action_group);
+
+ window->priv->menubar = gtk_ui_manager_get_widget (manager, "/MenuBar");
+ gtk_box_pack_start (GTK_BOX (main_box),
+ window->priv->menubar,
+ FALSE,
+ FALSE,
+ 0);
+
+ window->priv->toolbar = gtk_ui_manager_get_widget (manager, "/ToolBar");
+ gtk_box_pack_start (GTK_BOX (main_box),
+ window->priv->toolbar,
+ FALSE,
+ FALSE,
+ 0);
+
+ set_toolbar_style (window, NULL);
+
+ window->priv->toolbar_recent_menu = setup_toolbar_open_button (window,
+ window->priv->toolbar);
+
+ gtk_container_foreach (GTK_CONTAINER (window->priv->toolbar),
+ (GtkCallback)set_non_homogeneus,
+ NULL);
+
+ g_signal_connect_after (G_OBJECT (window->priv->toolbar),
+ "show",
+ G_CALLBACK (toolbar_visibility_changed),
+ window);
+ g_signal_connect_after (G_OBJECT (window->priv->toolbar),
+ "hide",
+ G_CALLBACK (toolbar_visibility_changed),
+ window);
+}
+
+static void
+documents_list_menu_activate (GtkToggleAction *action,
+ PlumaWindow *window)
+{
+ gint n;
+
+ if (gtk_toggle_action_get_active (action) == FALSE)
+ return;
+
+ n = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (action));
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (window->priv->notebook), n);
+}
+
+static gchar *
+get_menu_tip_for_tab (PlumaTab *tab)
+{
+ PlumaDocument *doc;
+ gchar *uri;
+ gchar *ruri;
+ gchar *tip;
+
+ doc = pluma_tab_get_document (tab);
+
+ uri = pluma_document_get_uri_for_display (doc);
+ ruri = pluma_utils_replace_home_dir_with_tilde (uri);
+ g_free (uri);
+
+ /* Translators: %s is a URI */
+ tip = g_strdup_printf (_("Activate '%s'"), ruri);
+ g_free (ruri);
+
+ return tip;
+}
+
+static void
+update_documents_list_menu (PlumaWindow *window)
+{
+ PlumaWindowPrivate *p = window->priv;
+ GList *actions, *l;
+ gint n, i;
+ guint id;
+ GSList *group = NULL;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ g_return_if_fail (p->documents_list_action_group != NULL);
+
+ if (p->documents_list_menu_ui_id != 0)
+ gtk_ui_manager_remove_ui (p->manager,
+ p->documents_list_menu_ui_id);
+
+ actions = gtk_action_group_list_actions (p->documents_list_action_group);
+ for (l = actions; l != NULL; l = l->next)
+ {
+ g_signal_handlers_disconnect_by_func (GTK_ACTION (l->data),
+ G_CALLBACK (documents_list_menu_activate),
+ window);
+ gtk_action_group_remove_action (p->documents_list_action_group,
+ GTK_ACTION (l->data));
+ }
+ g_list_free (actions);
+
+ n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (p->notebook));
+
+ id = (n > 0) ? gtk_ui_manager_new_merge_id (p->manager) : 0;
+
+ for (i = 0; i < n; i++)
+ {
+ GtkWidget *tab;
+ GtkRadioAction *action;
+ gchar *action_name;
+ gchar *tab_name;
+ gchar *name;
+ gchar *tip;
+ gchar *accel;
+
+ tab = gtk_notebook_get_nth_page (GTK_NOTEBOOK (p->notebook), i);
+
+ /* NOTE: the action is associated to the position of the tab in
+ * the notebook not to the tab itself! This is needed to work
+ * around the gtk+ bug #170727: gtk leaves around the accels
+ * of the action. Since the accel depends on the tab position
+ * the problem is worked around, action with the same name always
+ * get the same accel.
+ */
+ action_name = g_strdup_printf ("Tab_%d", i);
+ tab_name = _pluma_tab_get_name (PLUMA_TAB (tab));
+ name = pluma_utils_escape_underscores (tab_name, -1);
+ tip = get_menu_tip_for_tab (PLUMA_TAB (tab));
+
+ /* alt + 1, 2, 3... 0 to switch to the first ten tabs */
+ accel = (i < 10) ? g_strdup_printf ("<alt>%d", (i + 1) % 10) : NULL;
+
+ action = gtk_radio_action_new (action_name,
+ name,
+ tip,
+ NULL,
+ i);
+
+ if (group != NULL)
+ gtk_radio_action_set_group (action, group);
+
+ /* note that group changes each time we add an action, so it must be updated */
+ group = gtk_radio_action_get_group (action);
+
+ gtk_action_group_add_action_with_accel (p->documents_list_action_group,
+ GTK_ACTION (action),
+ accel);
+
+ g_signal_connect (action,
+ "activate",
+ G_CALLBACK (documents_list_menu_activate),
+ window);
+
+ gtk_ui_manager_add_ui (p->manager,
+ id,
+ "/MenuBar/DocumentsMenu/DocumentsListPlaceholder",
+ action_name, action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ if (PLUMA_TAB (tab) == p->active_tab)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+
+ g_object_unref (action);
+
+ g_free (action_name);
+ g_free (tab_name);
+ g_free (name);
+ g_free (tip);
+ g_free (accel);
+ }
+
+ p->documents_list_menu_ui_id = id;
+}
+
+/* Returns TRUE if status bar is visible */
+static gboolean
+set_statusbar_style (PlumaWindow *window,
+ PlumaWindow *origin)
+{
+ GtkAction *action;
+
+ gboolean visible;
+
+ if (origin == NULL)
+ visible = pluma_prefs_manager_get_statusbar_visible ();
+ else
+ visible = GTK_WIDGET_VISIBLE (origin->priv->statusbar);
+
+ if (visible)
+ gtk_widget_show (window->priv->statusbar);
+ else
+ gtk_widget_hide (window->priv->statusbar);
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "ViewStatusbar");
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) != visible)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
+
+ return visible;
+}
+
+static void
+statusbar_visibility_changed (GtkWidget *statusbar,
+ PlumaWindow *window)
+{
+ gboolean visible;
+ GtkAction *action;
+
+ visible = GTK_WIDGET_VISIBLE (statusbar);
+
+ if (pluma_prefs_manager_statusbar_visible_can_set ())
+ pluma_prefs_manager_set_statusbar_visible (visible);
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "ViewStatusbar");
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) != visible)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
+}
+
+static void
+tab_width_combo_changed (PlumaStatusComboBox *combo,
+ GtkMenuItem *item,
+ PlumaWindow *window)
+{
+ PlumaView *view;
+ guint width_data = 0;
+
+ view = pluma_window_get_active_view (window);
+
+ if (!view)
+ return;
+
+ width_data = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), TAB_WIDTH_DATA));
+
+ if (width_data == 0)
+ return;
+
+ g_signal_handler_block (view, window->priv->tab_width_id);
+ gtk_source_view_set_tab_width (GTK_SOURCE_VIEW (view), width_data);
+ g_signal_handler_unblock (view, window->priv->tab_width_id);
+}
+
+static void
+use_spaces_toggled (GtkCheckMenuItem *item,
+ PlumaWindow *window)
+{
+ PlumaView *view;
+
+ view = pluma_window_get_active_view (window);
+
+ g_signal_handler_block (view, window->priv->spaces_instead_of_tabs_id);
+ gtk_source_view_set_insert_spaces_instead_of_tabs (
+ GTK_SOURCE_VIEW (view),
+ gtk_check_menu_item_get_active (item));
+ g_signal_handler_unblock (view, window->priv->spaces_instead_of_tabs_id);
+}
+
+static void
+language_combo_changed (PlumaStatusComboBox *combo,
+ GtkMenuItem *item,
+ PlumaWindow *window)
+{
+ PlumaDocument *doc;
+ GtkSourceLanguage *language;
+
+ doc = pluma_window_get_active_document (window);
+
+ if (!doc)
+ return;
+
+ language = GTK_SOURCE_LANGUAGE (g_object_get_data (G_OBJECT (item), LANGUAGE_DATA));
+
+ g_signal_handler_block (doc, window->priv->language_changed_id);
+ pluma_document_set_language (doc, language);
+ g_signal_handler_unblock (doc, window->priv->language_changed_id);
+}
+
+typedef struct
+{
+ const gchar *label;
+ guint width;
+} TabWidthDefinition;
+
+static void
+fill_tab_width_combo (PlumaWindow *window)
+{
+ static TabWidthDefinition defs[] = {
+ {"2", 2},
+ {"4", 4},
+ {"8", 8},
+ {"", 0}, /* custom size */
+ {NULL, 0}
+ };
+
+ PlumaStatusComboBox *combo = PLUMA_STATUS_COMBO_BOX (window->priv->tab_width_combo);
+ guint i = 0;
+ GtkWidget *item;
+
+ while (defs[i].label != NULL)
+ {
+ item = gtk_menu_item_new_with_label (defs[i].label);
+ g_object_set_data (G_OBJECT (item), TAB_WIDTH_DATA, GINT_TO_POINTER (defs[i].width));
+
+ pluma_status_combo_box_add_item (combo,
+ GTK_MENU_ITEM (item),
+ defs[i].label);
+
+ if (defs[i].width != 0)
+ gtk_widget_show (item);
+
+ ++i;
+ }
+
+ item = gtk_separator_menu_item_new ();
+ pluma_status_combo_box_add_item (combo, GTK_MENU_ITEM (item), NULL);
+ gtk_widget_show (item);
+
+ item = gtk_check_menu_item_new_with_label (_("Use Spaces"));
+ pluma_status_combo_box_add_item (combo, GTK_MENU_ITEM (item), NULL);
+ gtk_widget_show (item);
+
+ g_signal_connect (item,
+ "toggled",
+ G_CALLBACK (use_spaces_toggled),
+ window);
+}
+
+static void
+fill_language_combo (PlumaWindow *window)
+{
+ GtkSourceLanguageManager *manager;
+ GSList *languages;
+ GSList *item;
+ GtkWidget *menu_item;
+ const gchar *name;
+
+ manager = pluma_get_language_manager ();
+ languages = pluma_language_manager_list_languages_sorted (manager, FALSE);
+
+ name = _("Plain Text");
+ menu_item = gtk_menu_item_new_with_label (name);
+ gtk_widget_show (menu_item);
+
+ g_object_set_data (G_OBJECT (menu_item), LANGUAGE_DATA, NULL);
+ pluma_status_combo_box_add_item (PLUMA_STATUS_COMBO_BOX (window->priv->language_combo),
+ GTK_MENU_ITEM (menu_item),
+ name);
+
+ for (item = languages; item; item = item->next)
+ {
+ GtkSourceLanguage *lang = GTK_SOURCE_LANGUAGE (item->data);
+
+ name = gtk_source_language_get_name (lang);
+ menu_item = gtk_menu_item_new_with_label (name);
+ gtk_widget_show (menu_item);
+
+ g_object_set_data_full (G_OBJECT (menu_item),
+ LANGUAGE_DATA,
+ g_object_ref (lang),
+ (GDestroyNotify)g_object_unref);
+
+ pluma_status_combo_box_add_item (PLUMA_STATUS_COMBO_BOX (window->priv->language_combo),
+ GTK_MENU_ITEM (menu_item),
+ name);
+ }
+
+ g_slist_free (languages);
+}
+
+static void
+create_statusbar (PlumaWindow *window,
+ GtkWidget *main_box)
+{
+ pluma_debug (DEBUG_WINDOW);
+
+ window->priv->statusbar = pluma_statusbar_new ();
+
+ window->priv->generic_message_cid = gtk_statusbar_get_context_id
+ (GTK_STATUSBAR (window->priv->statusbar), "generic_message");
+ window->priv->tip_message_cid = gtk_statusbar_get_context_id
+ (GTK_STATUSBAR (window->priv->statusbar), "tip_message");
+
+ gtk_box_pack_end (GTK_BOX (main_box),
+ window->priv->statusbar,
+ FALSE,
+ TRUE,
+ 0);
+
+ window->priv->tab_width_combo = pluma_status_combo_box_new (_("Tab Width"));
+ gtk_widget_show (window->priv->tab_width_combo);
+ gtk_box_pack_end (GTK_BOX (window->priv->statusbar),
+ window->priv->tab_width_combo,
+ FALSE,
+ TRUE,
+ 0);
+
+ fill_tab_width_combo (window);
+
+ g_signal_connect (G_OBJECT (window->priv->tab_width_combo),
+ "changed",
+ G_CALLBACK (tab_width_combo_changed),
+ window);
+
+ window->priv->language_combo = pluma_status_combo_box_new (NULL);
+ gtk_widget_show (window->priv->language_combo);
+ gtk_box_pack_end (GTK_BOX (window->priv->statusbar),
+ window->priv->language_combo,
+ FALSE,
+ TRUE,
+ 0);
+
+ fill_language_combo (window);
+
+ g_signal_connect (G_OBJECT (window->priv->language_combo),
+ "changed",
+ G_CALLBACK (language_combo_changed),
+ window);
+
+ g_signal_connect_after (G_OBJECT (window->priv->statusbar),
+ "show",
+ G_CALLBACK (statusbar_visibility_changed),
+ window);
+ g_signal_connect_after (G_OBJECT (window->priv->statusbar),
+ "hide",
+ G_CALLBACK (statusbar_visibility_changed),
+ window);
+
+ set_statusbar_style (window, NULL);
+}
+
+static PlumaWindow *
+clone_window (PlumaWindow *origin)
+{
+ PlumaWindow *window;
+ GdkScreen *screen;
+ PlumaApp *app;
+ gint panel_page;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ app = pluma_app_get_default ();
+
+ screen = gtk_window_get_screen (GTK_WINDOW (origin));
+ window = pluma_app_create_window (app, screen);
+
+ if ((origin->priv->window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0)
+ {
+ gint w, h;
+
+ pluma_prefs_manager_get_default_window_size (&w, &h);
+ gtk_window_set_default_size (GTK_WINDOW (window), w, h);
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ else
+ {
+ gtk_window_set_default_size (GTK_WINDOW (window),
+ origin->priv->width,
+ origin->priv->height);
+
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ }
+
+ if ((origin->priv->window_state & GDK_WINDOW_STATE_STICKY ) != 0)
+ gtk_window_stick (GTK_WINDOW (window));
+ else
+ gtk_window_unstick (GTK_WINDOW (window));
+
+ /* set the panes size, the paned position will be set when
+ * they are mapped */
+ window->priv->side_panel_size = origin->priv->side_panel_size;
+ window->priv->bottom_panel_size = origin->priv->bottom_panel_size;
+
+ panel_page = _pluma_panel_get_active_item_id (PLUMA_PANEL (origin->priv->side_panel));
+ _pluma_panel_set_active_item_by_id (PLUMA_PANEL (window->priv->side_panel),
+ panel_page);
+
+ panel_page = _pluma_panel_get_active_item_id (PLUMA_PANEL (origin->priv->bottom_panel));
+ _pluma_panel_set_active_item_by_id (PLUMA_PANEL (window->priv->bottom_panel),
+ panel_page);
+
+ if (GTK_WIDGET_VISIBLE (origin->priv->side_panel))
+ gtk_widget_show (window->priv->side_panel);
+ else
+ gtk_widget_hide (window->priv->side_panel);
+
+ if (GTK_WIDGET_VISIBLE (origin->priv->bottom_panel))
+ gtk_widget_show (window->priv->bottom_panel);
+ else
+ gtk_widget_hide (window->priv->bottom_panel);
+
+ set_statusbar_style (window, origin);
+ set_toolbar_style (window, origin);
+
+ return window;
+}
+
+static void
+update_cursor_position_statusbar (GtkTextBuffer *buffer,
+ PlumaWindow *window)
+{
+ gint row, col;
+ GtkTextIter iter;
+ GtkTextIter start;
+ guint tab_size;
+ PlumaView *view;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ if (buffer != GTK_TEXT_BUFFER (pluma_window_get_active_document (window)))
+ return;
+
+ view = pluma_window_get_active_view (window);
+
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter,
+ gtk_text_buffer_get_insert (buffer));
+
+ row = gtk_text_iter_get_line (&iter);
+
+ start = iter;
+ gtk_text_iter_set_line_offset (&start, 0);
+ col = 0;
+
+ tab_size = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (view));
+
+ while (!gtk_text_iter_equal (&start, &iter))
+ {
+ /* FIXME: Are we Unicode compliant here? */
+ if (gtk_text_iter_get_char (&start) == '\t')
+
+ col += (tab_size - (col % tab_size));
+ else
+ ++col;
+
+ gtk_text_iter_forward_char (&start);
+ }
+
+ pluma_statusbar_set_cursor_position (
+ PLUMA_STATUSBAR (window->priv->statusbar),
+ row + 1,
+ col + 1);
+}
+
+static void
+update_overwrite_mode_statusbar (GtkTextView *view,
+ PlumaWindow *window)
+{
+ if (view != GTK_TEXT_VIEW (pluma_window_get_active_view (window)))
+ return;
+
+ /* Note that we have to use !gtk_text_view_get_overwrite since we
+ are in the in the signal handler of "toggle overwrite" that is
+ G_SIGNAL_RUN_LAST
+ */
+ pluma_statusbar_set_overwrite (
+ PLUMA_STATUSBAR (window->priv->statusbar),
+ !gtk_text_view_get_overwrite (view));
+}
+
+#define MAX_TITLE_LENGTH 100
+
+static void
+set_title (PlumaWindow *window)
+{
+ PlumaDocument *doc = NULL;
+ gchar *name;
+ gchar *dirname = NULL;
+ gchar *title = NULL;
+ gint len;
+
+ if (window->priv->active_tab == NULL)
+ {
+#ifdef OS_OSX
+ pluma_osx_set_window_title (window, "pluma", NULL);
+#else
+ gtk_window_set_title (GTK_WINDOW (window), "pluma");
+#endif
+ return;
+ }
+
+ doc = pluma_tab_get_document (window->priv->active_tab);
+ g_return_if_fail (doc != NULL);
+
+ name = pluma_document_get_short_name_for_display (doc);
+
+ len = g_utf8_strlen (name, -1);
+
+ /* if the name is awfully long, truncate it and be done with it,
+ * otherwise also show the directory (ellipsized if needed)
+ */
+ if (len > MAX_TITLE_LENGTH)
+ {
+ gchar *tmp;
+
+ tmp = pluma_utils_str_middle_truncate (name,
+ MAX_TITLE_LENGTH);
+ g_free (name);
+ name = tmp;
+ }
+ else
+ {
+ GFile *file;
+
+ file = pluma_document_get_location (doc);
+ if (file != NULL)
+ {
+ gchar *str;
+
+ str = pluma_utils_location_get_dirname_for_display (file);
+ g_object_unref (file);
+
+ /* use the remaining space for the dir, but use a min of 20 chars
+ * so that we do not end up with a dirname like "(a...b)".
+ * This means that in the worst case when the filename is long 99
+ * we have a title long 99 + 20, but I think it's a rare enough
+ * case to be acceptable. It's justa darn title afterall :)
+ */
+ dirname = pluma_utils_str_middle_truncate (str,
+ MAX (20, MAX_TITLE_LENGTH - len));
+ g_free (str);
+ }
+ }
+
+ if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
+ {
+ gchar *tmp_name;
+
+ tmp_name = g_strdup_printf ("*%s", name);
+ g_free (name);
+
+ name = tmp_name;
+ }
+
+ if (pluma_document_get_readonly (doc))
+ {
+ if (dirname != NULL)
+ title = g_strdup_printf ("%s [%s] (%s) - pluma",
+ name,
+ _("Read-Only"),
+ dirname);
+ else
+ title = g_strdup_printf ("%s [%s] - pluma",
+ name,
+ _("Read-Only"));
+ }
+ else
+ {
+ if (dirname != NULL)
+ title = g_strdup_printf ("%s (%s) - pluma",
+ name,
+ dirname);
+ else
+ title = g_strdup_printf ("%s - pluma",
+ name);
+ }
+
+#ifdef OS_OSX
+ pluma_osx_set_window_title (window, title, doc);
+#else
+ gtk_window_set_title (GTK_WINDOW (window), title);
+#endif
+
+ g_free (dirname);
+ g_free (name);
+ g_free (title);
+}
+
+#undef MAX_TITLE_LENGTH
+
+static void
+set_tab_width_item_blocked (PlumaWindow *window,
+ GtkMenuItem *item)
+{
+ g_signal_handlers_block_by_func (window->priv->tab_width_combo,
+ tab_width_combo_changed,
+ window);
+
+ pluma_status_combo_box_set_item (PLUMA_STATUS_COMBO_BOX (window->priv->tab_width_combo),
+ item);
+
+ g_signal_handlers_unblock_by_func (window->priv->tab_width_combo,
+ tab_width_combo_changed,
+ window);
+}
+
+static void
+spaces_instead_of_tabs_changed (GObject *object,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ PlumaView *view = PLUMA_VIEW (object);
+ gboolean active = gtk_source_view_get_insert_spaces_instead_of_tabs (
+ GTK_SOURCE_VIEW (view));
+ GList *children = pluma_status_combo_box_get_items (
+ PLUMA_STATUS_COMBO_BOX (window->priv->tab_width_combo));
+ GtkCheckMenuItem *item;
+
+ item = GTK_CHECK_MENU_ITEM (g_list_last (children)->data);
+
+ gtk_check_menu_item_set_active (item, active);
+
+ g_list_free (children);
+}
+
+static void
+tab_width_changed (GObject *object,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ GList *items;
+ GList *item;
+ PlumaStatusComboBox *combo = PLUMA_STATUS_COMBO_BOX (window->priv->tab_width_combo);
+ guint new_tab_width;
+ gboolean found = FALSE;
+
+ items = pluma_status_combo_box_get_items (combo);
+
+ new_tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (object));
+
+ for (item = items; item; item = item->next)
+ {
+ guint tab_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item->data), TAB_WIDTH_DATA));
+
+ if (tab_width == new_tab_width)
+ {
+ set_tab_width_item_blocked (window, GTK_MENU_ITEM (item->data));
+ found = TRUE;
+ }
+
+ if (GTK_IS_SEPARATOR_MENU_ITEM (item->next->data))
+ {
+ if (!found)
+ {
+ /* Set for the last item the custom thing */
+ gchar *text;
+
+ text = g_strdup_printf ("%u", new_tab_width);
+ pluma_status_combo_box_set_item_text (combo,
+ GTK_MENU_ITEM (item->data),
+ text);
+
+ gtk_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (item->data))),
+ text);
+
+ set_tab_width_item_blocked (window, GTK_MENU_ITEM (item->data));
+ gtk_widget_show (GTK_WIDGET (item->data));
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (item->data));
+ }
+
+ break;
+ }
+ }
+
+ g_list_free (items);
+}
+
+static void
+language_changed (GObject *object,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ GList *items;
+ GList *item;
+ PlumaStatusComboBox *combo = PLUMA_STATUS_COMBO_BOX (window->priv->language_combo);
+ GtkSourceLanguage *new_language;
+ const gchar *new_id;
+
+ items = pluma_status_combo_box_get_items (combo);
+
+ new_language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (object));
+
+ if (new_language)
+ new_id = gtk_source_language_get_id (new_language);
+ else
+ new_id = NULL;
+
+ for (item = items; item; item = item->next)
+ {
+ GtkSourceLanguage *lang = g_object_get_data (G_OBJECT (item->data), LANGUAGE_DATA);
+
+ if ((new_id == NULL && lang == NULL) ||
+ (new_id != NULL && lang != NULL && strcmp (gtk_source_language_get_id (lang),
+ new_id) == 0))
+ {
+ g_signal_handlers_block_by_func (window->priv->language_combo,
+ language_combo_changed,
+ window);
+
+ pluma_status_combo_box_set_item (PLUMA_STATUS_COMBO_BOX (window->priv->language_combo),
+ GTK_MENU_ITEM (item->data));
+
+ g_signal_handlers_unblock_by_func (window->priv->language_combo,
+ language_combo_changed,
+ window);
+ }
+ }
+
+ g_list_free (items);
+}
+
+static void
+notebook_switch_page (GtkNotebook *book,
+ GtkNotebookPage *pg,
+ gint page_num,
+ PlumaWindow *window)
+{
+ PlumaView *view;
+ PlumaTab *tab;
+ GtkAction *action;
+ gchar *action_name;
+
+ /* CHECK: I don't know why but it seems notebook_switch_page is called
+ two times every time the user change the active tab */
+
+ tab = PLUMA_TAB (gtk_notebook_get_nth_page (book, page_num));
+ if (tab == window->priv->active_tab)
+ return;
+
+ if (window->priv->active_tab)
+ {
+ if (window->priv->tab_width_id)
+ {
+ g_signal_handler_disconnect (pluma_tab_get_view (window->priv->active_tab),
+ window->priv->tab_width_id);
+
+ window->priv->tab_width_id = 0;
+ }
+
+ if (window->priv->spaces_instead_of_tabs_id)
+ {
+ g_signal_handler_disconnect (pluma_tab_get_view (window->priv->active_tab),
+ window->priv->spaces_instead_of_tabs_id);
+
+ window->priv->spaces_instead_of_tabs_id = 0;
+ }
+ }
+
+ /* set the active tab */
+ window->priv->active_tab = tab;
+
+ set_title (window);
+ set_sensitivity_according_to_tab (window, tab);
+
+ /* activate the right item in the documents menu */
+ action_name = g_strdup_printf ("Tab_%d", page_num);
+ action = gtk_action_group_get_action (window->priv->documents_list_action_group,
+ action_name);
+
+ /* sometimes the action doesn't exist yet, and the proper action
+ * is set active during the documents list menu creation
+ * CHECK: would it be nicer if active_tab was a property and we monitored the notify signal?
+ */
+ if (action != NULL)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+
+ g_free (action_name);
+
+ /* update the syntax menu */
+ update_languages_menu (window);
+
+ view = pluma_tab_get_view (tab);
+
+ /* sync the statusbar */
+ update_cursor_position_statusbar (GTK_TEXT_BUFFER (pluma_tab_get_document (tab)),
+ window);
+ pluma_statusbar_set_overwrite (PLUMA_STATUSBAR (window->priv->statusbar),
+ gtk_text_view_get_overwrite (GTK_TEXT_VIEW (view)));
+
+ gtk_widget_show (window->priv->tab_width_combo);
+ gtk_widget_show (window->priv->language_combo);
+
+ window->priv->tab_width_id = g_signal_connect (view,
+ "notify::tab-width",
+ G_CALLBACK (tab_width_changed),
+ window);
+ window->priv->spaces_instead_of_tabs_id = g_signal_connect (view,
+ "notify::insert-spaces-instead-of-tabs",
+ G_CALLBACK (spaces_instead_of_tabs_changed),
+ window);
+
+ window->priv->language_changed_id = g_signal_connect (pluma_tab_get_document (tab),
+ "notify::language",
+ G_CALLBACK (language_changed),
+ window);
+
+ /* call it for the first time */
+ tab_width_changed (G_OBJECT (view), NULL, window);
+ spaces_instead_of_tabs_changed (G_OBJECT (view), NULL, window);
+ language_changed (G_OBJECT (pluma_tab_get_document (tab)), NULL, window);
+
+ g_signal_emit (G_OBJECT (window),
+ signals[ACTIVE_TAB_CHANGED],
+ 0,
+ window->priv->active_tab);
+}
+
+static void
+set_sensitivity_according_to_window_state (PlumaWindow *window)
+{
+ GtkAction *action;
+ PlumaLockdownMask lockdown;
+
+ lockdown = pluma_app_get_lockdown (pluma_app_get_default ());
+
+ /* We disable File->Quit/SaveAll/CloseAll while printing to avoid to have two
+ operations (save and print/print preview) that uses the message area at
+ the same time (may be we can remove this limitation in the future) */
+ /* We disable File->Quit/CloseAll if state is saving since saving cannot be
+ cancelled (may be we can remove this limitation in the future) */
+ gtk_action_group_set_sensitive (window->priv->quit_action_group,
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING) &&
+ !(window->priv->state & PLUMA_WINDOW_STATE_PRINTING));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FileCloseAll");
+ gtk_action_set_sensitive (action,
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING) &&
+ !(window->priv->state & PLUMA_WINDOW_STATE_PRINTING));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FileSaveAll");
+ gtk_action_set_sensitive (action,
+ !(window->priv->state & PLUMA_WINDOW_STATE_PRINTING) &&
+ !(lockdown & PLUMA_LOCKDOWN_SAVE_TO_DISK));
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "FileNew");
+ gtk_action_set_sensitive (action,
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION));
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "FileOpen");
+ gtk_action_set_sensitive (action,
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION));
+
+ gtk_action_group_set_sensitive (window->priv->recents_action_group,
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION));
+
+ pluma_notebook_set_close_buttons_sensitive (PLUMA_NOTEBOOK (window->priv->notebook),
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION));
+
+ pluma_notebook_set_tab_drag_and_drop_enabled (PLUMA_NOTEBOOK (window->priv->notebook),
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION));
+
+ if ((window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION) != 0)
+ {
+ /* TODO: If we really care, Find could be active
+ * when in SAVING_SESSION state */
+
+ if (gtk_action_group_get_sensitive (window->priv->action_group))
+ gtk_action_group_set_sensitive (window->priv->action_group,
+ FALSE);
+ if (gtk_action_group_get_sensitive (window->priv->quit_action_group))
+ gtk_action_group_set_sensitive (window->priv->quit_action_group,
+ FALSE);
+ if (gtk_action_group_get_sensitive (window->priv->close_action_group))
+ gtk_action_group_set_sensitive (window->priv->close_action_group,
+ FALSE);
+ }
+ else
+ {
+ if (!gtk_action_group_get_sensitive (window->priv->action_group))
+ gtk_action_group_set_sensitive (window->priv->action_group,
+ window->priv->num_tabs > 0);
+ if (!gtk_action_group_get_sensitive (window->priv->quit_action_group))
+ gtk_action_group_set_sensitive (window->priv->quit_action_group,
+ window->priv->num_tabs > 0);
+ if (!gtk_action_group_get_sensitive (window->priv->close_action_group))
+ {
+#ifdef OS_OSX
+ /* On OS X, File Close is always sensitive */
+ gtk_action_group_set_sensitive (window->priv->close_action_group,
+ TRUE);
+#else
+ gtk_action_group_set_sensitive (window->priv->close_action_group,
+ window->priv->num_tabs > 0);
+#endif
+ }
+ }
+}
+
+static void
+update_tab_autosave (GtkWidget *widget,
+ gpointer data)
+{
+ PlumaTab *tab = PLUMA_TAB (widget);
+ gboolean *enabled = (gboolean *) data;
+
+ pluma_tab_set_auto_save_enabled (tab, *enabled);
+}
+
+void
+_pluma_window_set_lockdown (PlumaWindow *window,
+ PlumaLockdownMask lockdown)
+{
+ PlumaTab *tab;
+ GtkAction *action;
+ gboolean autosave;
+
+ /* start/stop autosave in each existing tab */
+ autosave = pluma_prefs_manager_get_auto_save ();
+ gtk_container_foreach (GTK_CONTAINER (window->priv->notebook),
+ update_tab_autosave,
+ &autosave);
+
+ /* update menues wrt the current active tab */
+ tab = pluma_window_get_active_tab (window);
+
+ set_sensitivity_according_to_tab (window, tab);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FileSaveAll");
+ gtk_action_set_sensitive (action,
+ !(window->priv->state & PLUMA_WINDOW_STATE_PRINTING) &&
+ !(lockdown & PLUMA_LOCKDOWN_SAVE_TO_DISK));
+
+#if !GTK_CHECK_VERSION (2, 17, 4)
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FilePageSetup");
+ gtk_action_set_sensitive (action,
+ !(lockdown & PLUMA_LOCKDOWN_PRINT_SETUP));
+#endif
+}
+
+static void
+analyze_tab_state (PlumaTab *tab,
+ PlumaWindow *window)
+{
+ PlumaTabState ts;
+
+ ts = pluma_tab_get_state (tab);
+
+ switch (ts)
+ {
+ case PLUMA_TAB_STATE_LOADING:
+ case PLUMA_TAB_STATE_REVERTING:
+ window->priv->state |= PLUMA_WINDOW_STATE_LOADING;
+ break;
+
+ case PLUMA_TAB_STATE_SAVING:
+ window->priv->state |= PLUMA_WINDOW_STATE_SAVING;
+ break;
+
+ case PLUMA_TAB_STATE_PRINTING:
+ case PLUMA_TAB_STATE_PRINT_PREVIEWING:
+ window->priv->state |= PLUMA_WINDOW_STATE_PRINTING;
+ break;
+
+ case PLUMA_TAB_STATE_LOADING_ERROR:
+ case PLUMA_TAB_STATE_REVERTING_ERROR:
+ case PLUMA_TAB_STATE_SAVING_ERROR:
+ case PLUMA_TAB_STATE_GENERIC_ERROR:
+ window->priv->state |= PLUMA_WINDOW_STATE_ERROR;
+ ++window->priv->num_tabs_with_error;
+ default:
+ /* NOP */
+ break;
+ }
+}
+
+static void
+update_window_state (PlumaWindow *window)
+{
+ PlumaWindowState old_ws;
+ gint old_num_of_errors;
+
+ pluma_debug_message (DEBUG_WINDOW, "Old state: %x", window->priv->state);
+
+ old_ws = window->priv->state;
+ old_num_of_errors = window->priv->num_tabs_with_error;
+
+ window->priv->state = old_ws & PLUMA_WINDOW_STATE_SAVING_SESSION;
+
+ window->priv->num_tabs_with_error = 0;
+
+ gtk_container_foreach (GTK_CONTAINER (window->priv->notebook),
+ (GtkCallback)analyze_tab_state,
+ window);
+
+ pluma_debug_message (DEBUG_WINDOW, "New state: %x", window->priv->state);
+
+ if (old_ws != window->priv->state)
+ {
+ set_sensitivity_according_to_window_state (window);
+
+ pluma_statusbar_set_window_state (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->state,
+ window->priv->num_tabs_with_error);
+
+ g_object_notify (G_OBJECT (window), "state");
+ }
+ else if (old_num_of_errors != window->priv->num_tabs_with_error)
+ {
+ pluma_statusbar_set_window_state (PLUMA_STATUSBAR (window->priv->statusbar),
+ window->priv->state,
+ window->priv->num_tabs_with_error);
+ }
+}
+
+static void
+sync_state (PlumaTab *tab,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ pluma_debug (DEBUG_WINDOW);
+
+ update_window_state (window);
+
+ if (tab != window->priv->active_tab)
+ return;
+
+ set_sensitivity_according_to_tab (window, tab);
+
+ g_signal_emit (G_OBJECT (window), signals[ACTIVE_TAB_STATE_CHANGED], 0);
+}
+
+static void
+sync_name (PlumaTab *tab,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ GtkAction *action;
+ gchar *action_name;
+ gchar *tab_name;
+ gchar *escaped_name;
+ gchar *tip;
+ gint n;
+ PlumaDocument *doc;
+
+ if (tab == window->priv->active_tab)
+ {
+ set_title (window);
+
+ doc = pluma_tab_get_document (tab);
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "FileRevert");
+ gtk_action_set_sensitive (action,
+ !pluma_document_is_untitled (doc));
+ }
+
+ /* sync the item in the documents list menu */
+ n = gtk_notebook_page_num (GTK_NOTEBOOK (window->priv->notebook),
+ GTK_WIDGET (tab));
+ action_name = g_strdup_printf ("Tab_%d", n);
+ action = gtk_action_group_get_action (window->priv->documents_list_action_group,
+ action_name);
+ g_return_if_fail (action != NULL);
+
+ tab_name = _pluma_tab_get_name (tab);
+ escaped_name = pluma_utils_escape_underscores (tab_name, -1);
+ tip = get_menu_tip_for_tab (tab);
+
+ g_object_set (action, "label", escaped_name, NULL);
+ g_object_set (action, "tooltip", tip, NULL);
+
+ g_free (action_name);
+ g_free (tab_name);
+ g_free (escaped_name);
+ g_free (tip);
+
+ pluma_plugins_engine_update_plugins_ui (pluma_plugins_engine_get_default (),
+ window);
+}
+
+static PlumaWindow *
+get_drop_window (GtkWidget *widget)
+{
+ GtkWidget *target_window;
+
+ target_window = gtk_widget_get_toplevel (widget);
+ g_return_val_if_fail (PLUMA_IS_WINDOW (target_window), NULL);
+
+ if ((PLUMA_WINDOW(target_window)->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION) != 0)
+ return NULL;
+
+ return PLUMA_WINDOW (target_window);
+}
+
+static void
+load_uris_from_drop (PlumaWindow *window,
+ gchar **uri_list)
+{
+ GSList *uris = NULL;
+ gint i;
+
+ if (uri_list == NULL)
+ return;
+
+ for (i = 0; uri_list[i] != NULL; ++i)
+ {
+ uris = g_slist_prepend (uris, uri_list[i]);
+ }
+
+ uris = g_slist_reverse (uris);
+ pluma_commands_load_uris (window,
+ uris,
+ NULL,
+ 0);
+
+ g_slist_free (uris);
+}
+
+/* Handle drops on the PlumaWindow */
+static void
+drag_data_received_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint timestamp,
+ gpointer data)
+{
+ PlumaWindow *window;
+ gchar **uri_list;
+
+ window = get_drop_window (widget);
+
+ if (window == NULL)
+ return;
+
+ if (info == TARGET_URI_LIST)
+ {
+ uri_list = pluma_utils_drop_get_uris(selection_data);
+ load_uris_from_drop (window, uri_list);
+ g_strfreev (uri_list);
+ }
+}
+
+/* Handle drops on the PlumaView */
+static void
+drop_uris_cb (GtkWidget *widget,
+ gchar **uri_list)
+{
+ PlumaWindow *window;
+
+ window = get_drop_window (widget);
+
+ if (window == NULL)
+ return;
+
+ load_uris_from_drop (window, uri_list);
+}
+
+static void
+fullscreen_controls_show (PlumaWindow *window)
+{
+ GdkScreen *screen;
+ GdkRectangle fs_rect;
+ gint w, h;
+
+ screen = gtk_window_get_screen (GTK_WINDOW (window));
+ gdk_screen_get_monitor_geometry (screen,
+ gdk_screen_get_monitor_at_window (screen,
+ gtk_widget_get_window (GTK_WIDGET (window))),
+ &fs_rect);
+
+ gtk_window_get_size (GTK_WINDOW (window->priv->fullscreen_controls), &w, &h);
+
+ gtk_window_resize (GTK_WINDOW (window->priv->fullscreen_controls),
+ fs_rect.width, h);
+
+ gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls),
+ fs_rect.x, fs_rect.y - h + 1);
+
+ gtk_widget_show_all (window->priv->fullscreen_controls);
+}
+
+static gboolean
+run_fullscreen_animation (gpointer data)
+{
+ PlumaWindow *window = PLUMA_WINDOW (data);
+ GdkScreen *screen;
+ GdkRectangle fs_rect;
+ gint x, y;
+
+ screen = gtk_window_get_screen (GTK_WINDOW (window));
+ gdk_screen_get_monitor_geometry (screen,
+ gdk_screen_get_monitor_at_window (screen,
+ gtk_widget_get_window (GTK_WIDGET (window))),
+ &fs_rect);
+
+ gtk_window_get_position (GTK_WINDOW (window->priv->fullscreen_controls),
+ &x, &y);
+
+ if (window->priv->fullscreen_animation_enter)
+ {
+ if (y == fs_rect.y)
+ {
+ window->priv->fullscreen_animation_timeout_id = 0;
+ return FALSE;
+ }
+ else
+ {
+ gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls),
+ x, y + 1);
+ return TRUE;
+ }
+ }
+ else
+ {
+ gint w, h;
+
+ gtk_window_get_size (GTK_WINDOW (window->priv->fullscreen_controls),
+ &w, &h);
+
+ if (y == fs_rect.y - h + 1)
+ {
+ window->priv->fullscreen_animation_timeout_id = 0;
+ return FALSE;
+ }
+ else
+ {
+ gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls),
+ x, y - 1);
+ return TRUE;
+ }
+ }
+}
+
+static void
+show_hide_fullscreen_toolbar (PlumaWindow *window,
+ gboolean show,
+ gint height)
+{
+ GtkSettings *settings;
+ gboolean enable_animations;
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (window));
+ g_object_get (G_OBJECT (settings),
+ "gtk-enable-animations",
+ &enable_animations,
+ NULL);
+
+ if (enable_animations)
+ {
+ window->priv->fullscreen_animation_enter = show;
+
+ if (window->priv->fullscreen_animation_timeout_id == 0)
+ {
+ window->priv->fullscreen_animation_timeout_id =
+ g_timeout_add (FULLSCREEN_ANIMATION_SPEED,
+ (GSourceFunc) run_fullscreen_animation,
+ window);
+ }
+ }
+ else
+ {
+ GdkRectangle fs_rect;
+ GdkScreen *screen;
+
+ screen = gtk_window_get_screen (GTK_WINDOW (window));
+ gdk_screen_get_monitor_geometry (screen,
+ gdk_screen_get_monitor_at_window (screen,
+ gtk_widget_get_window (GTK_WIDGET (window))),
+ &fs_rect);
+
+ if (show)
+ gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls),
+ fs_rect.x, fs_rect.y);
+ else
+ gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls),
+ fs_rect.x, fs_rect.y - height + 1);
+ }
+
+}
+
+static gboolean
+on_fullscreen_controls_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event,
+ PlumaWindow *window)
+{
+ show_hide_fullscreen_toolbar (window, TRUE, 0);
+
+ return FALSE;
+}
+
+static gboolean
+on_fullscreen_controls_leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event,
+ PlumaWindow *window)
+{
+ GdkDisplay *display;
+ GdkScreen *screen;
+ gint w, h;
+ gint x, y;
+
+ display = gdk_display_get_default ();
+ screen = gtk_window_get_screen (GTK_WINDOW (window));
+
+ gtk_window_get_size (GTK_WINDOW (window->priv->fullscreen_controls), &w, &h);
+ gdk_display_get_pointer (display, &screen, &x, &y, NULL);
+
+ /* gtk seems to emit leave notify when clicking on tool items,
+ * work around it by checking the coordinates
+ */
+ if (y >= h)
+ {
+ show_hide_fullscreen_toolbar (window, FALSE, h);
+ }
+
+ return FALSE;
+}
+
+static void
+fullscreen_controls_build (PlumaWindow *window)
+{
+ PlumaWindowPrivate *priv = window->priv;
+ GtkWidget *toolbar;
+ GtkWidget *toolbar_recent_menu;
+ GtkAction *action;
+
+ if (priv->fullscreen_controls != NULL)
+ return;
+
+ priv->fullscreen_controls = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_window_set_transient_for (GTK_WINDOW (priv->fullscreen_controls),
+ &window->window);
+
+ /* popup toolbar */
+ toolbar = gtk_ui_manager_get_widget (priv->manager, "/FullscreenToolBar");
+ gtk_container_add (GTK_CONTAINER (priv->fullscreen_controls),
+ toolbar);
+
+ action = gtk_action_group_get_action (priv->always_sensitive_action_group,
+ "LeaveFullscreen");
+ g_object_set (action, "is-important", TRUE, NULL);
+
+ toolbar_recent_menu = setup_toolbar_open_button (window, toolbar);
+
+ gtk_container_foreach (GTK_CONTAINER (toolbar),
+ (GtkCallback)set_non_homogeneus,
+ NULL);
+
+ /* Set the toolbar style */
+ gtk_toolbar_set_style (GTK_TOOLBAR (toolbar),
+ GTK_TOOLBAR_BOTH_HORIZ);
+
+ g_signal_connect (priv->fullscreen_controls, "enter-notify-event",
+ G_CALLBACK (on_fullscreen_controls_enter_notify_event),
+ window);
+ g_signal_connect (priv->fullscreen_controls, "leave-notify-event",
+ G_CALLBACK (on_fullscreen_controls_leave_notify_event),
+ window);
+}
+
+static void
+can_search_again (PlumaDocument *doc,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ gboolean sensitive;
+ GtkAction *action;
+
+ if (doc != pluma_window_get_active_document (window))
+ return;
+
+ sensitive = pluma_document_get_can_search_again (doc);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchFindNext");
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchFindPrevious");
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "SearchClearHighlight");
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+static void
+can_undo (PlumaDocument *doc,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ GtkAction *action;
+ gboolean sensitive;
+
+ sensitive = gtk_source_buffer_can_undo (GTK_SOURCE_BUFFER (doc));
+
+ if (doc != pluma_window_get_active_document (window))
+ return;
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditUndo");
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+static void
+can_redo (PlumaDocument *doc,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ GtkAction *action;
+ gboolean sensitive;
+
+ sensitive = gtk_source_buffer_can_redo (GTK_SOURCE_BUFFER (doc));
+
+ if (doc != pluma_window_get_active_document (window))
+ return;
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditRedo");
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+static void
+selection_changed (PlumaDocument *doc,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ PlumaTab *tab;
+ PlumaView *view;
+ GtkAction *action;
+ PlumaTabState state;
+ gboolean state_normal;
+ gboolean editable;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ if (doc != pluma_window_get_active_document (window))
+ return;
+
+ tab = pluma_tab_get_from_document (doc);
+ state = pluma_tab_get_state (tab);
+ state_normal = (state == PLUMA_TAB_STATE_NORMAL);
+
+ view = pluma_tab_get_view (tab);
+ editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (view));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditCut");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ editable &&
+ gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc)));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditCopy");
+ gtk_action_set_sensitive (action,
+ (state_normal ||
+ state == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) &&
+ gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc)));
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "EditDelete");
+ gtk_action_set_sensitive (action,
+ state_normal &&
+ editable &&
+ gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (doc)));
+
+ pluma_plugins_engine_update_plugins_ui (pluma_plugins_engine_get_default (),
+ window);
+}
+
+static void
+sync_languages_menu (PlumaDocument *doc,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ update_languages_menu (window);
+ pluma_plugins_engine_update_plugins_ui (pluma_plugins_engine_get_default (),
+ window);
+}
+
+static void
+readonly_changed (PlumaDocument *doc,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ set_sensitivity_according_to_tab (window, window->priv->active_tab);
+
+ sync_name (window->priv->active_tab, NULL, window);
+
+ pluma_plugins_engine_update_plugins_ui (pluma_plugins_engine_get_default (),
+ window);
+}
+
+static void
+editable_changed (PlumaView *view,
+ GParamSpec *arg1,
+ PlumaWindow *window)
+{
+ pluma_plugins_engine_update_plugins_ui (pluma_plugins_engine_get_default (),
+ window);
+}
+
+static void
+update_sensitivity_according_to_open_tabs (PlumaWindow *window)
+{
+ GtkAction *action;
+
+ /* Set sensitivity */
+ gtk_action_group_set_sensitive (window->priv->action_group,
+ window->priv->num_tabs != 0);
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "DocumentsMoveToNewWindow");
+ gtk_action_set_sensitive (action,
+ window->priv->num_tabs > 1);
+
+ /* Do not set close action insensitive on OS X */
+#ifndef OS_OSX
+ gtk_action_group_set_sensitive (window->priv->close_action_group,
+ window->priv->num_tabs != 0);
+#endif
+}
+
+static void
+notebook_tab_added (PlumaNotebook *notebook,
+ PlumaTab *tab,
+ PlumaWindow *window)
+{
+ PlumaView *view;
+ PlumaDocument *doc;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ g_return_if_fail ((window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION) == 0);
+
+ ++window->priv->num_tabs;
+
+ update_sensitivity_according_to_open_tabs (window);
+
+ view = pluma_tab_get_view (tab);
+ doc = pluma_tab_get_document (tab);
+
+ /* IMPORTANT: remember to disconnect the signal in notebook_tab_removed
+ * if a new signal is connected here */
+
+ g_signal_connect (tab,
+ "notify::name",
+ G_CALLBACK (sync_name),
+ window);
+ g_signal_connect (tab,
+ "notify::state",
+ G_CALLBACK (sync_state),
+ window);
+
+ g_signal_connect (doc,
+ "cursor-moved",
+ G_CALLBACK (update_cursor_position_statusbar),
+ window);
+ g_signal_connect (doc,
+ "notify::can-search-again",
+ G_CALLBACK (can_search_again),
+ window);
+ g_signal_connect (doc,
+ "notify::can-undo",
+ G_CALLBACK (can_undo),
+ window);
+ g_signal_connect (doc,
+ "notify::can-redo",
+ G_CALLBACK (can_redo),
+ window);
+ g_signal_connect (doc,
+ "notify::has-selection",
+ G_CALLBACK (selection_changed),
+ window);
+ g_signal_connect (doc,
+ "notify::language",
+ G_CALLBACK (sync_languages_menu),
+ window);
+ g_signal_connect (doc,
+ "notify::read-only",
+ G_CALLBACK (readonly_changed),
+ window);
+ g_signal_connect (view,
+ "toggle_overwrite",
+ G_CALLBACK (update_overwrite_mode_statusbar),
+ window);
+ g_signal_connect (view,
+ "notify::editable",
+ G_CALLBACK (editable_changed),
+ window);
+
+ update_documents_list_menu (window);
+
+ g_signal_connect (view,
+ "drop_uris",
+ G_CALLBACK (drop_uris_cb),
+ NULL);
+
+ update_window_state (window);
+
+ g_signal_emit (G_OBJECT (window), signals[TAB_ADDED], 0, tab);
+}
+
+static void
+notebook_tab_removed (PlumaNotebook *notebook,
+ PlumaTab *tab,
+ PlumaWindow *window)
+{
+ PlumaView *view;
+ PlumaDocument *doc;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ g_return_if_fail ((window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION) == 0);
+
+ --window->priv->num_tabs;
+
+ view = pluma_tab_get_view (tab);
+ doc = pluma_tab_get_document (tab);
+
+ g_signal_handlers_disconnect_by_func (tab,
+ G_CALLBACK (sync_name),
+ window);
+ g_signal_handlers_disconnect_by_func (tab,
+ G_CALLBACK (sync_state),
+ window);
+ g_signal_handlers_disconnect_by_func (doc,
+ G_CALLBACK (update_cursor_position_statusbar),
+ window);
+ g_signal_handlers_disconnect_by_func (doc,
+ G_CALLBACK (can_search_again),
+ window);
+ g_signal_handlers_disconnect_by_func (doc,
+ G_CALLBACK (can_undo),
+ window);
+ g_signal_handlers_disconnect_by_func (doc,
+ G_CALLBACK (can_redo),
+ window);
+ g_signal_handlers_disconnect_by_func (doc,
+ G_CALLBACK (selection_changed),
+ window);
+ g_signal_handlers_disconnect_by_func (doc,
+ G_CALLBACK (sync_languages_menu),
+ window);
+ g_signal_handlers_disconnect_by_func (doc,
+ G_CALLBACK (readonly_changed),
+ window);
+ g_signal_handlers_disconnect_by_func (view,
+ G_CALLBACK (update_overwrite_mode_statusbar),
+ window);
+ g_signal_handlers_disconnect_by_func (view,
+ G_CALLBACK (editable_changed),
+ window);
+ g_signal_handlers_disconnect_by_func (view,
+ G_CALLBACK (drop_uris_cb),
+ NULL);
+
+ if (window->priv->tab_width_id && tab == pluma_window_get_active_tab (window))
+ {
+ g_signal_handler_disconnect (view, window->priv->tab_width_id);
+ window->priv->tab_width_id = 0;
+ }
+
+ if (window->priv->spaces_instead_of_tabs_id && tab == pluma_window_get_active_tab (window))
+ {
+ g_signal_handler_disconnect (view, window->priv->spaces_instead_of_tabs_id);
+ window->priv->spaces_instead_of_tabs_id = 0;
+ }
+
+ if (window->priv->language_changed_id && tab == pluma_window_get_active_tab (window))
+ {
+ g_signal_handler_disconnect (doc, window->priv->language_changed_id);
+ window->priv->language_changed_id = 0;
+ }
+
+ g_return_if_fail (window->priv->num_tabs >= 0);
+ if (window->priv->num_tabs == 0)
+ {
+ window->priv->active_tab = NULL;
+
+ set_title (window);
+
+ /* Remove line and col info */
+ pluma_statusbar_set_cursor_position (
+ PLUMA_STATUSBAR (window->priv->statusbar),
+ -1,
+ -1);
+
+ pluma_statusbar_clear_overwrite (
+ PLUMA_STATUSBAR (window->priv->statusbar));
+
+ /* hide the combos */
+ gtk_widget_hide (window->priv->tab_width_combo);
+ gtk_widget_hide (window->priv->language_combo);
+ }
+
+ if (!window->priv->removing_tabs)
+ {
+ update_documents_list_menu (window);
+ update_next_prev_doc_sensitivity_per_window (window);
+ }
+ else
+ {
+ if (window->priv->num_tabs == 0)
+ {
+ update_documents_list_menu (window);
+ update_next_prev_doc_sensitivity_per_window (window);
+ }
+ }
+
+ update_sensitivity_according_to_open_tabs (window);
+
+ if (window->priv->num_tabs == 0)
+ {
+ pluma_plugins_engine_update_plugins_ui (pluma_plugins_engine_get_default (),
+ window);
+ }
+
+ update_window_state (window);
+
+ g_signal_emit (G_OBJECT (window), signals[TAB_REMOVED], 0, tab);
+}
+
+static void
+notebook_tabs_reordered (PlumaNotebook *notebook,
+ PlumaWindow *window)
+{
+ update_documents_list_menu (window);
+ update_next_prev_doc_sensitivity_per_window (window);
+
+ g_signal_emit (G_OBJECT (window), signals[TABS_REORDERED], 0);
+}
+
+static void
+notebook_tab_detached (PlumaNotebook *notebook,
+ PlumaTab *tab,
+ PlumaWindow *window)
+{
+ PlumaWindow *new_window;
+
+ new_window = clone_window (window);
+
+ pluma_notebook_move_tab (notebook,
+ PLUMA_NOTEBOOK (_pluma_window_get_notebook (new_window)),
+ tab, 0);
+
+ gtk_window_set_position (GTK_WINDOW (new_window),
+ GTK_WIN_POS_MOUSE);
+
+ gtk_widget_show (GTK_WIDGET (new_window));
+}
+
+static void
+notebook_tab_close_request (PlumaNotebook *notebook,
+ PlumaTab *tab,
+ GtkWindow *window)
+{
+ /* Note: we are destroying the tab before the default handler
+ * seems to be ok, but we need to keep an eye on this. */
+ _pluma_cmd_file_close_tab (tab, PLUMA_WINDOW (window));
+}
+
+static gboolean
+show_notebook_popup_menu (GtkNotebook *notebook,
+ PlumaWindow *window,
+ GdkEventButton *event)
+{
+ GtkWidget *menu;
+// GtkAction *action;
+
+ menu = gtk_ui_manager_get_widget (window->priv->manager, "/NotebookPopup");
+ g_return_val_if_fail (menu != NULL, FALSE);
+
+// CHECK do we need this?
+#if 0
+ /* allow extensions to sync when showing the popup */
+ action = gtk_action_group_get_action (window->priv->action_group,
+ "NotebookPopupAction");
+ g_return_val_if_fail (action != NULL, FALSE);
+ gtk_action_activate (action);
+#endif
+ if (event != NULL)
+ {
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+ NULL, NULL,
+ event->button, event->time);
+ }
+ else
+ {
+ GtkWidget *tab;
+ GtkWidget *tab_label;
+
+ tab = GTK_WIDGET (pluma_window_get_active_tab (window));
+ g_return_val_if_fail (tab != NULL, FALSE);
+
+ tab_label = gtk_notebook_get_tab_label (notebook, tab);
+
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+ pluma_utils_menu_position_under_widget, tab_label,
+ 0, gtk_get_current_event_time ());
+
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+notebook_button_press_event (GtkNotebook *notebook,
+ GdkEventButton *event,
+ PlumaWindow *window)
+{
+ if (GDK_BUTTON_PRESS == event->type && 3 == event->button)
+ {
+ return show_notebook_popup_menu (notebook, window, event);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+notebook_popup_menu (GtkNotebook *notebook,
+ PlumaWindow *window)
+{
+ /* Only respond if the notebook is the actual focus */
+ if (PLUMA_IS_NOTEBOOK (gtk_window_get_focus (GTK_WINDOW (window))))
+ {
+ return show_notebook_popup_menu (notebook, window, NULL);
+ }
+
+ return FALSE;
+}
+
+static void
+side_panel_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation,
+ PlumaWindow *window)
+{
+ window->priv->side_panel_size = allocation->width;
+}
+
+static void
+bottom_panel_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation,
+ PlumaWindow *window)
+{
+ window->priv->bottom_panel_size = allocation->height;
+}
+
+static void
+hpaned_restore_position (GtkWidget *widget,
+ PlumaWindow *window)
+{
+ gint pos;
+
+ pluma_debug_message (DEBUG_WINDOW,
+ "Restoring hpaned position: side panel size %d",
+ window->priv->side_panel_size);
+
+ pos = MAX (100, window->priv->side_panel_size);
+ gtk_paned_set_position (GTK_PANED (window->priv->hpaned), pos);
+
+ /* start monitoring the size */
+ g_signal_connect (window->priv->side_panel,
+ "size-allocate",
+ G_CALLBACK (side_panel_size_allocate),
+ window);
+
+ /* run this only once */
+ g_signal_handlers_disconnect_by_func (widget, hpaned_restore_position, window);
+}
+
+static void
+vpaned_restore_position (GtkWidget *widget,
+ PlumaWindow *window)
+{
+ gint pos;
+
+ pluma_debug_message (DEBUG_WINDOW,
+ "Restoring vpaned position: bottom panel size %d",
+ window->priv->bottom_panel_size);
+
+ pos = widget->allocation.height -
+ MAX (50, window->priv->bottom_panel_size);
+ gtk_paned_set_position (GTK_PANED (window->priv->vpaned), pos);
+
+ /* start monitoring the size */
+ g_signal_connect (window->priv->bottom_panel,
+ "size-allocate",
+ G_CALLBACK (bottom_panel_size_allocate),
+ window);
+
+ /* run this only once */
+ g_signal_handlers_disconnect_by_func (widget, vpaned_restore_position, window);
+}
+
+static void
+side_panel_visibility_changed (GtkWidget *side_panel,
+ PlumaWindow *window)
+{
+ gboolean visible;
+ GtkAction *action;
+
+ visible = GTK_WIDGET_VISIBLE (side_panel);
+
+ if (pluma_prefs_manager_side_pane_visible_can_set ())
+ pluma_prefs_manager_set_side_pane_visible (visible);
+
+ action = gtk_action_group_get_action (window->priv->panes_action_group,
+ "ViewSidePane");
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) != visible)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
+
+ /* focus the document */
+ if (!visible && window->priv->active_tab != NULL)
+ gtk_widget_grab_focus (GTK_WIDGET (
+ pluma_tab_get_view (PLUMA_TAB (window->priv->active_tab))));
+}
+
+static void
+create_side_panel (PlumaWindow *window)
+{
+ GtkWidget *documents_panel;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ window->priv->side_panel = pluma_panel_new (GTK_ORIENTATION_VERTICAL);
+
+ gtk_paned_pack1 (GTK_PANED (window->priv->hpaned),
+ window->priv->side_panel,
+ FALSE,
+ FALSE);
+
+ g_signal_connect_after (window->priv->side_panel,
+ "show",
+ G_CALLBACK (side_panel_visibility_changed),
+ window);
+ g_signal_connect_after (window->priv->side_panel,
+ "hide",
+ G_CALLBACK (side_panel_visibility_changed),
+ window);
+
+ documents_panel = pluma_documents_panel_new (window);
+ pluma_panel_add_item_with_stock_icon (PLUMA_PANEL (window->priv->side_panel),
+ documents_panel,
+ _("Documents"),
+ GTK_STOCK_FILE);
+}
+
+static void
+bottom_panel_visibility_changed (PlumaPanel *bottom_panel,
+ PlumaWindow *window)
+{
+ gboolean visible;
+ GtkAction *action;
+
+ visible = GTK_WIDGET_VISIBLE (bottom_panel);
+
+ if (pluma_prefs_manager_bottom_panel_visible_can_set ())
+ pluma_prefs_manager_set_bottom_panel_visible (visible);
+
+ action = gtk_action_group_get_action (window->priv->panes_action_group,
+ "ViewBottomPane");
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) != visible)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
+
+ /* focus the document */
+ if (!visible && window->priv->active_tab != NULL)
+ gtk_widget_grab_focus (GTK_WIDGET (
+ pluma_tab_get_view (PLUMA_TAB (window->priv->active_tab))));
+}
+
+static void
+bottom_panel_item_removed (PlumaPanel *panel,
+ GtkWidget *item,
+ PlumaWindow *window)
+{
+ if (pluma_panel_get_n_items (panel) == 0)
+ {
+ GtkAction *action;
+
+ gtk_widget_hide (GTK_WIDGET (panel));
+
+ action = gtk_action_group_get_action (window->priv->panes_action_group,
+ "ViewBottomPane");
+ gtk_action_set_sensitive (action, FALSE);
+ }
+}
+
+static void
+bottom_panel_item_added (PlumaPanel *panel,
+ GtkWidget *item,
+ PlumaWindow *window)
+{
+ /* if it's the first item added, set the menu item
+ * sensitive and if needed show the panel */
+ if (pluma_panel_get_n_items (panel) == 1)
+ {
+ GtkAction *action;
+ gboolean show;
+
+ action = gtk_action_group_get_action (window->priv->panes_action_group,
+ "ViewBottomPane");
+ gtk_action_set_sensitive (action, TRUE);
+
+ show = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ if (show)
+ gtk_widget_show (GTK_WIDGET (panel));
+ }
+}
+
+static void
+create_bottom_panel (PlumaWindow *window)
+{
+ pluma_debug (DEBUG_WINDOW);
+
+ window->priv->bottom_panel = pluma_panel_new (GTK_ORIENTATION_HORIZONTAL);
+
+ gtk_paned_pack2 (GTK_PANED (window->priv->vpaned),
+ window->priv->bottom_panel,
+ FALSE,
+ FALSE);
+
+ g_signal_connect_after (window->priv->bottom_panel,
+ "show",
+ G_CALLBACK (bottom_panel_visibility_changed),
+ window);
+ g_signal_connect_after (window->priv->bottom_panel,
+ "hide",
+ G_CALLBACK (bottom_panel_visibility_changed),
+ window);
+}
+
+static void
+init_panels_visibility (PlumaWindow *window)
+{
+ gint active_page;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ /* side pane */
+ active_page = pluma_prefs_manager_get_side_panel_active_page ();
+ _pluma_panel_set_active_item_by_id (PLUMA_PANEL (window->priv->side_panel),
+ active_page);
+
+ if (pluma_prefs_manager_get_side_pane_visible ())
+ {
+ gtk_widget_show (window->priv->side_panel);
+ }
+
+ /* bottom pane, it can be empty */
+ if (pluma_panel_get_n_items (PLUMA_PANEL (window->priv->bottom_panel)) > 0)
+ {
+ active_page = pluma_prefs_manager_get_bottom_panel_active_page ();
+ _pluma_panel_set_active_item_by_id (PLUMA_PANEL (window->priv->bottom_panel),
+ active_page);
+
+ if (pluma_prefs_manager_get_bottom_panel_visible ())
+ {
+ gtk_widget_show (window->priv->bottom_panel);
+ }
+ }
+ else
+ {
+ GtkAction *action;
+ action = gtk_action_group_get_action (window->priv->panes_action_group,
+ "ViewBottomPane");
+ gtk_action_set_sensitive (action, FALSE);
+ }
+
+ /* start track sensitivity after the initial state is set */
+ window->priv->bottom_panel_item_removed_handler_id =
+ g_signal_connect (window->priv->bottom_panel,
+ "item_removed",
+ G_CALLBACK (bottom_panel_item_removed),
+ window);
+
+ g_signal_connect (window->priv->bottom_panel,
+ "item_added",
+ G_CALLBACK (bottom_panel_item_added),
+ window);
+}
+
+static void
+clipboard_owner_change (GtkClipboard *clipboard,
+ GdkEventOwnerChange *event,
+ PlumaWindow *window)
+{
+ set_paste_sensitivity_according_to_clipboard (window,
+ clipboard);
+}
+
+static void
+window_realized (GtkWidget *window,
+ gpointer *data)
+{
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_widget_get_clipboard (window,
+ GDK_SELECTION_CLIPBOARD);
+
+ g_signal_connect (clipboard,
+ "owner_change",
+ G_CALLBACK (clipboard_owner_change),
+ window);
+}
+
+static void
+window_unrealized (GtkWidget *window,
+ gpointer *data)
+{
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_widget_get_clipboard (window,
+ GDK_SELECTION_CLIPBOARD);
+
+ g_signal_handlers_disconnect_by_func (clipboard,
+ G_CALLBACK (clipboard_owner_change),
+ window);
+}
+
+static void
+check_window_is_active (PlumaWindow *window,
+ GParamSpec *property,
+ gpointer useless)
+{
+ if (window->priv->window_state & GDK_WINDOW_STATE_FULLSCREEN)
+ {
+ if (gtk_window_is_active (GTK_WINDOW (window)))
+ {
+ gtk_widget_show (window->priv->fullscreen_controls);
+ }
+ else
+ {
+ gtk_widget_hide (window->priv->fullscreen_controls);
+ }
+ }
+}
+
+#ifdef OS_OSX
+static void
+setup_mac_menu (PlumaWindow *window)
+{
+ GtkAction *action;
+
+ gtk_widget_hide (window->priv->menubar);
+ action = gtk_ui_manager_get_action (window->priv->manager, "/ui/MenuBar/HelpMenu/HelpAboutMenu");
+
+ gtk_action_set_label (action, _("About pluma"));
+
+ ige_mac_menu_set_menu_bar (GTK_MENU_SHELL (window->priv->menubar));
+ ige_mac_menu_set_quit_menu_item (ui_manager_menu_item (window->priv->manager, "/ui/MenuBar/FileMenu/FileQuitMenu"));
+
+ ige_mac_menu_set_preferences_menu_item (ui_manager_menu_item (window->priv->manager, "/ui/MenuBar/EditMenu/EditPreferencesMenu"));
+
+ add_mac_root_menu (window);
+ ige_mac_menu_connect_window_key_handler (GTK_WINDOW (window));
+}
+#endif
+
+static void
+connect_notebook_signals (PlumaWindow *window,
+ GtkWidget *notebook)
+{
+ g_signal_connect (notebook,
+ "switch-page",
+ G_CALLBACK (notebook_switch_page),
+ window);
+ g_signal_connect (notebook,
+ "tab-added",
+ G_CALLBACK (notebook_tab_added),
+ window);
+ g_signal_connect (notebook,
+ "tab-removed",
+ G_CALLBACK (notebook_tab_removed),
+ window);
+ g_signal_connect (notebook,
+ "tabs-reordered",
+ G_CALLBACK (notebook_tabs_reordered),
+ window);
+ g_signal_connect (notebook,
+ "tab-detached",
+ G_CALLBACK (notebook_tab_detached),
+ window);
+ g_signal_connect (notebook,
+ "tab-close-request",
+ G_CALLBACK (notebook_tab_close_request),
+ window);
+ g_signal_connect (notebook,
+ "button-press-event",
+ G_CALLBACK (notebook_button_press_event),
+ window);
+ g_signal_connect (notebook,
+ "popup-menu",
+ G_CALLBACK (notebook_popup_menu),
+ window);
+}
+
+static void
+add_notebook (PlumaWindow *window,
+ GtkWidget *notebook)
+{
+ gtk_paned_pack1 (GTK_PANED (window->priv->vpaned),
+ notebook,
+ TRUE,
+ TRUE);
+
+ gtk_widget_show (notebook);
+
+ connect_notebook_signals (window, notebook);
+}
+
+static void
+pluma_window_init (PlumaWindow *window)
+{
+ GtkWidget *main_box;
+ GtkTargetList *tl;
+
+ pluma_debug (DEBUG_WINDOW);
+
+ window->priv = PLUMA_WINDOW_GET_PRIVATE (window);
+ window->priv->active_tab = NULL;
+ window->priv->num_tabs = 0;
+ window->priv->removing_tabs = FALSE;
+ window->priv->state = PLUMA_WINDOW_STATE_NORMAL;
+ window->priv->dispose_has_run = FALSE;
+ window->priv->fullscreen_controls = NULL;
+ window->priv->fullscreen_animation_timeout_id = 0;
+
+ window->priv->message_bus = pluma_message_bus_new ();
+
+ window->priv->window_group = gtk_window_group_new ();
+ gtk_window_group_add_window (window->priv->window_group, GTK_WINDOW (window));
+
+ main_box = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (window), main_box);
+ gtk_widget_show (main_box);
+
+ /* Add menu bar and toolbar bar */
+ create_menu_bar_and_toolbar (window, main_box);
+
+ /* Add status bar */
+ create_statusbar (window, main_box);
+
+ /* Add the main area */
+ pluma_debug_message (DEBUG_WINDOW, "Add main area");
+ window->priv->hpaned = gtk_hpaned_new ();
+ gtk_box_pack_start (GTK_BOX (main_box),
+ window->priv->hpaned,
+ TRUE,
+ TRUE,
+ 0);
+
+ window->priv->vpaned = gtk_vpaned_new ();
+ gtk_paned_pack2 (GTK_PANED (window->priv->hpaned),
+ window->priv->vpaned,
+ TRUE,
+ FALSE);
+
+ pluma_debug_message (DEBUG_WINDOW, "Create pluma notebook");
+ window->priv->notebook = pluma_notebook_new ();
+ add_notebook (window, window->priv->notebook);
+
+ /* side and bottom panels */
+ create_side_panel (window);
+ create_bottom_panel (window);
+
+ /* panes' state must be restored after panels have been mapped,
+ * since the bottom pane position depends on the size of the vpaned. */
+ window->priv->side_panel_size = pluma_prefs_manager_get_side_panel_size ();
+ window->priv->bottom_panel_size = pluma_prefs_manager_get_bottom_panel_size ();
+
+ g_signal_connect_after (window->priv->hpaned,
+ "map",
+ G_CALLBACK (hpaned_restore_position),
+ window);
+ g_signal_connect_after (window->priv->vpaned,
+ "map",
+ G_CALLBACK (vpaned_restore_position),
+ window);
+
+ gtk_widget_show (window->priv->hpaned);
+ gtk_widget_show (window->priv->vpaned);
+
+ /* Drag and drop support, set targets to NULL because we add the
+ default uri_targets below */
+ gtk_drag_dest_set (GTK_WIDGET (window),
+ GTK_DEST_DEFAULT_MOTION |
+ GTK_DEST_DEFAULT_HIGHLIGHT |
+ GTK_DEST_DEFAULT_DROP,
+ NULL,
+ 0,
+ GDK_ACTION_COPY);
+
+ /* Add uri targets */
+ tl = gtk_drag_dest_get_target_list (GTK_WIDGET (window));
+
+ if (tl == NULL)
+ {
+ tl = gtk_target_list_new (NULL, 0);
+ gtk_drag_dest_set_target_list (GTK_WIDGET (window), tl);
+ gtk_target_list_unref (tl);
+ }
+
+ gtk_target_list_add_uri_targets (tl, TARGET_URI_LIST);
+
+ /* connect instead of override, so that we can
+ * share the cb code with the view */
+ g_signal_connect (window,
+ "drag_data_received",
+ G_CALLBACK (drag_data_received_cb),
+ NULL);
+
+ /* we can get the clipboard only after the widget
+ * is realized */
+ g_signal_connect (window,
+ "realize",
+ G_CALLBACK (window_realized),
+ NULL);
+ g_signal_connect (window,
+ "unrealize",
+ G_CALLBACK (window_unrealized),
+ NULL);
+
+ /* Check if the window is active for fullscreen */
+ g_signal_connect (window,
+ "notify::is-active",
+ G_CALLBACK (check_window_is_active),
+ NULL);
+
+ pluma_debug_message (DEBUG_WINDOW, "Update plugins ui");
+
+ pluma_plugins_engine_activate_plugins (pluma_plugins_engine_get_default (),
+ window);
+
+ /* set visibility of panes.
+ * This needs to be done after plugins activatation */
+ init_panels_visibility (window);
+
+ update_sensitivity_according_to_open_tabs (window);
+
+#ifdef OS_OSX
+ setup_mac_menu (window);
+#endif
+
+ pluma_debug_message (DEBUG_WINDOW, "END");
+}
+
+/**
+ * pluma_window_get_active_view:
+ * @window: a #PlumaWindow
+ *
+ * Gets the active #PlumaView.
+ *
+ * Returns: the active #PlumaView
+ */
+PlumaView *
+pluma_window_get_active_view (PlumaWindow *window)
+{
+ PlumaView *view;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ if (window->priv->active_tab == NULL)
+ return NULL;
+
+ view = pluma_tab_get_view (PLUMA_TAB (window->priv->active_tab));
+
+ return view;
+}
+
+/**
+ * pluma_window_get_active_document:
+ * @window: a #PlumaWindow
+ *
+ * Gets the active #PlumaDocument.
+ *
+ * Returns: the active #PlumaDocument
+ */
+PlumaDocument *
+pluma_window_get_active_document (PlumaWindow *window)
+{
+ PlumaView *view;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ view = pluma_window_get_active_view (window);
+ if (view == NULL)
+ return NULL;
+
+ return PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+}
+
+GtkWidget *
+_pluma_window_get_notebook (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return window->priv->notebook;
+}
+
+/**
+ * pluma_window_create_tab:
+ * @window: a #PlumaWindow
+ * @jump_to: %TRUE to set the new #PlumaTab as active
+ *
+ * Creates a new #PlumaTab and adds the new tab to the #PlumaNotebook.
+ * In case @jump_to is %TRUE the #PlumaNotebook switches to that new #PlumaTab.
+ *
+ * Returns: a new #PlumaTab
+ */
+PlumaTab *
+pluma_window_create_tab (PlumaWindow *window,
+ gboolean jump_to)
+{
+ PlumaTab *tab;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ tab = PLUMA_TAB (_pluma_tab_new ());
+ gtk_widget_show (GTK_WIDGET (tab));
+
+ pluma_notebook_add_tab (PLUMA_NOTEBOOK (window->priv->notebook),
+ tab,
+ -1,
+ jump_to);
+
+ if (!GTK_WIDGET_VISIBLE (window))
+ {
+ gtk_window_present (GTK_WINDOW (window));
+ }
+
+ return tab;
+}
+
+/**
+ * pluma_window_create_tab_from_uri:
+ * @window: a #PlumaWindow
+ * @uri: the uri of the document
+ * @encoding: a #PlumaEncoding
+ * @line_pos: the line position to visualize
+ * @create: %TRUE to create a new document in case @uri does exist
+ * @jump_to: %TRUE to set the new #PlumaTab as active
+ *
+ * Creates a new #PlumaTab loading the document specified by @uri.
+ * In case @jump_to is %TRUE the #PlumaNotebook swithes to that new #PlumaTab.
+ * Whether @create is %TRUE, creates a new empty document if location does
+ * not refer to an existing file
+ *
+ * Returns: a new #PlumaTab
+ */
+PlumaTab *
+pluma_window_create_tab_from_uri (PlumaWindow *window,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create,
+ gboolean jump_to)
+{
+ GtkWidget *tab;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ tab = _pluma_tab_new_from_uri (uri,
+ encoding,
+ line_pos,
+ create);
+ if (tab == NULL)
+ return NULL;
+
+ gtk_widget_show (tab);
+
+ pluma_notebook_add_tab (PLUMA_NOTEBOOK (window->priv->notebook),
+ PLUMA_TAB (tab),
+ -1,
+ jump_to);
+
+
+ if (!GTK_WIDGET_VISIBLE (window))
+ {
+ gtk_window_present (GTK_WINDOW (window));
+ }
+
+ return PLUMA_TAB (tab);
+}
+
+/**
+ * pluma_window_get_active_tab:
+ * @window: a PlumaWindow
+ *
+ * Gets the active #PlumaTab in the @window.
+ *
+ * Returns: the active #PlumaTab in the @window.
+ */
+PlumaTab *
+pluma_window_get_active_tab (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return (window->priv->active_tab == NULL) ?
+ NULL : PLUMA_TAB (window->priv->active_tab);
+}
+
+static void
+add_document (PlumaTab *tab, GList **res)
+{
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+
+ *res = g_list_prepend (*res, doc);
+}
+
+/**
+ * pluma_window_get_documents:
+ * @window: a #PlumaWindow
+ *
+ * Gets a newly allocated list with all the documents in the window.
+ * This list must be freed.
+ *
+ * Returns: a newly allocated list with all the documents in the window
+ */
+GList *
+pluma_window_get_documents (PlumaWindow *window)
+{
+ GList *res = NULL;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ gtk_container_foreach (GTK_CONTAINER (window->priv->notebook),
+ (GtkCallback)add_document,
+ &res);
+
+ res = g_list_reverse (res);
+
+ return res;
+}
+
+static void
+add_view (PlumaTab *tab, GList **res)
+{
+ PlumaView *view;
+
+ view = pluma_tab_get_view (tab);
+
+ *res = g_list_prepend (*res, view);
+}
+
+/**
+ * pluma_window_get_views:
+ * @window: a #PlumaWindow
+ *
+ * Gets a list with all the views in the window. This list must be freed.
+ *
+ * Returns: a newly allocated list with all the views in the window
+ */
+GList *
+pluma_window_get_views (PlumaWindow *window)
+{
+ GList *res = NULL;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ gtk_container_foreach (GTK_CONTAINER (window->priv->notebook),
+ (GtkCallback)add_view,
+ &res);
+
+ res = g_list_reverse (res);
+
+ return res;
+}
+
+/**
+ * pluma_window_close_tab:
+ * @window: a #PlumaWindow
+ * @tab: the #PlumaTab to close
+ *
+ * Closes the @tab.
+ */
+void
+pluma_window_close_tab (PlumaWindow *window,
+ PlumaTab *tab)
+{
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+ g_return_if_fail ((pluma_tab_get_state (tab) != PLUMA_TAB_STATE_SAVING) &&
+ (pluma_tab_get_state (tab) != PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW));
+
+ pluma_notebook_remove_tab (PLUMA_NOTEBOOK (window->priv->notebook),
+ tab);
+}
+
+/**
+ * pluma_window_close_all_tabs:
+ * @window: a #PlumaWindow
+ *
+ * Closes all opened tabs.
+ */
+void
+pluma_window_close_all_tabs (PlumaWindow *window)
+{
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+ g_return_if_fail (!(window->priv->state & PLUMA_WINDOW_STATE_SAVING) &&
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION));
+
+ window->priv->removing_tabs = TRUE;
+
+ pluma_notebook_remove_all_tabs (PLUMA_NOTEBOOK (window->priv->notebook));
+
+ window->priv->removing_tabs = FALSE;
+}
+
+/**
+ * pluma_window_close_tabs:
+ * @window: a #PlumaWindow
+ * @tabs: a list of #PlumaTab
+ *
+ * Closes all tabs specified by @tabs.
+ */
+void
+pluma_window_close_tabs (PlumaWindow *window,
+ const GList *tabs)
+{
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+ g_return_if_fail (!(window->priv->state & PLUMA_WINDOW_STATE_SAVING) &&
+ !(window->priv->state & PLUMA_WINDOW_STATE_SAVING_SESSION));
+
+ if (tabs == NULL)
+ return;
+
+ window->priv->removing_tabs = TRUE;
+
+ while (tabs != NULL)
+ {
+ if (tabs->next == NULL)
+ window->priv->removing_tabs = FALSE;
+
+ pluma_notebook_remove_tab (PLUMA_NOTEBOOK (window->priv->notebook),
+ PLUMA_TAB (tabs->data));
+
+ tabs = g_list_next (tabs);
+ }
+
+ g_return_if_fail (window->priv->removing_tabs == FALSE);
+}
+
+PlumaWindow *
+_pluma_window_move_tab_to_new_window (PlumaWindow *window,
+ PlumaTab *tab)
+{
+ PlumaWindow *new_window;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+ g_return_val_if_fail (PLUMA_IS_TAB (tab), NULL);
+ g_return_val_if_fail (gtk_notebook_get_n_pages (
+ GTK_NOTEBOOK (window->priv->notebook)) > 1,
+ NULL);
+
+ new_window = clone_window (window);
+
+ pluma_notebook_move_tab (PLUMA_NOTEBOOK (window->priv->notebook),
+ PLUMA_NOTEBOOK (new_window->priv->notebook),
+ tab,
+ -1);
+
+ gtk_widget_show (GTK_WIDGET (new_window));
+
+ return new_window;
+}
+
+/**
+ * pluma_window_set_active_tab:
+ * @window: a #PlumaWindow
+ * @tab: a #PlumaTab
+ *
+ * Switches to the tab that matches with @tab.
+ */
+void
+pluma_window_set_active_tab (PlumaWindow *window,
+ PlumaTab *tab)
+{
+ gint page_num;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+ g_return_if_fail (PLUMA_IS_TAB (tab));
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (window->priv->notebook),
+ GTK_WIDGET (tab));
+ g_return_if_fail (page_num != -1);
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (window->priv->notebook),
+ page_num);
+}
+
+/**
+ * pluma_window_get_group:
+ * @window: a #PlumaWindow
+ *
+ * Gets the #GtkWindowGroup in which @window resides.
+ *
+ * Returns: the #GtkWindowGroup
+ */
+GtkWindowGroup *
+pluma_window_get_group (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return window->priv->window_group;
+}
+
+gboolean
+_pluma_window_is_removing_tabs (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), FALSE);
+
+ return window->priv->removing_tabs;
+}
+
+/**
+ * pluma_window_get_ui_manager:
+ * @window: a #PlumaWindow
+ *
+ * Gets the #GtkUIManager associated with the @window.
+ *
+ * Returns: the #GtkUIManager of the @window.
+ */
+GtkUIManager *
+pluma_window_get_ui_manager (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return window->priv->manager;
+}
+
+/**
+ * pluma_window_get_side_panel:
+ * @window: a #PlumaWindow
+ *
+ * Gets the side #PlumaPanel of the @window.
+ *
+ * Returns: the side #PlumaPanel.
+ */
+PlumaPanel *
+pluma_window_get_side_panel (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return PLUMA_PANEL (window->priv->side_panel);
+}
+
+/**
+ * pluma_window_get_bottom_panel:
+ * @window: a #PlumaWindow
+ *
+ * Gets the bottom #PlumaPanel of the @window.
+ *
+ * Returns: the bottom #PlumaPanel.
+ */
+PlumaPanel *
+pluma_window_get_bottom_panel (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return PLUMA_PANEL (window->priv->bottom_panel);
+}
+
+/**
+ * pluma_window_get_statusbar:
+ * @window: a #PlumaWindow
+ *
+ * Gets the #PlumaStatusbar of the @window.
+ *
+ * Returns: the #PlumaStatusbar of the @window.
+ */
+GtkWidget *
+pluma_window_get_statusbar (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), 0);
+
+ return window->priv->statusbar;
+}
+
+/**
+ * pluma_window_get_state:
+ * @window: a #PlumaWindow
+ *
+ * Retrieves the state of the @window.
+ *
+ * Returns: the current #PlumaWindowState of the @window.
+ */
+PlumaWindowState
+pluma_window_get_state (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), PLUMA_WINDOW_STATE_NORMAL);
+
+ return window->priv->state;
+}
+
+GFile *
+_pluma_window_get_default_location (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return window->priv->default_location != NULL ?
+ g_object_ref (window->priv->default_location) : NULL;
+}
+
+void
+_pluma_window_set_default_location (PlumaWindow *window,
+ GFile *location)
+{
+ GFile *dir;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+ g_return_if_fail (G_IS_FILE (location));
+
+ dir = g_file_get_parent (location);
+ g_return_if_fail (dir != NULL);
+
+ if (window->priv->default_location != NULL)
+ g_object_unref (window->priv->default_location);
+
+ window->priv->default_location = dir;
+}
+
+/**
+ * pluma_window_get_unsaved_documents:
+ * @window: a #PlumaWindow
+ *
+ * Gets the list of documents that need to be saved before closing the window.
+ *
+ * Returns: a list of #PlumaDocument that need to be saved before closing the window
+ */
+GList *
+pluma_window_get_unsaved_documents (PlumaWindow *window)
+{
+ GList *unsaved_docs = NULL;
+ GList *tabs;
+ GList *l;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ tabs = gtk_container_get_children (GTK_CONTAINER (window->priv->notebook));
+
+ l = tabs;
+ while (l != NULL)
+ {
+ PlumaTab *tab;
+
+ tab = PLUMA_TAB (l->data);
+
+ if (!_pluma_tab_can_close (tab))
+ {
+ PlumaDocument *doc;
+
+ doc = pluma_tab_get_document (tab);
+ unsaved_docs = g_list_prepend (unsaved_docs, doc);
+ }
+
+ l = g_list_next (l);
+ }
+
+ g_list_free (tabs);
+
+ return g_list_reverse (unsaved_docs);
+}
+
+void
+_pluma_window_set_saving_session_state (PlumaWindow *window,
+ gboolean saving_session)
+{
+ PlumaWindowState old_state;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ old_state = window->priv->state;
+
+ if (saving_session)
+ window->priv->state |= PLUMA_WINDOW_STATE_SAVING_SESSION;
+ else
+ window->priv->state &= ~PLUMA_WINDOW_STATE_SAVING_SESSION;
+
+ if (old_state != window->priv->state)
+ {
+ set_sensitivity_according_to_window_state (window);
+
+ g_object_notify (G_OBJECT (window), "state");
+ }
+}
+
+static void
+hide_notebook_tabs_on_fullscreen (GtkNotebook *notebook,
+ GParamSpec *pspec,
+ PlumaWindow *window)
+{
+ gtk_notebook_set_show_tabs (notebook, FALSE);
+}
+
+void
+_pluma_window_fullscreen (PlumaWindow *window)
+{
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ if (_pluma_window_is_fullscreen (window))
+ return;
+
+ /* Go to fullscreen mode and hide bars */
+ gtk_window_fullscreen (&window->window);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (window->priv->notebook), FALSE);
+ g_signal_connect (window->priv->notebook, "notify::show-tabs",
+ G_CALLBACK (hide_notebook_tabs_on_fullscreen), window);
+
+ gtk_widget_hide (window->priv->menubar);
+
+ g_signal_handlers_block_by_func (window->priv->toolbar,
+ toolbar_visibility_changed,
+ window);
+ gtk_widget_hide (window->priv->toolbar);
+
+ g_signal_handlers_block_by_func (window->priv->statusbar,
+ statusbar_visibility_changed,
+ window);
+ gtk_widget_hide (window->priv->statusbar);
+
+ fullscreen_controls_build (window);
+ fullscreen_controls_show (window);
+}
+
+void
+_pluma_window_unfullscreen (PlumaWindow *window)
+{
+ gboolean visible;
+ GtkAction *action;
+
+ g_return_if_fail (PLUMA_IS_WINDOW (window));
+
+ if (!_pluma_window_is_fullscreen (window))
+ return;
+
+ /* Unfullscreen and show bars */
+ gtk_window_unfullscreen (&window->window);
+ g_signal_handlers_disconnect_by_func (window->priv->notebook,
+ hide_notebook_tabs_on_fullscreen,
+ window);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (window->priv->notebook), TRUE);
+ gtk_widget_show (window->priv->menubar);
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "ViewToolbar");
+ visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ if (visible)
+ gtk_widget_show (window->priv->toolbar);
+ g_signal_handlers_unblock_by_func (window->priv->toolbar,
+ toolbar_visibility_changed,
+ window);
+
+ action = gtk_action_group_get_action (window->priv->always_sensitive_action_group,
+ "ViewStatusbar");
+ visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ if (visible)
+ gtk_widget_show (window->priv->statusbar);
+ g_signal_handlers_unblock_by_func (window->priv->statusbar,
+ statusbar_visibility_changed,
+ window);
+
+ gtk_widget_hide (window->priv->fullscreen_controls);
+}
+
+gboolean
+_pluma_window_is_fullscreen (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), FALSE);
+
+ return window->priv->window_state & GDK_WINDOW_STATE_FULLSCREEN;
+}
+
+/**
+ * pluma_window_get_tab_from_location:
+ * @window: a #PlumaWindow
+ * @location: a #GFile
+ *
+ * Gets the #PlumaTab that matches with the given @location.
+ *
+ * Returns: the #PlumaTab that matches with the given @location.
+ */
+PlumaTab *
+pluma_window_get_tab_from_location (PlumaWindow *window,
+ GFile *location)
+{
+ GList *tabs;
+ GList *l;
+ PlumaTab *ret = NULL;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+ g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+ tabs = gtk_container_get_children (GTK_CONTAINER (window->priv->notebook));
+
+ for (l = tabs; l != NULL; l = g_list_next (l))
+ {
+ PlumaDocument *d;
+ PlumaTab *t;
+ GFile *f;
+
+ t = PLUMA_TAB (l->data);
+ d = pluma_tab_get_document (t);
+
+ f = pluma_document_get_location (d);
+
+ if ((f != NULL))
+ {
+ gboolean found = g_file_equal (location, f);
+
+ g_object_unref (f);
+
+ if (found)
+ {
+ ret = t;
+ break;
+ }
+ }
+ }
+
+ g_list_free (tabs);
+
+ return ret;
+}
+
+/**
+ * pluma_window_get_message_bus:
+ * @window: a #PlumaWindow
+ *
+ * Gets the #PlumaMessageBus associated with @window. The returned reference
+ * is owned by the window and should not be unreffed.
+ *
+ * Return value: the #PlumaMessageBus associated with @window
+ */
+PlumaMessageBus *
+pluma_window_get_message_bus (PlumaWindow *window)
+{
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+
+ return window->priv->message_bus;
+}
+
+/**
+ * pluma_window_get_tab_from_uri:
+ * @window: a #PlumaWindow
+ * @uri: the uri to get the #PlumaTab
+ *
+ * Gets the #PlumaTab that matches @uri.
+ *
+ * Returns: the #PlumaTab associated with @uri.
+ *
+ * Deprecated: 2.24: Use pluma_window_get_tab_from_location() instead.
+ */
+PlumaTab *
+pluma_window_get_tab_from_uri (PlumaWindow *window,
+ const gchar *uri)
+{
+ GFile *f;
+ PlumaTab *tab;
+
+ g_return_val_if_fail (PLUMA_IS_WINDOW (window), NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ f = g_file_new_for_uri (uri);
+ tab = pluma_window_get_tab_from_location (window, f);
+ g_object_unref (f);
+
+ return tab;
+}
diff --git a/pluma/pluma-window.h b/pluma/pluma-window.h
new file mode 100755
index 00000000..ba099ecf
--- /dev/null
+++ b/pluma/pluma-window.h
@@ -0,0 +1,195 @@
+/*
+ * pluma-window.h
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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
+ * MERCHANWINDOWILITY 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __PLUMA_WINDOW_H__
+#define __PLUMA_WINDOW_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include <pluma/pluma-tab.h>
+#include <pluma/pluma-panel.h>
+#include <pluma/pluma-message-bus.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ PLUMA_WINDOW_STATE_NORMAL = 0,
+ PLUMA_WINDOW_STATE_SAVING = 1 << 1,
+ PLUMA_WINDOW_STATE_PRINTING = 1 << 2,
+ PLUMA_WINDOW_STATE_LOADING = 1 << 3,
+ PLUMA_WINDOW_STATE_ERROR = 1 << 4,
+ PLUMA_WINDOW_STATE_SAVING_SESSION = 1 << 5
+} PlumaWindowState;
+
+/*
+ * Type checking and casting macros
+ */
+#define PLUMA_TYPE_WINDOW (pluma_window_get_type())
+#define PLUMA_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PLUMA_TYPE_WINDOW, PlumaWindow))
+#define PLUMA_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PLUMA_TYPE_WINDOW, PlumaWindowClass))
+#define PLUMA_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLUMA_TYPE_WINDOW))
+#define PLUMA_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLUMA_TYPE_WINDOW))
+#define PLUMA_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PLUMA_TYPE_WINDOW, PlumaWindowClass))
+
+/* Private structure type */
+typedef struct _PlumaWindowPrivate PlumaWindowPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _PlumaWindow PlumaWindow;
+
+struct _PlumaWindow
+{
+ GtkWindow window;
+
+ /*< private > */
+ PlumaWindowPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _PlumaWindowClass PlumaWindowClass;
+
+struct _PlumaWindowClass
+{
+ GtkWindowClass parent_class;
+
+ /* Signals */
+ void (* tab_added) (PlumaWindow *window,
+ PlumaTab *tab);
+ void (* tab_removed) (PlumaWindow *window,
+ PlumaTab *tab);
+ void (* tabs_reordered) (PlumaWindow *window);
+ void (* active_tab_changed) (PlumaWindow *window,
+ PlumaTab *tab);
+ void (* active_tab_state_changed)
+ (PlumaWindow *window);
+};
+
+/*
+ * Public methods
+ */
+GType pluma_window_get_type (void) G_GNUC_CONST;
+
+PlumaTab *pluma_window_create_tab (PlumaWindow *window,
+ gboolean jump_to);
+
+PlumaTab *pluma_window_create_tab_from_uri (PlumaWindow *window,
+ const gchar *uri,
+ const PlumaEncoding *encoding,
+ gint line_pos,
+ gboolean create,
+ gboolean jump_to);
+
+void pluma_window_close_tab (PlumaWindow *window,
+ PlumaTab *tab);
+
+void pluma_window_close_all_tabs (PlumaWindow *window);
+
+void pluma_window_close_tabs (PlumaWindow *window,
+ const GList *tabs);
+
+PlumaTab *pluma_window_get_active_tab (PlumaWindow *window);
+
+void pluma_window_set_active_tab (PlumaWindow *window,
+ PlumaTab *tab);
+
+/* Helper functions */
+PlumaView *pluma_window_get_active_view (PlumaWindow *window);
+PlumaDocument *pluma_window_get_active_document (PlumaWindow *window);
+
+/* Returns a newly allocated list with all the documents in the window */
+GList *pluma_window_get_documents (PlumaWindow *window);
+
+/* Returns a newly allocated list with all the documents that need to be
+ saved before closing the window */
+GList *pluma_window_get_unsaved_documents (PlumaWindow *window);
+
+/* Returns a newly allocated list with all the views in the window */
+GList *pluma_window_get_views (PlumaWindow *window);
+
+GtkWindowGroup *pluma_window_get_group (PlumaWindow *window);
+
+PlumaPanel *pluma_window_get_side_panel (PlumaWindow *window);
+
+PlumaPanel *pluma_window_get_bottom_panel (PlumaWindow *window);
+
+GtkWidget *pluma_window_get_statusbar (PlumaWindow *window);
+
+GtkUIManager *pluma_window_get_ui_manager (PlumaWindow *window);
+
+PlumaWindowState pluma_window_get_state (PlumaWindow *window);
+
+PlumaTab *pluma_window_get_tab_from_location (PlumaWindow *window,
+ GFile *location);
+
+PlumaTab *pluma_window_get_tab_from_uri (PlumaWindow *window,
+ const gchar *uri);
+
+/* Message bus */
+PlumaMessageBus *pluma_window_get_message_bus (PlumaWindow *window);
+
+/*
+ * Non exported functions
+ */
+GtkWidget *_pluma_window_get_notebook (PlumaWindow *window);
+
+PlumaWindow *_pluma_window_move_tab_to_new_window (PlumaWindow *window,
+ PlumaTab *tab);
+gboolean _pluma_window_is_removing_tabs (PlumaWindow *window);
+
+GFile *_pluma_window_get_default_location (PlumaWindow *window);
+
+void _pluma_window_set_default_location (PlumaWindow *window,
+ GFile *location);
+
+void _pluma_window_set_saving_session_state (PlumaWindow *window,
+ gboolean saving_session);
+
+void _pluma_window_fullscreen (PlumaWindow *window);
+
+void _pluma_window_unfullscreen (PlumaWindow *window);
+
+gboolean _pluma_window_is_fullscreen (PlumaWindow *window);
+
+/* these are in pluma-window because of screen safety */
+void _pluma_recent_add (PlumaWindow *window,
+ const gchar *uri,
+ const gchar *mime);
+void _pluma_recent_remove (PlumaWindow *window,
+ const gchar *uri);
+
+G_END_DECLS
+
+#endif /* __PLUMA_WINDOW_H__ */
diff --git a/pluma/pluma.c b/pluma/pluma.c
new file mode 100755
index 00000000..b3a0c232
--- /dev/null
+++ b/pluma/pluma.c
@@ -0,0 +1,766 @@
+/*
+ * pluma.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the pluma Team, 2005. See the AUTHORS file for a
+ * list of people on the pluma Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "pluma-app.h"
+#include "pluma-commands.h"
+#include "pluma-debug.h"
+#include "pluma-dirs.h"
+#include "pluma-encodings.h"
+#include "pluma-plugins-engine.h"
+#include "pluma-prefs-manager-app.h"
+#include "pluma-session.h"
+#include "pluma-utils.h"
+#include "pluma-window.h"
+
+#include "eggsmclient.h"
+#include "eggdesktopfile.h"
+
+#ifdef G_OS_WIN32
+#define SAVE_DATADIR DATADIR
+#undef DATADIR
+#include <io.h>
+#include <conio.h>
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#define DATADIR SAVE_DATADIR
+#undef SAVE_DATADIR
+#endif
+
+#ifdef OS_OSX
+#include <ige-mac-dock.h>
+#include <ige-mac-integration.h>
+#include "osx/pluma-osx.h"
+#endif
+
+#ifndef ENABLE_GVFS_METADATA
+#include "pluma-metadata-manager.h"
+#endif
+
+static guint32 startup_timestamp = 0;
+
+#ifndef G_OS_WIN32
+#include "bacon-message-connection.h"
+
+static BaconMessageConnection *connection;
+#endif
+
+/* command line */
+static gint line_position = 0;
+static gchar *encoding_charset = NULL;
+static gboolean new_window_option = FALSE;
+static gboolean new_document_option = FALSE;
+static gchar **remaining_args = NULL;
+static GSList *file_list = NULL;
+
+static void
+show_version_and_quit (void)
+{
+ g_print ("%s - Version %s\n", g_get_application_name (), VERSION);
+
+ exit (0);
+}
+
+static void
+list_encodings_and_quit (void)
+{
+ gint i = 0;
+ const PlumaEncoding *enc;
+
+ while ((enc = pluma_encoding_get_from_index (i)) != NULL)
+ {
+ g_print ("%s\n", pluma_encoding_get_charset (enc));
+
+ ++i;
+ }
+
+ exit (0);
+}
+
+static const GOptionEntry options [] =
+{
+ { "version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ show_version_and_quit, N_("Show the application's version"), NULL },
+
+ { "encoding", '\0', 0, G_OPTION_ARG_STRING, &encoding_charset,
+ N_("Set the character encoding to be used to open the files listed on the command line"), N_("ENCODING")},
+
+ { "list-encodings", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ list_encodings_and_quit, N_("Display list of possible values for the encoding option"), NULL},
+
+ { "new-window", '\0', 0, G_OPTION_ARG_NONE, &new_window_option,
+ N_("Create a new top-level window in an existing instance of pluma"), NULL },
+
+ { "new-document", '\0', 0, G_OPTION_ARG_NONE, &new_document_option,
+ N_("Create a new document in an existing instance of pluma"), NULL },
+
+ { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args,
+ NULL, N_("[FILE...]") }, /* collects file arguments */
+
+ {NULL}
+};
+
+static void
+free_command_line_data (void)
+{
+ g_slist_foreach (file_list, (GFunc) g_object_unref, NULL);
+ g_slist_free (file_list);
+ file_list = NULL;
+
+ g_strfreev (remaining_args);
+ remaining_args = NULL;
+
+ g_free (encoding_charset);
+ encoding_charset = NULL;
+
+ new_window_option = FALSE;
+ new_document_option = FALSE;
+ line_position = 0;
+}
+
+static void
+pluma_get_command_line_data (void)
+{
+ if (remaining_args)
+ {
+ gint i;
+
+ for (i = 0; remaining_args[i]; i++)
+ {
+ if (*remaining_args[i] == '+')
+ {
+ if (*(remaining_args[i] + 1) == '\0')
+ /* goto the last line of the document */
+ line_position = G_MAXINT;
+ else
+ line_position = atoi (remaining_args[i] + 1);
+ }
+ else
+ {
+ GFile *file;
+
+ file = g_file_new_for_commandline_arg (remaining_args[i]);
+ file_list = g_slist_prepend (file_list, file);
+ }
+ }
+
+ file_list = g_slist_reverse (file_list);
+ }
+
+ if (encoding_charset &&
+ (pluma_encoding_get_from_charset (encoding_charset) == NULL))
+ {
+ g_print (_("%s: invalid encoding.\n"),
+ encoding_charset);
+ }
+}
+
+static guint32
+get_startup_timestamp (void)
+{
+ const gchar *startup_id_env;
+ gchar *startup_id = NULL;
+ gchar *time_str;
+ gchar *end;
+ gulong retval = 0;
+
+ /* we don't unset the env, since startup-notification
+ * may still need it */
+ startup_id_env = g_getenv ("DESKTOP_STARTUP_ID");
+ if (startup_id_env == NULL)
+ goto out;
+
+ startup_id = g_strdup (startup_id_env);
+
+ time_str = g_strrstr (startup_id, "_TIME");
+ if (time_str == NULL)
+ goto out;
+
+ errno = 0;
+
+ /* Skip past the "_TIME" part */
+ time_str += 5;
+
+ retval = strtoul (time_str, &end, 0);
+ if (end == time_str || errno != 0)
+ retval = 0;
+
+ out:
+ g_free (startup_id);
+
+ return (retval > 0) ? retval : 0;
+}
+
+#ifndef G_OS_WIN32
+static GdkDisplay *
+display_open_if_needed (const gchar *name)
+{
+ GSList *displays;
+ GSList *l;
+ GdkDisplay *display = NULL;
+
+ displays = gdk_display_manager_list_displays (gdk_display_manager_get ());
+
+ for (l = displays; l != NULL; l = l->next)
+ {
+ if (strcmp (gdk_display_get_name ((GdkDisplay *) l->data), name) == 0)
+ {
+ display = l->data;
+ break;
+ }
+ }
+
+ g_slist_free (displays);
+
+ return display != NULL ? display : gdk_display_open (name);
+}
+
+/* serverside */
+static void
+on_message_received (const char *message,
+ gpointer data)
+{
+ const PlumaEncoding *encoding = NULL;
+ gchar **commands;
+ gchar **params;
+ gint workspace;
+ gint viewport_x;
+ gint viewport_y;
+ gchar *display_name;
+ gint screen_number;
+ gint i;
+ PlumaApp *app;
+ PlumaWindow *window;
+ GdkDisplay *display;
+ GdkScreen *screen;
+
+ g_return_if_fail (message != NULL);
+
+ pluma_debug_message (DEBUG_APP, "Received message:\n%s\n", message);
+
+ commands = g_strsplit (message, "\v", -1);
+
+ /* header */
+ params = g_strsplit (commands[0], "\t", 6);
+ startup_timestamp = atoi (params[0]);
+ display_name = params[1];
+ screen_number = atoi (params[2]);
+ workspace = atoi (params[3]);
+ viewport_x = atoi (params[4]);
+ viewport_y = atoi (params[5]);
+
+ display = display_open_if_needed (display_name);
+ if (display == NULL)
+ {
+ g_warning ("Could not open display %s\n", display_name);
+ g_strfreev (params);
+ goto out;
+ }
+
+ screen = gdk_display_get_screen (display, screen_number);
+
+ g_strfreev (params);
+
+ /* body */
+ for (i = 1; commands[i] != NULL; i++)
+ {
+ params = g_strsplit (commands[i], "\t", -1);
+
+ if (strcmp (params[0], "NEW-WINDOW") == 0)
+ {
+ new_window_option = TRUE;
+ }
+ else if (strcmp (params[0], "NEW-DOCUMENT") == 0)
+ {
+ new_document_option = TRUE;
+ }
+ else if (strcmp (params[0], "OPEN-URIS") == 0)
+ {
+ gint n_uris, j;
+ gchar **uris;
+
+ line_position = atoi (params[1]);
+
+ if (params[2] != '\0')
+ encoding = pluma_encoding_get_from_charset (params[2]);
+
+ n_uris = atoi (params[3]);
+ uris = g_strsplit (params[4], " ", n_uris);
+
+ for (j = 0; j < n_uris; j++)
+ {
+ GFile *file;
+
+ file = g_file_new_for_uri (uris[j]);
+ file_list = g_slist_prepend (file_list, file);
+ }
+
+ file_list = g_slist_reverse (file_list);
+
+ /* the list takes ownerhip of the strings,
+ * only free the array */
+ g_free (uris);
+ }
+ else
+ {
+ g_warning ("Unexpected bacon command");
+ }
+
+ g_strfreev (params);
+ }
+
+ /* execute the commands */
+
+ app = pluma_app_get_default ();
+
+ if (new_window_option)
+ {
+ window = pluma_app_create_window (app, screen);
+ }
+ else
+ {
+ /* get a window in the current workspace (if exists) and raise it */
+ window = _pluma_app_get_window_in_viewport (app,
+ screen,
+ workspace,
+ viewport_x,
+ viewport_y);
+ }
+
+ if (file_list != NULL)
+ {
+ _pluma_cmd_load_files_from_prompt (window,
+ file_list,
+ encoding,
+ line_position);
+
+ if (new_document_option)
+ pluma_window_create_tab (window, TRUE);
+ }
+ else
+ {
+ PlumaDocument *doc;
+ doc = pluma_window_get_active_document (window);
+
+ if (doc == NULL ||
+ !pluma_document_is_untouched (doc) ||
+ new_document_option)
+ pluma_window_create_tab (window, TRUE);
+ }
+
+ /* set the proper interaction time on the window.
+ * Fall back to roundtripping to the X server when we
+ * don't have the timestamp, e.g. when launched from
+ * terminal. We also need to make sure that the window
+ * has been realized otherwise it will not work. lame.
+ */
+ if (!GTK_WIDGET_REALIZED (window))
+ gtk_widget_realize (GTK_WIDGET (window));
+
+#ifdef GDK_WINDOWING_X11
+ if (startup_timestamp <= 0)
+ startup_timestamp = gdk_x11_get_server_time (gtk_widget_get_window (GTK_WIDGET (window)));
+
+ gdk_x11_window_set_user_time (gtk_widget_get_window (GTK_WIDGET (window)),
+ startup_timestamp);
+#endif
+
+ gtk_window_present (GTK_WINDOW (window));
+
+ out:
+ g_strfreev (commands);
+
+ free_command_line_data ();
+}
+
+/* clientside */
+static void
+send_bacon_message (void)
+{
+ GdkScreen *screen;
+ GdkDisplay *display;
+ const gchar *display_name;
+ gint screen_number;
+ gint ws;
+ gint viewport_x;
+ gint viewport_y;
+ GString *command;
+
+ /* the messages have the following format:
+ * <--- header ---> <---- body ----->
+ * timestamp \t display_name \t screen_number \t workspace \t viewport_x \t viewport_y \v OP1 \t arg \t arg \v OP2 \t arg \t arg|...
+ *
+ * when the arg is a list of uri, they are separated by a space.
+ * So the delimiters are \v for the commands, \t for the tokens in
+ * a command and ' ' for the uris: note that such delimiters cannot
+ * be part of an uri, this way parsing is easier.
+ */
+
+ pluma_debug (DEBUG_APP);
+
+ screen = gdk_screen_get_default ();
+ display = gdk_screen_get_display (screen);
+
+ display_name = gdk_display_get_name (display);
+ screen_number = gdk_screen_get_number (screen);
+
+ pluma_debug_message (DEBUG_APP, "Display: %s", display_name);
+ pluma_debug_message (DEBUG_APP, "Screen: %d", screen_number);
+
+ ws = pluma_utils_get_current_workspace (screen);
+ pluma_utils_get_current_viewport (screen, &viewport_x, &viewport_y);
+
+ command = g_string_new (NULL);
+
+ /* header */
+ g_string_append_printf (command,
+ "%" G_GUINT32_FORMAT "\t%s\t%d\t%d\t%d\t%d",
+ startup_timestamp,
+ display_name,
+ screen_number,
+ ws,
+ viewport_x,
+ viewport_y);
+
+ /* NEW-WINDOW command */
+ if (new_window_option)
+ {
+ command = g_string_append_c (command, '\v');
+ command = g_string_append (command, "NEW-WINDOW");
+ }
+
+ /* NEW-DOCUMENT command */
+ if (new_document_option)
+ {
+ command = g_string_append_c (command, '\v');
+ command = g_string_append (command, "NEW-DOCUMENT");
+ }
+
+ /* OPEN_URIS command, optionally specify line_num and encoding */
+ if (file_list)
+ {
+ GSList *l;
+
+ command = g_string_append_c (command, '\v');
+ command = g_string_append (command, "OPEN-URIS");
+
+ g_string_append_printf (command,
+ "\t%d\t%s\t%u\t",
+ line_position,
+ encoding_charset ? encoding_charset : "",
+ g_slist_length (file_list));
+
+ for (l = file_list; l != NULL; l = l->next)
+ {
+ gchar *uri;
+
+ uri = g_file_get_uri (G_FILE (l->data));
+ command = g_string_append (command, uri);
+ if (l->next != NULL)
+ command = g_string_append_c (command, ' ');
+
+ g_free (uri);
+ }
+ }
+
+ pluma_debug_message (DEBUG_APP, "Bacon Message: %s", command->str);
+
+ bacon_message_connection_send (connection,
+ command->str);
+
+ g_string_free (command, TRUE);
+}
+#endif /* G_OS_WIN32 */
+
+#ifdef G_OS_WIN32
+static void
+setup_path (void)
+{
+ gchar *path;
+ gchar *installdir;
+ gchar *bin;
+
+ installdir = g_win32_get_package_installation_directory_of_module (NULL);
+
+ bin = g_build_filename (installdir,
+ "bin", NULL);
+ g_free (installdir);
+
+ /* Set PATH to include the pluma executable's folder */
+ path = g_build_path (";",
+ bin,
+ g_getenv ("PATH"),
+ NULL);
+ g_free (bin);
+
+ if (!g_setenv ("PATH", path, TRUE))
+ g_warning ("Could not set PATH for pluma");
+
+ g_free (path);
+}
+#endif
+
+int
+main (int argc, char *argv[])
+{
+ GOptionContext *context;
+ PlumaPluginsEngine *engine;
+ PlumaWindow *window;
+ PlumaApp *app;
+ gboolean restored = FALSE;
+ GError *error = NULL;
+ gchar *dir;
+ gchar *icon_dir;
+
+ /* Init type system as soon as possible */
+ g_type_init ();
+
+ /* Init glib threads asap */
+ g_thread_init (NULL);
+
+ /* Setup debugging */
+ pluma_debug_init ();
+ pluma_debug_message (DEBUG_APP, "Startup");
+
+ setlocale (LC_ALL, "");
+
+ dir = pluma_dirs_get_pluma_locale_dir ();
+ bindtextdomain (GETTEXT_PACKAGE, dir);
+ g_free (dir);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ startup_timestamp = get_startup_timestamp();
+
+ /* Setup command line options */
+ context = g_option_context_new (_("- Edit text files"));
+ g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
+ g_option_context_add_group (context, gtk_get_option_group (FALSE));
+ g_option_context_add_group (context, egg_sm_client_get_option_group ());
+
+#ifdef G_OS_WIN32
+ setup_path ();
+
+ /* If we open pluma from a console get the stdout printing */
+ if (fileno (stdout) != -1 &&
+ _get_osfhandle (fileno (stdout)) != -1)
+ {
+ /* stdout is fine, presumably redirected to a file or pipe */
+ }
+ else
+ {
+ typedef BOOL (* WINAPI AttachConsole_t) (DWORD);
+
+ AttachConsole_t p_AttachConsole =
+ (AttachConsole_t) GetProcAddress (GetModuleHandle ("kernel32.dll"),
+ "AttachConsole");
+
+ if (p_AttachConsole != NULL && p_AttachConsole (ATTACH_PARENT_PROCESS))
+ {
+ freopen ("CONOUT$", "w", stdout);
+ dup2 (fileno (stdout), 1);
+ freopen ("CONOUT$", "w", stderr);
+ dup2 (fileno (stderr), 2);
+ }
+ }
+#endif
+
+ gtk_init (&argc, &argv);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_print(_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
+ error->message, argv[0]);
+ g_error_free (error);
+ return 1;
+ }
+
+ g_option_context_free (context);
+
+#ifndef G_OS_WIN32
+ pluma_debug_message (DEBUG_APP, "Create bacon connection");
+
+ connection = bacon_message_connection_new ("pluma");
+
+ if (connection != NULL)
+ {
+ if (!bacon_message_connection_get_is_server (connection))
+ {
+ pluma_debug_message (DEBUG_APP, "I'm a client");
+
+ pluma_get_command_line_data ();
+
+ send_bacon_message ();
+
+ free_command_line_data ();
+
+ /* we never popup a window... tell startup-notification
+ * that we are done.
+ */
+ gdk_notify_startup_complete ();
+
+ bacon_message_connection_free (connection);
+
+ exit (0);
+ }
+ else
+ {
+ pluma_debug_message (DEBUG_APP, "I'm a server");
+
+ bacon_message_connection_set_callback (connection,
+ on_message_received,
+ NULL);
+ }
+ }
+ else
+ {
+ g_warning ("Cannot create the 'pluma' connection.");
+ }
+#endif
+
+ pluma_debug_message (DEBUG_APP, "Set icon");
+
+ dir = pluma_dirs_get_pluma_data_dir ();
+ icon_dir = g_build_filename (dir,
+ "icons",
+ NULL);
+ g_free (dir);
+
+ gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
+ icon_dir);
+ g_free (icon_dir);
+
+#ifdef GDK_WINDOWING_X11
+ /* Set the associated .desktop file */
+ egg_set_desktop_file (DATADIR "/applications/pluma.desktop");
+#else
+ /* manually set name and icon */
+ g_set_application_name("pluma");
+ gtk_window_set_default_icon_name ("accessories-text-editor");
+#endif
+
+ /* Load user preferences */
+ pluma_debug_message (DEBUG_APP, "Init prefs manager");
+ pluma_prefs_manager_app_init ();
+
+ /* Init plugins engine */
+ pluma_debug_message (DEBUG_APP, "Init plugins");
+ engine = pluma_plugins_engine_get_default ();
+
+ #if !GTK_CHECK_VERSION(3, 0, 0)
+ gtk_about_dialog_set_url_hook(pluma_utils_activate_url, NULL, NULL);
+ #endif
+ /* Initialize session management */
+ pluma_debug_message (DEBUG_APP, "Init session manager");
+ pluma_session_init ();
+
+#ifdef OS_OSX
+ ige_mac_menu_set_global_key_handler_enabled (FALSE);
+#endif
+
+ if (pluma_session_is_restored ())
+ restored = pluma_session_load ();
+
+ if (!restored)
+ {
+ pluma_debug_message (DEBUG_APP, "Analyze command line data");
+ pluma_get_command_line_data ();
+
+ pluma_debug_message (DEBUG_APP, "Get default app");
+ app = pluma_app_get_default ();
+
+ pluma_debug_message (DEBUG_APP, "Create main window");
+ window = pluma_app_create_window (app, NULL);
+
+ if (file_list != NULL)
+ {
+ const PlumaEncoding *encoding = NULL;
+
+ if (encoding_charset)
+ encoding = pluma_encoding_get_from_charset (encoding_charset);
+
+ pluma_debug_message (DEBUG_APP, "Load files");
+ _pluma_cmd_load_files_from_prompt (window,
+ file_list,
+ encoding,
+ line_position);
+ }
+ else
+ {
+ pluma_debug_message (DEBUG_APP, "Create tab");
+ pluma_window_create_tab (window, TRUE);
+ }
+
+ pluma_debug_message (DEBUG_APP, "Show window");
+ gtk_widget_show (GTK_WIDGET (window));
+
+ free_command_line_data ();
+ }
+
+ pluma_debug_message (DEBUG_APP, "Start gtk-main");
+
+#ifdef OS_OSX
+ pluma_osx_init(pluma_app_get_default ());
+#endif
+ gtk_main();
+
+#ifndef G_OS_WIN32
+ bacon_message_connection_free (connection);
+#endif
+
+ /* We kept the original engine reference here. So let's unref it to
+ * finalize it properly.
+ */
+ g_object_unref (engine);
+ pluma_prefs_manager_app_shutdown ();
+
+#ifndef ENABLE_GVFS_METADATA
+ pluma_metadata_manager_shutdown ();
+#endif
+
+ return 0;
+}
+
diff --git a/pluma/pluma.rc b/pluma/pluma.rc
new file mode 100755
index 00000000..83356308
--- /dev/null
+++ b/pluma/pluma.rc
@@ -0,0 +1 @@
+A ICON MOVEABLE PURE LOADONCALL DISCARDABLE "../pixmaps/pluma.ico"
diff --git a/pluma/plumatextregion.c b/pluma/plumatextregion.c
new file mode 100755
index 00000000..511df64b
--- /dev/null
+++ b/pluma/plumatextregion.c
@@ -0,0 +1,647 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * plumatextregion.h - GtkTextMark based region utility functions
+ *
+ * This file is part of the GtkSourceView widget
+ *
+ * Copyright (C) 2002 Gustavo Gir�ldez <[email protected]>
+ *
+ * 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 <glib.h>
+
+#include "plumatextregion.h"
+
+
+#undef ENABLE_DEBUG
+/*
+#define ENABLE_DEBUG
+*/
+
+#ifdef ENABLE_DEBUG
+#define DEBUG(x) (x)
+#else
+#define DEBUG(x)
+#endif
+
+typedef struct _Subregion {
+ GtkTextMark *start;
+ GtkTextMark *end;
+} Subregion;
+
+struct _PlumaTextRegion {
+ GtkTextBuffer *buffer;
+ GList *subregions;
+ guint32 time_stamp;
+};
+
+typedef struct _PlumaTextRegionIteratorReal PlumaTextRegionIteratorReal;
+
+struct _PlumaTextRegionIteratorReal {
+ PlumaTextRegion *region;
+ guint32 region_time_stamp;
+
+ GList *subregions;
+};
+
+
+/* ----------------------------------------------------------------------
+ Private interface
+ ---------------------------------------------------------------------- */
+
+/* Find and return a subregion node which contains the given text
+ iter. If left_side is TRUE, return the subregion which contains
+ the text iter or which is the leftmost; else return the rightmost
+ subregion */
+static GList *
+find_nearest_subregion (PlumaTextRegion *region,
+ const GtkTextIter *iter,
+ GList *begin,
+ gboolean leftmost,
+ gboolean include_edges)
+{
+ GList *l, *retval;
+
+ g_return_val_if_fail (region != NULL && iter != NULL, NULL);
+
+ if (!begin)
+ begin = region->subregions;
+
+ if (begin)
+ retval = begin->prev;
+ else
+ retval = NULL;
+
+ for (l = begin; l; l = l->next) {
+ GtkTextIter sr_iter;
+ Subregion *sr = l->data;
+ gint cmp;
+
+ if (!leftmost) {
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_iter, sr->end);
+ cmp = gtk_text_iter_compare (iter, &sr_iter);
+ if (cmp < 0 || (cmp == 0 && include_edges)) {
+ retval = l;
+ break;
+ }
+
+ } else {
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_iter, sr->start);
+ cmp = gtk_text_iter_compare (iter, &sr_iter);
+ if (cmp > 0 || (cmp == 0 && include_edges))
+ retval = l;
+ else
+ break;
+ }
+ }
+ return retval;
+}
+
+/* ----------------------------------------------------------------------
+ Public interface
+ ---------------------------------------------------------------------- */
+
+PlumaTextRegion *
+pluma_text_region_new (GtkTextBuffer *buffer)
+{
+ PlumaTextRegion *region;
+
+ g_return_val_if_fail (buffer != NULL, NULL);
+
+ region = g_new (PlumaTextRegion, 1);
+ region->buffer = buffer;
+ region->subregions = NULL;
+ region->time_stamp = 0;
+
+ return region;
+}
+
+void
+pluma_text_region_destroy (PlumaTextRegion *region, gboolean delete_marks)
+{
+ g_return_if_fail (region != NULL);
+
+ while (region->subregions) {
+ Subregion *sr = region->subregions->data;
+ if (delete_marks) {
+ gtk_text_buffer_delete_mark (region->buffer, sr->start);
+ gtk_text_buffer_delete_mark (region->buffer, sr->end);
+ }
+ g_free (sr);
+ region->subregions = g_list_delete_link (region->subregions,
+ region->subregions);
+ }
+ region->buffer = NULL;
+ region->time_stamp = 0;
+
+ g_free (region);
+}
+
+GtkTextBuffer *
+pluma_text_region_get_buffer (PlumaTextRegion *region)
+{
+ g_return_val_if_fail (region != NULL, NULL);
+
+ return region->buffer;
+}
+
+static void
+pluma_text_region_clear_zero_length_subregions (PlumaTextRegion *region)
+{
+ GtkTextIter start, end;
+ GList *node;
+
+ g_return_if_fail (region != NULL);
+
+ for (node = region->subregions; node; ) {
+ Subregion *sr = node->data;
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &start, sr->start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &end, sr->end);
+ if (gtk_text_iter_equal (&start, &end)) {
+ gtk_text_buffer_delete_mark (region->buffer, sr->start);
+ gtk_text_buffer_delete_mark (region->buffer, sr->end);
+ g_free (sr);
+ if (node == region->subregions)
+ region->subregions = node = g_list_delete_link (node, node);
+ else
+ node = g_list_delete_link (node, node);
+
+ ++region->time_stamp;
+
+ } else {
+ node = node->next;
+ }
+ }
+}
+
+void
+pluma_text_region_add (PlumaTextRegion *region,
+ const GtkTextIter *_start,
+ const GtkTextIter *_end)
+{
+ GList *start_node, *end_node;
+ GtkTextIter start, end;
+
+ g_return_if_fail (region != NULL && _start != NULL && _end != NULL);
+
+ start = *_start;
+ end = *_end;
+
+ DEBUG (g_print ("---\n"));
+ DEBUG (pluma_text_region_debug_print (region));
+ DEBUG (g_message ("region_add (%d, %d)",
+ gtk_text_iter_get_offset (&start),
+ gtk_text_iter_get_offset (&end)));
+
+ gtk_text_iter_order (&start, &end);
+
+ /* don't add zero-length regions */
+ if (gtk_text_iter_equal (&start, &end))
+ return;
+
+ /* find bounding subregions */
+ start_node = find_nearest_subregion (region, &start, NULL, FALSE, TRUE);
+ end_node = find_nearest_subregion (region, &end, start_node, TRUE, TRUE);
+
+ if (start_node == NULL || end_node == NULL || end_node == start_node->prev) {
+ /* create the new subregion */
+ Subregion *sr = g_new0 (Subregion, 1);
+ sr->start = gtk_text_buffer_create_mark (region->buffer, NULL, &start, TRUE);
+ sr->end = gtk_text_buffer_create_mark (region->buffer, NULL, &end, FALSE);
+
+ if (start_node == NULL) {
+ /* append the new region */
+ region->subregions = g_list_append (region->subregions, sr);
+
+ } else if (end_node == NULL) {
+ /* prepend the new region */
+ region->subregions = g_list_prepend (region->subregions, sr);
+
+ } else {
+ /* we are in the middle of two subregions */
+ region->subregions = g_list_insert_before (region->subregions,
+ start_node, sr);
+ }
+ }
+ else {
+ GtkTextIter iter;
+ Subregion *sr = start_node->data;
+ if (start_node != end_node) {
+ /* we need to merge some subregions */
+ GList *l = start_node->next;
+ Subregion *q;
+
+ gtk_text_buffer_delete_mark (region->buffer, sr->end);
+ while (l != end_node) {
+ q = l->data;
+ gtk_text_buffer_delete_mark (region->buffer, q->start);
+ gtk_text_buffer_delete_mark (region->buffer, q->end);
+ g_free (q);
+ l = g_list_delete_link (l, l);
+ }
+ q = l->data;
+ gtk_text_buffer_delete_mark (region->buffer, q->start);
+ sr->end = q->end;
+ g_free (q);
+ l = g_list_delete_link (l, l);
+ }
+ /* now move marks if that action expands the region */
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &iter, sr->start);
+ if (gtk_text_iter_compare (&iter, &start) > 0)
+ gtk_text_buffer_move_mark (region->buffer, sr->start, &start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &iter, sr->end);
+ if (gtk_text_iter_compare (&iter, &end) < 0)
+ gtk_text_buffer_move_mark (region->buffer, sr->end, &end);
+ }
+
+ ++region->time_stamp;
+
+ DEBUG (pluma_text_region_debug_print (region));
+}
+
+void
+pluma_text_region_subtract (PlumaTextRegion *region,
+ const GtkTextIter *_start,
+ const GtkTextIter *_end)
+{
+ GList *start_node, *end_node, *node;
+ GtkTextIter sr_start_iter, sr_end_iter;
+ gboolean done;
+ gboolean start_is_outside, end_is_outside;
+ Subregion *sr;
+ GtkTextIter start, end;
+
+ g_return_if_fail (region != NULL && _start != NULL && _end != NULL);
+
+ start = *_start;
+ end = *_end;
+
+ DEBUG (g_print ("---\n"));
+ DEBUG (pluma_text_region_debug_print (region));
+ DEBUG (g_message ("region_substract (%d, %d)",
+ gtk_text_iter_get_offset (&start),
+ gtk_text_iter_get_offset (&end)));
+
+ gtk_text_iter_order (&start, &end);
+
+ /* find bounding subregions */
+ start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
+ end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
+
+ /* easy case first */
+ if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
+ return;
+
+ /* deal with the start point */
+ start_is_outside = end_is_outside = FALSE;
+
+ sr = start_node->data;
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
+
+ if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter) &&
+ !gtk_text_iter_equal (&start, &sr_start_iter)) {
+ /* the starting point is inside the first subregion */
+ if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
+ !gtk_text_iter_equal (&end, &sr_end_iter)) {
+ /* the ending point is also inside the first
+ subregion: we need to split */
+ Subregion *new_sr = g_new0 (Subregion, 1);
+ new_sr->end = sr->end;
+ new_sr->start = gtk_text_buffer_create_mark (region->buffer,
+ NULL, &end, TRUE);
+ start_node = g_list_insert_before (start_node, start_node->next, new_sr);
+
+ sr->end = gtk_text_buffer_create_mark (region->buffer,
+ NULL, &start, FALSE);
+
+ /* no further processing needed */
+ DEBUG (g_message ("subregion splitted"));
+
+ return;
+ } else {
+ /* the ending point is outside, so just move
+ the end of the subregion to the starting point */
+ gtk_text_buffer_move_mark (region->buffer, sr->end, &start);
+ }
+ } else {
+ /* the starting point is outside (and so to the left)
+ of the first subregion */
+ DEBUG (g_message ("start is outside"));
+
+ start_is_outside = TRUE;
+ }
+
+ /* deal with the end point */
+ if (start_node != end_node) {
+ sr = end_node->data;
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
+ }
+
+ if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
+ !gtk_text_iter_equal (&end, &sr_end_iter)) {
+ /* ending point is inside, move the start mark */
+ gtk_text_buffer_move_mark (region->buffer, sr->start, &end);
+ } else {
+ end_is_outside = TRUE;
+ DEBUG (g_message ("end is outside"));
+
+ }
+
+ /* finally remove any intermediate subregions */
+ done = FALSE;
+ node = start_node;
+
+ while (!done) {
+ if (node == end_node)
+ /* we are done, exit in the next iteration */
+ done = TRUE;
+
+ if ((node == start_node && !start_is_outside) ||
+ (node == end_node && !end_is_outside)) {
+ /* skip starting or ending node */
+ node = node->next;
+ } else {
+ GList *l = node->next;
+ sr = node->data;
+ gtk_text_buffer_delete_mark (region->buffer, sr->start);
+ gtk_text_buffer_delete_mark (region->buffer, sr->end);
+ g_free (sr);
+ region->subregions = g_list_delete_link (region->subregions,
+ node);
+ node = l;
+ }
+ }
+
+ ++region->time_stamp;
+
+ DEBUG (pluma_text_region_debug_print (region));
+
+ /* now get rid of empty subregions */
+ pluma_text_region_clear_zero_length_subregions (region);
+
+ DEBUG (pluma_text_region_debug_print (region));
+}
+
+gint
+pluma_text_region_subregions (PlumaTextRegion *region)
+{
+ g_return_val_if_fail (region != NULL, 0);
+
+ return g_list_length (region->subregions);
+}
+
+gboolean
+pluma_text_region_nth_subregion (PlumaTextRegion *region,
+ guint subregion,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ Subregion *sr;
+
+ g_return_val_if_fail (region != NULL, FALSE);
+
+ sr = g_list_nth_data (region->subregions, subregion);
+ if (sr == NULL)
+ return FALSE;
+
+ if (start)
+ gtk_text_buffer_get_iter_at_mark (region->buffer, start, sr->start);
+ if (end)
+ gtk_text_buffer_get_iter_at_mark (region->buffer, end, sr->end);
+
+ return TRUE;
+}
+
+PlumaTextRegion *
+pluma_text_region_intersect (PlumaTextRegion *region,
+ const GtkTextIter *_start,
+ const GtkTextIter *_end)
+{
+ GList *start_node, *end_node, *node;
+ GtkTextIter sr_start_iter, sr_end_iter;
+ Subregion *sr, *new_sr;
+ gboolean done;
+ PlumaTextRegion *new_region;
+ GtkTextIter start, end;
+
+ g_return_val_if_fail (region != NULL && _start != NULL && _end != NULL, NULL);
+
+ start = *_start;
+ end = *_end;
+
+ gtk_text_iter_order (&start, &end);
+
+ /* find bounding subregions */
+ start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
+ end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
+
+ /* easy case first */
+ if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
+ return NULL;
+
+ new_region = pluma_text_region_new (region->buffer);
+ done = FALSE;
+
+ sr = start_node->data;
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
+
+ /* starting node */
+ if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter)) {
+ new_sr = g_new0 (Subregion, 1);
+ new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
+
+ new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
+ &start, TRUE);
+ if (start_node == end_node) {
+ /* things will finish shortly */
+ done = TRUE;
+ if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
+ new_sr->end = gtk_text_buffer_create_mark (new_region->buffer,
+ NULL, &end, FALSE);
+ else
+ new_sr->end = gtk_text_buffer_create_mark (new_region->buffer,
+ NULL, &sr_end_iter,
+ FALSE);
+ } else {
+ new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
+ &sr_end_iter, FALSE);
+ }
+ node = start_node->next;
+ } else {
+ /* start should be the same as the subregion, so copy it in the loop */
+ node = start_node;
+ }
+
+ if (!done) {
+ while (node != end_node) {
+ /* copy intermediate subregions verbatim */
+ sr = node->data;
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter,
+ sr->start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
+
+ new_sr = g_new0 (Subregion, 1);
+ new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
+ new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
+ &sr_start_iter, TRUE);
+ new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
+ &sr_end_iter, FALSE);
+ /* next node */
+ node = node->next;
+ }
+
+ /* ending node */
+ sr = node->data;
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
+
+ new_sr = g_new0 (Subregion, 1);
+ new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
+
+ new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
+ &sr_start_iter, TRUE);
+
+ if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
+ new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
+ &end, FALSE);
+ else
+ new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
+ &sr_end_iter, FALSE);
+ }
+
+ new_region->subregions = g_list_reverse (new_region->subregions);
+ return new_region;
+}
+
+static gboolean
+check_iterator (PlumaTextRegionIteratorReal *real)
+{
+ if ((real->region == NULL) ||
+ (real->region_time_stamp != real->region->time_stamp))
+ {
+ g_warning("Invalid iterator: either the iterator "
+ "is uninitialized, or the region "
+ "has been modified since the iterator "
+ "was created.");
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+pluma_text_region_get_iterator (PlumaTextRegion *region,
+ PlumaTextRegionIterator *iter,
+ guint start)
+{
+ PlumaTextRegionIteratorReal *real;
+
+ g_return_if_fail (region != NULL);
+ g_return_if_fail (iter != NULL);
+
+ real = (PlumaTextRegionIteratorReal *)iter;
+
+ /* region->subregions may be NULL, -> end iter */
+
+ real->region = region;
+ real->subregions = g_list_nth (region->subregions, start);
+ real->region_time_stamp = region->time_stamp;
+}
+
+gboolean
+pluma_text_region_iterator_is_end (PlumaTextRegionIterator *iter)
+{
+ PlumaTextRegionIteratorReal *real;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ real = (PlumaTextRegionIteratorReal *)iter;
+ g_return_val_if_fail (check_iterator (real), FALSE);
+
+ return (real->subregions == NULL);
+}
+
+gboolean
+pluma_text_region_iterator_next (PlumaTextRegionIterator *iter)
+{
+ PlumaTextRegionIteratorReal *real;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ real = (PlumaTextRegionIteratorReal *)iter;
+ g_return_val_if_fail (check_iterator (real), FALSE);
+
+ if (real->subregions != NULL) {
+ real->subregions = g_list_next (real->subregions);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+void
+pluma_text_region_iterator_get_subregion (PlumaTextRegionIterator *iter,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ PlumaTextRegionIteratorReal *real;
+ Subregion *sr;
+
+ g_return_if_fail (iter != NULL);
+
+ real = (PlumaTextRegionIteratorReal *)iter;
+ g_return_if_fail (check_iterator (real));
+ g_return_if_fail (real->subregions != NULL);
+
+ sr = (Subregion*)real->subregions->data;
+ g_return_if_fail (sr != NULL);
+
+ if (start)
+ gtk_text_buffer_get_iter_at_mark (real->region->buffer, start, sr->start);
+ if (end)
+ gtk_text_buffer_get_iter_at_mark (real->region->buffer, end, sr->end);
+}
+
+void
+pluma_text_region_debug_print (PlumaTextRegion *region)
+{
+ GList *l;
+
+ g_return_if_fail (region != NULL);
+
+ g_print ("Subregions: ");
+ l = region->subregions;
+ while (l) {
+ Subregion *sr = l->data;
+ GtkTextIter iter1, iter2;
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &iter1, sr->start);
+ gtk_text_buffer_get_iter_at_mark (region->buffer, &iter2, sr->end);
+ g_print ("%d-%d ", gtk_text_iter_get_offset (&iter1),
+ gtk_text_iter_get_offset (&iter2));
+ l = l->next;
+ }
+ g_print ("\n");
+}
+
diff --git a/pluma/plumatextregion.h b/pluma/plumatextregion.h
new file mode 100755
index 00000000..c87221f4
--- /dev/null
+++ b/pluma/plumatextregion.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * plumatextregion.h - GtkTextMark based region utility functions
+ *
+ * This file is part of the GtkSourceView widget
+ *
+ * Copyright (C) 2002 Gustavo Gir�ldez <[email protected]>
+ *
+ * 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 __PLUMA_TEXT_REGION_H__
+#define __PLUMA_TEXT_REGION_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _PlumaTextRegion PlumaTextRegion;
+typedef struct _PlumaTextRegionIterator PlumaTextRegionIterator;
+
+struct _PlumaTextRegionIterator {
+ /* PlumaTextRegionIterator is an opaque datatype; ignore all these fields.
+ * Initialize the iter with pluma_text_region_get_iterator
+ * function
+ */
+ /*< private >*/
+ gpointer dummy1;
+ guint32 dummy2;
+ gpointer dummy3;
+};
+
+PlumaTextRegion *pluma_text_region_new (GtkTextBuffer *buffer);
+void pluma_text_region_destroy (PlumaTextRegion *region,
+ gboolean delete_marks);
+
+GtkTextBuffer *pluma_text_region_get_buffer (PlumaTextRegion *region);
+
+void pluma_text_region_add (PlumaTextRegion *region,
+ const GtkTextIter *_start,
+ const GtkTextIter *_end);
+
+void pluma_text_region_subtract (PlumaTextRegion *region,
+ const GtkTextIter *_start,
+ const GtkTextIter *_end);
+
+gint pluma_text_region_subregions (PlumaTextRegion *region);
+
+gboolean pluma_text_region_nth_subregion (PlumaTextRegion *region,
+ guint subregion,
+ GtkTextIter *start,
+ GtkTextIter *end);
+
+PlumaTextRegion *pluma_text_region_intersect (PlumaTextRegion *region,
+ const GtkTextIter *_start,
+ const GtkTextIter *_end);
+
+void pluma_text_region_get_iterator (PlumaTextRegion *region,
+ PlumaTextRegionIterator *iter,
+ guint start);
+
+gboolean pluma_text_region_iterator_is_end (PlumaTextRegionIterator *iter);
+
+/* Returns FALSE if iterator is the end iterator */
+gboolean pluma_text_region_iterator_next (PlumaTextRegionIterator *iter);
+
+void pluma_text_region_iterator_get_subregion (PlumaTextRegionIterator *iter,
+ GtkTextIter *start,
+ GtkTextIter *end);
+
+void pluma_text_region_debug_print (PlumaTextRegion *region);
+
+G_END_DECLS
+
+#endif /* __PLUMA_TEXT_REGION_H__ */
diff --git a/pluma/smclient/Makefile.am b/pluma/smclient/Makefile.am
new file mode 100755
index 00000000..4f6d1d70
--- /dev/null
+++ b/pluma/smclient/Makefile.am
@@ -0,0 +1,52 @@
+if OS_WIN32
+platform_sources = eggsmclient-win32.c
+platform_logout_test_ldflags = -mwindows
+else
+if OS_OSX
+platform_defines = -xobjective-c
+platform_ldflags = -framework Carbon
+platform_sources = eggsmclient-osx.c
+else
+platform_defines = -DEGG_SM_CLIENT_BACKEND_XSMP
+platform_libs = libeggdesktopfile.la
+platform_ltlibraries = libeggdesktopfile.la
+platform_sources = eggsmclient-xsmp.c
+endif
+endif
+
+INCLUDES = \
+ -DG_LOG_DOMAIN=\""EggSMClient"\" \
+ $(PLUMA_CFLAGS) \
+ $(platform_defines) \
+ $(EGG_SMCLIENT_CFLAGS)
+
+noinst_LTLIBRARIES = \
+ libeggsmclient.la \
+ $(platform_ltlibraries)
+
+libeggsmclient_la_LIBADD = \
+ $(EGG_SMCLIENT_LIBS) \
+ $(platform_libs)
+
+libeggsmclient_la_LDFLAGS = \
+ $(platform_ldflags)
+
+libeggsmclient_la_SOURCES = \
+ eggsmclient.c \
+ eggsmclient.h \
+ eggsmclient-private.h \
+ $(platform_sources)
+
+libeggdesktopfile_la_LIBADD = \
+ $(EGG_LIBS)
+
+libeggdesktopfile_la_SOURCES = \
+ eggdesktopfile.c \
+ eggdesktopfile.h
+
+EXTRA_DIST = \
+ eggsmclient-osx.c \
+ eggsmclient-win32.c \
+ eggsmclient-xsmp.c
+
+-include $(top_srcdir)/git.mk
diff --git a/pluma/smclient/eggdesktopfile.c b/pluma/smclient/eggdesktopfile.c
new file mode 100755
index 00000000..5ac79507
--- /dev/null
+++ b/pluma/smclient/eggdesktopfile.c
@@ -0,0 +1,1510 @@
+/* eggdesktopfile.c - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Based on mate-desktop-item.c
+ * Copyright (C) 1999, 2000 Red Hat Inc.
+ * Copyright (C) 2001 George Lebl
+ *
+ * This library 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 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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 library; see the file COPYING.LIB. 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 "eggdesktopfile.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+struct EggDesktopFile {
+ GKeyFile *key_file;
+ char *source;
+
+ char *name, *icon;
+ EggDesktopFileType type;
+ char document_code;
+};
+
+/**
+ * egg_desktop_file_new:
+ * @desktop_file_path: path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @desktop_file.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new (const char *desktop_file_path, GError **error)
+{
+ GKeyFile *key_file;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ return egg_desktop_file_new_from_key_file (key_file, desktop_file_path,
+ error);
+}
+
+/**
+ * egg_desktop_file_new_from_data_dirs:
+ * @desktop_file_path: relative path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Looks for @desktop_file_path in the paths returned from
+ * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
+ * a new #EggDesktopFile from it.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ GKeyFile *key_file;
+ char *full_path;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path,
+ &full_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ desktop_file = egg_desktop_file_new_from_key_file (key_file,
+ full_path,
+ error);
+ g_free (full_path);
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_new_from_dirs:
+ * @desktop_file_path: relative path to a Freedesktop-style Desktop file
+ * @search_dirs: NULL-terminated array of directories to search
+ * @error: error pointer
+ *
+ * Looks for @desktop_file_path in the paths returned from
+ * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
+ * a new #EggDesktopFile from it.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_dirs (const char *desktop_file_path,
+ const char **search_dirs,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ GKeyFile *key_file;
+ char *full_path;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_dirs (key_file, desktop_file_path, search_dirs,
+ &full_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ desktop_file = egg_desktop_file_new_from_key_file (key_file,
+ full_path,
+ error);
+ g_free (full_path);
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_new_from_key_file:
+ * @key_file: a #GKeyFile representing a desktop file
+ * @source: the path or URI that @key_file was loaded from, or %NULL
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @key_file. Assumes ownership of
+ * @key_file (on success or failure); you should consider @key_file to
+ * be freed after calling this function.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ char *version, *type;
+
+ if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP))
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("File is not a valid .desktop file"));
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_VERSION,
+ NULL);
+ if (version)
+ {
+ double version_num;
+ char *end;
+
+ version_num = g_ascii_strtod (version, &end);
+ if (*end)
+ {
+ g_warning ("Invalid Version string '%s' in %s",
+ version, source ? source : "(unknown)");
+ }
+ else if (version_num > 1.0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("Unrecognized desktop file Version '%s'"), version);
+ g_free (version);
+ g_key_file_free (key_file);
+ return NULL;
+ }
+ g_free (version);
+ }
+
+ desktop_file = g_new0 (EggDesktopFile, 1);
+ desktop_file->key_file = key_file;
+
+ if (g_path_is_absolute (source))
+ desktop_file->source = g_filename_to_uri (source, NULL, NULL);
+ else
+ desktop_file->source = g_strdup (source);
+
+ desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME, error);
+ if (!desktop_file->name)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE, error);
+ if (!type)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ if (!strcmp (type, "Application"))
+ {
+ char *exec, *p;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION;
+
+ exec = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ {
+ egg_desktop_file_free (desktop_file);
+ g_free (type);
+ return NULL;
+ }
+
+ /* See if it takes paths or URIs or neither */
+ for (p = exec; *p; p++)
+ {
+ if (*p == '%')
+ {
+ if (p[1] == '\0' || strchr ("FfUu", p[1]))
+ {
+ desktop_file->document_code = p[1];
+ break;
+ }
+ p++;
+ }
+ }
+
+ g_free (exec);
+ }
+ else if (!strcmp (type, "Link"))
+ {
+ char *url;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK;
+
+ url = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ {
+ egg_desktop_file_free (desktop_file);
+ g_free (type);
+ return NULL;
+ }
+ g_free (url);
+ }
+ else if (!strcmp (type, "Directory"))
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY;
+ else
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED;
+
+ g_free (type);
+
+ /* Check the Icon key */
+ desktop_file->icon = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ICON,
+ NULL);
+ if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon))
+ {
+ char *ext;
+
+ /* Lots of .desktop files still get this wrong */
+ ext = strrchr (desktop_file->icon, '.');
+ if (ext && (!strcmp (ext, ".png") ||
+ !strcmp (ext, ".xpm") ||
+ !strcmp (ext, ".svg")))
+ {
+ g_warning ("Desktop file '%s' has malformed Icon key '%s'"
+ "(should not include extension)",
+ source ? source : "(unknown)",
+ desktop_file->icon);
+ *ext = '\0';
+ }
+ }
+
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_free:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Frees @desktop_file.
+ **/
+void
+egg_desktop_file_free (EggDesktopFile *desktop_file)
+{
+ g_key_file_free (desktop_file->key_file);
+ g_free (desktop_file->source);
+ g_free (desktop_file->name);
+ g_free (desktop_file->icon);
+ g_free (desktop_file);
+}
+
+/**
+ * egg_desktop_file_get_source:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the URI that @desktop_file was loaded from.
+ *
+ * Return value: @desktop_file's source URI
+ **/
+const char *
+egg_desktop_file_get_source (EggDesktopFile *desktop_file)
+{
+ return desktop_file->source;
+}
+
+/**
+ * egg_desktop_file_get_desktop_file_type:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the desktop file type of @desktop_file.
+ *
+ * Return value: @desktop_file's type
+ **/
+EggDesktopFileType
+egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file)
+{
+ return desktop_file->type;
+}
+
+/**
+ * egg_desktop_file_get_name:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the (localized) value of @desktop_file's "Name" key.
+ *
+ * Return value: the application/link name
+ **/
+const char *
+egg_desktop_file_get_name (EggDesktopFile *desktop_file)
+{
+ return desktop_file->name;
+}
+
+/**
+ * egg_desktop_file_get_icon:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the value of @desktop_file's "Icon" key.
+ *
+ * If the icon string is a full path (that is, if g_path_is_absolute()
+ * returns %TRUE when called on it), it points to a file containing an
+ * unthemed icon. If the icon string is not a full path, it is the
+ * name of a themed icon, which can be looked up with %GtkIconTheme,
+ * or passed directly to a theme-aware widget like %GtkImage or
+ * %GtkCellRendererPixbuf.
+ *
+ * Return value: the icon path or name
+ **/
+const char *
+egg_desktop_file_get_icon (EggDesktopFile *desktop_file)
+{
+ return desktop_file->icon;
+}
+
+gboolean
+egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error)
+{
+ return g_key_file_get_locale_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, locale,
+ error);
+}
+
+gboolean
+egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+double
+egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_double (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char **
+egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, length,
+ error);
+}
+
+char **
+egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_locale_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ locale, length,
+ error);
+}
+
+/**
+ * egg_desktop_file_can_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @desktop_environment: the name of the running desktop environment,
+ * or %NULL
+ *
+ * Tests if @desktop_file can/should be launched in the current
+ * environment. If @desktop_environment is non-%NULL, @desktop_file's
+ * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that
+ * this desktop_file is appropriate for the named environment.
+ *
+ * Furthermore, if @desktop_file has type
+ * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is
+ * also checked, to make sure the binary it points to exists.
+ *
+ * egg_desktop_file_can_launch() does NOT check the value of the
+ * "Hidden" key.
+ *
+ * Return value: %TRUE if @desktop_file can be launched
+ **/
+gboolean
+egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment)
+{
+ char *try_exec, *found_program;
+ char **only_show_in, **not_show_in;
+ gboolean found;
+ int i;
+
+ if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION &&
+ desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK)
+ return FALSE;
+
+ if (desktop_environment)
+ {
+ only_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN,
+ NULL, NULL);
+ if (only_show_in)
+ {
+ for (i = 0, found = FALSE; only_show_in[i] && !found; i++)
+ {
+ if (!strcmp (only_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (only_show_in);
+
+ if (!found)
+ return FALSE;
+ }
+
+ not_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN,
+ NULL, NULL);
+ if (not_show_in)
+ {
+ for (i = 0, found = FALSE; not_show_in[i] && !found; i++)
+ {
+ if (!strcmp (not_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (not_show_in);
+
+ if (found)
+ return FALSE;
+ }
+ }
+
+ if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION)
+ {
+ try_exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TRY_EXEC,
+ NULL);
+ if (try_exec)
+ {
+ found_program = g_find_program_in_path (try_exec);
+ g_free (try_exec);
+
+ if (!found_program)
+ return FALSE;
+ g_free (found_program);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * egg_desktop_file_accepts_documents:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file represents an application that can accept
+ * documents on the command line.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file)
+{
+ return desktop_file->document_code != 0;
+}
+
+/**
+ * egg_desktop_file_accepts_multiple:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept multiple documents at once.
+ *
+ * If this returns %FALSE, you can still pass multiple documents to
+ * egg_desktop_file_launch(), but that will result in multiple copies
+ * of the application being launched. See egg_desktop_file_launch()
+ * for more details.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'F' ||
+ desktop_file->document_code == 'U');
+}
+
+/**
+ * egg_desktop_file_accepts_uris:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept (non-"file:") URIs as documents to
+ * open.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'U' ||
+ desktop_file->document_code == 'u');
+}
+
+static void
+append_quoted_word (GString *str,
+ const char *s,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ const char *p;
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "\"'");
+
+ if (!strchr (s, '\''))
+ g_string_append (str, s);
+ else
+ {
+ for (p = s; *p != '\0'; p++)
+ {
+ if (*p == '\'')
+ g_string_append (str, "'\\''");
+ else
+ g_string_append_c (str, *p);
+ }
+ }
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "'\"");
+}
+
+static void
+do_percent_subst (EggDesktopFile *desktop_file,
+ char code,
+ GString *str,
+ GSList **documents,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ GSList *d;
+ char *doc;
+
+ switch (code)
+ {
+ case '%':
+ g_string_append_c (str, '%');
+ break;
+
+ case 'F':
+ case 'U':
+ for (d = *documents; d; d = d->next)
+ {
+ doc = d->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ }
+ *documents = NULL;
+ break;
+
+ case 'f':
+ case 'u':
+ if (*documents)
+ {
+ doc = (*documents)->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ *documents = (*documents)->next;
+ }
+ break;
+
+ case 'i':
+ if (desktop_file->icon)
+ {
+ g_string_append (str, "--icon ");
+ append_quoted_word (str, desktop_file->icon,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'c':
+ if (desktop_file->name)
+ {
+ append_quoted_word (str, desktop_file->name,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'k':
+ if (desktop_file->source)
+ {
+ append_quoted_word (str, desktop_file->source,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'D':
+ case 'N':
+ case 'd':
+ case 'n':
+ case 'v':
+ case 'm':
+ /* Deprecated; skip */
+ break;
+
+ default:
+ g_warning ("Unrecognized %%-code '%%%c' in Exec", code);
+ break;
+ }
+}
+
+static char *
+parse_exec (EggDesktopFile *desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *exec, *p, *command;
+ gboolean escape, single_quot, double_quot;
+ GString *gs;
+
+ exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ return NULL;
+
+ /* Build the command */
+ gs = g_string_new (NULL);
+ escape = single_quot = double_quot = FALSE;
+
+ for (p = exec; *p != '\0'; p++)
+ {
+ if (escape)
+ {
+ escape = FALSE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\\')
+ {
+ if (!single_quot)
+ escape = TRUE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\'')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ single_quot = TRUE;
+ else if (single_quot)
+ single_quot = FALSE;
+ }
+ else if (*p == '"')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ double_quot = TRUE;
+ else if (double_quot)
+ double_quot = FALSE;
+ }
+ else if (*p == '%' && p[1])
+ {
+ do_percent_subst (desktop_file, p[1], gs, documents,
+ single_quot, double_quot);
+ p++;
+ }
+ else
+ g_string_append_c (gs, *p);
+ }
+
+ g_free (exec);
+ command = g_string_free (gs, FALSE);
+
+ /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ NULL))
+ {
+ GError *terminal_error = NULL;
+ gboolean use_terminal =
+ g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ &terminal_error);
+ if (terminal_error)
+ {
+ g_free (command);
+ g_propagate_error (error, terminal_error);
+ return NULL;
+ }
+
+ if (use_terminal)
+ {
+ gs = g_string_new ("xdg-terminal ");
+ append_quoted_word (gs, command, FALSE, FALSE);
+ g_free (command);
+ command = g_string_free (gs, FALSE);
+ }
+ }
+
+ return command;
+}
+
+static GSList *
+translate_document_list (EggDesktopFile *desktop_file, GSList *documents)
+{
+ gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file);
+ GSList *ret, *d;
+
+ for (d = documents, ret = NULL; d; d = d->next)
+ {
+ const char *document = d->data;
+ gboolean is_uri = !g_path_is_absolute (document);
+ char *translated;
+
+ if (accepts_uris)
+ {
+ if (is_uri)
+ translated = g_strdup (document);
+ else
+ translated = g_filename_to_uri (document, NULL, NULL);
+ }
+ else
+ {
+ if (is_uri)
+ translated = g_filename_from_uri (document, NULL, NULL);
+ else
+ translated = g_strdup (document);
+ }
+
+ if (translated)
+ ret = g_slist_prepend (ret, translated);
+ }
+
+ return g_slist_reverse (ret);
+}
+
+static void
+free_document_list (GSList *documents)
+{
+ GSList *d;
+
+ for (d = documents; d; d = d->next)
+ g_free (d->data);
+ g_slist_free (documents);
+}
+
+/**
+ * egg_desktop_file_parse_exec:
+ * @desktop_file: a #EggDesktopFile
+ * @documents: a list of document paths or URIs
+ * @error: error pointer
+ *
+ * Parses @desktop_file's Exec key, inserting @documents into it, and
+ * returns the result.
+ *
+ * If @documents contains non-file: URIs and @desktop_file does not
+ * accept URIs, those URIs will be ignored. Likewise, if @documents
+ * contains more elements than @desktop_file accepts, the extra
+ * documents will be ignored.
+ *
+ * Return value: the parsed Exec string
+ **/
+char *
+egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error)
+{
+ GSList *translated, *docs;
+ char *command;
+
+ docs = translated = translate_document_list (desktop_file, documents);
+ command = parse_exec (desktop_file, &docs, error);
+ free_document_list (translated);
+
+ return command;
+}
+
+static gboolean
+parse_link (EggDesktopFile *desktop_file,
+ EggDesktopFile **app_desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *url;
+ GKeyFile *key_file;
+
+ url = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ return FALSE;
+ *documents = g_slist_prepend (NULL, url);
+
+ /* FIXME: use gvfs */
+ key_file = g_key_file_new ();
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME,
+ "xdg-open");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE,
+ "Application");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ "xdg-open %u");
+ *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL);
+ return TRUE;
+}
+
+#if GTK_CHECK_VERSION (2, 12, 0)
+static char *
+start_startup_notification (GdkDisplay *display,
+ EggDesktopFile *desktop_file,
+ const char *argv0,
+ int screen,
+ int workspace,
+ guint32 launch_time)
+{
+ static int sequence = 0;
+ char *startup_id;
+ char *description, *wmclass;
+ char *screen_str, *workspace_str;
+
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ {
+ if (!g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ return NULL;
+ wmclass = NULL;
+ }
+ else
+ {
+ wmclass = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS,
+ NULL);
+ if (!wmclass)
+ return NULL;
+ }
+
+ if (launch_time == (guint32)-1)
+ launch_time = gdk_x11_display_get_user_time (display);
+ startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu",
+ g_get_prgname (),
+ (unsigned long)getpid (),
+ g_get_host_name (),
+ argv0,
+ sequence++,
+ (unsigned long)launch_time);
+
+ description = g_strdup_printf (_("Starting %s"), desktop_file->name);
+ screen_str = g_strdup_printf ("%d", screen);
+ workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace);
+
+ gdk_x11_display_broadcast_startup_message (display, "new",
+ "ID", startup_id,
+ "NAME", desktop_file->name,
+ "SCREEN", screen_str,
+ "BIN", argv0,
+ "ICON", desktop_file->icon,
+ "DESKTOP", workspace_str,
+ "DESCRIPTION", description,
+ "WMCLASS", wmclass,
+ NULL);
+
+ g_free (description);
+ g_free (wmclass);
+ g_free (screen_str);
+ g_free (workspace_str);
+
+ return startup_id;
+}
+
+static void
+end_startup_notification (GdkDisplay *display,
+ const char *startup_id)
+{
+ gdk_x11_display_broadcast_startup_message (display, "remove",
+ "ID", startup_id,
+ NULL);
+}
+
+#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */)
+
+typedef struct {
+ GdkDisplay *display;
+ char *startup_id;
+} StartupNotificationData;
+
+static gboolean
+startup_notification_timeout (gpointer data)
+{
+ StartupNotificationData *sn_data = data;
+
+ end_startup_notification (sn_data->display, sn_data->startup_id);
+ g_object_unref (sn_data->display);
+ g_free (sn_data->startup_id);
+ g_free (sn_data);
+
+ return FALSE;
+}
+
+static void
+set_startup_notification_timeout (GdkDisplay *display,
+ const char *startup_id)
+{
+ StartupNotificationData *sn_data;
+
+ sn_data = g_new (StartupNotificationData, 1);
+ sn_data->display = g_object_ref (display);
+ sn_data->startup_id = g_strdup (startup_id);
+
+ g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH,
+ startup_notification_timeout, sn_data);
+}
+#endif /* GTK 2.12 */
+
+static GPtrArray *
+array_putenv (GPtrArray *env, char *variable)
+{
+ guint i, keylen;
+
+ if (!env)
+ {
+ char **envp;
+
+ env = g_ptr_array_new ();
+
+ envp = g_listenv ();
+ for (i = 0; envp[i]; i++)
+ {
+ const char *value;
+
+ value = g_getenv (envp[i]);
+ g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i],
+ value ? value : ""));
+ }
+ g_strfreev (envp);
+ }
+
+ keylen = strcspn (variable, "=");
+
+ /* Remove old value of key */
+ for (i = 0; i < env->len; i++)
+ {
+ char *envvar = env->pdata[i];
+
+ if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=')
+ {
+ g_free (envvar);
+ g_ptr_array_remove_index_fast (env, i);
+ break;
+ }
+ }
+
+ /* Add new value */
+ g_ptr_array_add (env, g_strdup (variable));
+
+ return env;
+}
+
+static gboolean
+egg_desktop_file_launchv (EggDesktopFile *desktop_file,
+ GSList *documents, va_list args,
+ GError **error)
+{
+ EggDesktopFileLaunchOption option;
+ GSList *translated_documents = NULL, *docs = NULL;
+ char *command, **argv;
+ int argc, i, screen_num;
+ gboolean success, current_success;
+ GdkDisplay *display;
+ char *startup_id;
+
+ GPtrArray *env = NULL;
+ char **variables = NULL;
+ GdkScreen *screen = NULL;
+ int workspace = -1;
+ const char *directory = NULL;
+ guint32 launch_time = (guint32)-1;
+ GSpawnFlags flags = G_SPAWN_SEARCH_PATH;
+ GSpawnChildSetupFunc setup_func = NULL;
+ gpointer setup_data = NULL;
+
+ GPid *ret_pid = NULL;
+ int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL;
+ char **ret_startup_id = NULL;
+
+ if (documents && desktop_file->document_code == 0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Application does not accept documents on command line"));
+ return FALSE;
+ }
+
+ /* Read the options: technically it's incorrect for the caller to
+ * NULL-terminate the list of options (rather than 0-terminating
+ * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED,
+ * it's more consistent with other glib/gtk methods, and it will
+ * work as long as sizeof (int) <= sizeof (NULL), and NULL is
+ * represented as 0. (Which is true everywhere we care about.)
+ */
+ while ((option = va_arg (args, EggDesktopFileLaunchOption)))
+ {
+ switch (option)
+ {
+ case EGG_DESKTOP_FILE_LAUNCH_CLEARENV:
+ if (env)
+ g_ptr_array_free (env, TRUE);
+ env = g_ptr_array_new ();
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_PUTENV:
+ variables = va_arg (args, char **);
+ for (i = 0; variables[i]; i++)
+ env = array_putenv (env, variables[i]);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_SCREEN:
+ screen = va_arg (args, GdkScreen *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE:
+ workspace = va_arg (args, int);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY:
+ directory = va_arg (args, const char *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_TIME:
+ launch_time = va_arg (args, guint32);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_FLAGS:
+ flags |= va_arg (args, GSpawnFlags);
+ /* Make sure they didn't set any flags that don't make sense. */
+ flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO;
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC:
+ setup_func = va_arg (args, GSpawnChildSetupFunc);
+ setup_data = va_arg (args, gpointer);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID:
+ ret_pid = va_arg (args, GPid *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE:
+ ret_stdin = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE:
+ ret_stdout = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE:
+ ret_stderr = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID:
+ ret_startup_id = va_arg (args, char **);
+ break;
+
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
+ _("Unrecognized launch option: %d"),
+ GPOINTER_TO_INT (option));
+ success = FALSE;
+ goto out;
+ }
+ }
+
+ if (screen)
+ {
+ char *display_name = gdk_screen_make_display_name (screen);
+ char *display_env = g_strdup_printf ("DISPLAY=%s", display_name);
+ env = array_putenv (env, display_env);
+ g_free (display_name);
+ g_free (display_env);
+
+ display = gdk_screen_get_display (screen);
+ }
+ else
+ {
+ display = gdk_display_get_default ();
+ screen = gdk_display_get_default_screen (display);
+ }
+ screen_num = gdk_screen_get_number (screen);
+
+ translated_documents = translate_document_list (desktop_file, documents);
+ docs = translated_documents;
+
+ success = FALSE;
+
+ do
+ {
+ command = parse_exec (desktop_file, &docs, error);
+ if (!command)
+ goto out;
+
+ if (!g_shell_parse_argv (command, &argc, &argv, error))
+ {
+ g_free (command);
+ goto out;
+ }
+ g_free (command);
+
+#if GTK_CHECK_VERSION (2, 12, 0)
+ startup_id = start_startup_notification (display, desktop_file,
+ argv[0], screen_num,
+ workspace, launch_time);
+ if (startup_id)
+ {
+ char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
+ startup_id);
+ env = array_putenv (env, startup_id_env);
+ g_free (startup_id_env);
+ }
+#else
+ startup_id = NULL;
+#endif /* GTK 2.12 */
+
+ if (env != NULL)
+ g_ptr_array_add (env, NULL);
+
+ current_success =
+ g_spawn_async_with_pipes (directory,
+ argv,
+ env ? (char **)(env->pdata) : NULL,
+ flags,
+ setup_func, setup_data,
+ ret_pid,
+ ret_stdin, ret_stdout, ret_stderr,
+ error);
+ g_strfreev (argv);
+
+ if (startup_id)
+ {
+#if GTK_CHECK_VERSION (2, 12, 0)
+ if (current_success)
+ {
+ set_startup_notification_timeout (display, startup_id);
+
+ if (ret_startup_id)
+ *ret_startup_id = startup_id;
+ else
+ g_free (startup_id);
+ }
+ else
+#endif /* GTK 2.12 */
+ g_free (startup_id);
+ }
+ else if (ret_startup_id)
+ *ret_startup_id = NULL;
+
+ if (current_success)
+ {
+ /* If we successfully launch any instances of the app, make
+ * sure we return TRUE and don't set @error.
+ */
+ success = TRUE;
+ error = NULL;
+
+ /* Also, only set the output params on the first one */
+ ret_pid = NULL;
+ ret_stdin = ret_stdout = ret_stderr = NULL;
+ ret_startup_id = NULL;
+ }
+ }
+ while (docs && current_success);
+
+ out:
+ if (env)
+ {
+ g_ptr_array_foreach (env, (GFunc)g_free, NULL);
+ g_ptr_array_free (env, TRUE);
+ }
+ free_document_list (translated_documents);
+
+ return success;
+}
+
+/**
+ * egg_desktop_file_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @documents: a list of URIs or paths to documents to open
+ * @error: error pointer
+ * @...: additional options
+ *
+ * Launches @desktop_file with the given arguments. Additional options
+ * can be specified as follows:
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments)
+ * clears the environment in the child process
+ * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables)
+ * adds the NAME=VALUE strings in the given %NULL-terminated
+ * array to the child process's environment
+ * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen)
+ * causes the application to be launched on the given screen
+ * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace)
+ * causes the application to be launched on the given workspace
+ * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir)
+ * causes the application to be launched in the given directory
+ * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time)
+ * sets the "launch time" for the application. If the user
+ * interacts with another window after @launch_time but before
+ * the launched application creates its first window, the window
+ * manager may choose to not give focus to the new application.
+ * Passing 0 for @launch_time will explicitly request that the
+ * application not receive focus.
+ * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags)
+ * Sets additional #GSpawnFlags to use. See g_spawn_async() for
+ * more details.
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer)
+ * Sets the child setup callback and the data to pass to it.
+ * (See g_spawn_async() for more details.)
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid)
+ * On a successful launch, sets *@pid to the PID of the launched
+ * application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id)
+ * On a successful launch, sets *@startup_id to the Startup
+ * Notification "startup id" of the launched application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stdin.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stdout.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stderr.
+ *
+ * The options should be terminated with a single %NULL.
+ *
+ * If @documents contains multiple documents, but
+ * egg_desktop_file_accepts_multiple() returns %FALSE for
+ * @desktop_file, then egg_desktop_file_launch() will actually launch
+ * multiple instances of the application. In that case, the return
+ * value (as well as any values passed via
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the
+ * first instance of the application that was launched (but the
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each
+ * instance).
+ *
+ * Return value: %TRUE if the application was successfully launched.
+ **/
+gboolean
+egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents, GError **error,
+ ...)
+{
+ va_list args;
+ gboolean success;
+ EggDesktopFile *app_desktop_file;
+
+ switch (desktop_file->type)
+ {
+ case EGG_DESKTOP_FILE_TYPE_APPLICATION:
+ va_start (args, error);
+ success = egg_desktop_file_launchv (desktop_file, documents,
+ args, error);
+ va_end (args);
+ break;
+
+ case EGG_DESKTOP_FILE_TYPE_LINK:
+ if (documents)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Can't pass document URIs to a 'Type=Link' desktop entry"));
+ return FALSE;
+ }
+
+ if (!parse_link (desktop_file, &app_desktop_file, &documents, error))
+ return FALSE;
+
+ va_start (args, error);
+ success = egg_desktop_file_launchv (app_desktop_file, documents,
+ args, error);
+ va_end (args);
+
+ egg_desktop_file_free (app_desktop_file);
+ free_document_list (documents);
+ break;
+
+ case EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED:
+ case EGG_DESKTOP_FILE_TYPE_DIRECTORY:
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Not a launchable item"));
+ success = FALSE;
+ break;
+ }
+
+ return success;
+}
+
+
+GQuark
+egg_desktop_file_error_quark (void)
+{
+ return g_quark_from_static_string ("egg-desktop_file-error-quark");
+}
+
+
+G_LOCK_DEFINE_STATIC (egg_desktop_file);
+static EggDesktopFile *egg_desktop_file;
+
+static void
+egg_set_desktop_file_internal (const char *desktop_file_path,
+ gboolean set_defaults)
+{
+ GError *error = NULL;
+
+ G_LOCK (egg_desktop_file);
+ if (egg_desktop_file)
+ egg_desktop_file_free (egg_desktop_file);
+
+ egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error);
+ if (error)
+ {
+ g_warning ("Could not load desktop file '%s': %s",
+ desktop_file_path, error->message);
+ g_error_free (error);
+ }
+
+ if (set_defaults && egg_desktop_file != NULL) {
+ /* Set localized application name and default window icon */
+ if (egg_desktop_file->name)
+ g_set_application_name (egg_desktop_file->name);
+ if (egg_desktop_file->icon)
+ {
+ if (g_path_is_absolute (egg_desktop_file->icon))
+ gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL);
+ else
+ gtk_window_set_default_icon_name (egg_desktop_file->icon);
+ }
+ }
+
+ G_UNLOCK (egg_desktop_file);
+}
+
+/**
+ * egg_set_desktop_file:
+ * @desktop_file_path: path to the application's desktop file
+ *
+ * Creates an #EggDesktopFile for the application from the data at
+ * @desktop_file_path. This will also call g_set_application_name()
+ * with the localized application name from the desktop file, and
+ * gtk_window_set_default_icon_name() or
+ * gtk_window_set_default_icon_from_file() with the application's
+ * icon. Other code may use additional information from the desktop
+ * file.
+ * See egg_set_desktop_file_without_defaults() for a variant of this
+ * function that does not set the application name and default window
+ * icon.
+ *
+ * Note that for thread safety reasons, this function can only
+ * be called once, and is mutually exclusive with calling
+ * egg_set_desktop_file_without_defaults().
+ **/
+void
+egg_set_desktop_file (const char *desktop_file_path)
+{
+ egg_set_desktop_file_internal (desktop_file_path, TRUE);
+}
+
+/**
+ * egg_set_desktop_file_without_defaults:
+ * @desktop_file_path: path to the application's desktop file
+ *
+ * Creates an #EggDesktopFile for the application from the data at
+ * @desktop_file_path.
+ * See egg_set_desktop_file() for a variant of this function that
+ * sets the application name and default window icon from the information
+ * in the desktop file.
+ *
+ * Note that for thread safety reasons, this function can only
+ * be called once, and is mutually exclusive with calling
+ * egg_set_desktop_file().
+ **/
+void
+egg_set_desktop_file_without_defaults (const char *desktop_file_path)
+{
+ egg_set_desktop_file_internal (desktop_file_path, FALSE);
+}
+
+/**
+ * egg_get_desktop_file:
+ *
+ * Gets the application's #EggDesktopFile, as set by
+ * egg_set_desktop_file().
+ *
+ * Return value: the #EggDesktopFile, or %NULL if it hasn't been set.
+ **/
+EggDesktopFile *
+egg_get_desktop_file (void)
+{
+ EggDesktopFile *retval;
+
+ G_LOCK (egg_desktop_file);
+ retval = egg_desktop_file;
+ G_UNLOCK (egg_desktop_file);
+
+ return retval;
+}
diff --git a/pluma/smclient/eggdesktopfile.h b/pluma/smclient/eggdesktopfile.h
new file mode 100755
index 00000000..18fe4631
--- /dev/null
+++ b/pluma/smclient/eggdesktopfile.h
@@ -0,0 +1,160 @@
+/* eggdesktopfile.h - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library 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 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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 library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place -
+ * Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_DESKTOP_FILE_H__
+#define __EGG_DESKTOP_FILE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct EggDesktopFile EggDesktopFile;
+
+typedef enum {
+ EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED,
+
+ EGG_DESKTOP_FILE_TYPE_APPLICATION,
+ EGG_DESKTOP_FILE_TYPE_LINK,
+ EGG_DESKTOP_FILE_TYPE_DIRECTORY
+} EggDesktopFileType;
+
+EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path,
+ GError **error);
+
+EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error);
+EggDesktopFile *egg_desktop_file_new_from_dirs (const char *desktop_file_path,
+ const char **search_dirs,
+ GError **error);
+EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error);
+
+void egg_desktop_file_free (EggDesktopFile *desktop_file);
+
+const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file);
+
+EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file);
+
+const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file);
+const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file);
+
+gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment);
+
+gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file);
+
+char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error);
+
+gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef enum {
+ EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1,
+ EGG_DESKTOP_FILE_LAUNCH_PUTENV,
+ EGG_DESKTOP_FILE_LAUNCH_SCREEN,
+ EGG_DESKTOP_FILE_LAUNCH_WORKSPACE,
+ EGG_DESKTOP_FILE_LAUNCH_DIRECTORY,
+ EGG_DESKTOP_FILE_LAUNCH_TIME,
+ EGG_DESKTOP_FILE_LAUNCH_FLAGS,
+ EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_PID,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID
+} EggDesktopFileLaunchOption;
+
+/* Standard Keys */
+#define EGG_DESKTOP_FILE_GROUP "Desktop Entry"
+
+#define EGG_DESKTOP_FILE_KEY_TYPE "Type"
+#define EGG_DESKTOP_FILE_KEY_VERSION "Version"
+#define EGG_DESKTOP_FILE_KEY_NAME "Name"
+#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName"
+#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay"
+#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment"
+#define EGG_DESKTOP_FILE_KEY_ICON "Icon"
+#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden"
+#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn"
+#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn"
+#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec"
+#define EGG_DESKTOP_FILE_KEY_EXEC "Exec"
+#define EGG_DESKTOP_FILE_KEY_PATH "Path"
+#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal"
+#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType"
+#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass"
+#define EGG_DESKTOP_FILE_KEY_URL "URL"
+
+/* Accessors */
+gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char *egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error) G_GNUC_MALLOC;
+char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error) G_GNUC_MALLOC;
+gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+
+
+/* Errors */
+#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark()
+
+GQuark egg_desktop_file_error_quark (void);
+
+typedef enum {
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION
+} EggDesktopFileError;
+
+/* Global application desktop file */
+void egg_set_desktop_file (const char *desktop_file_path);
+void egg_set_desktop_file_without_defaults (const char *desktop_file_path);
+EggDesktopFile *egg_get_desktop_file (void);
+
+
+G_END_DECLS
+
+#endif /* __EGG_DESKTOP_FILE_H__ */
diff --git a/pluma/smclient/eggsmclient-osx.c b/pluma/smclient/eggsmclient-osx.c
new file mode 100755
index 00000000..7d3ff4b6
--- /dev/null
+++ b/pluma/smclient/eggsmclient-osx.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* EggSMClientOSX
+ *
+ * For details on the OS X logout process, see:
+ * http://developer.apple.com/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/BootProcess.html#//apple_ref/doc/uid/20002130-114618
+ *
+ * EggSMClientOSX registers for the kAEQuitApplication AppleEvent; the
+ * handler we register (quit_requested()) will be invoked from inside
+ * the quartz event-handling code (specifically, from inside
+ * [NSApplication nextEventMatchingMask]) when an AppleEvent arrives.
+ * We use AESuspendTheCurrentEvent() and AEResumeTheCurrentEvent() to
+ * allow asynchronous / non-main-loop-reentering processing of the
+ * quit request. (These are part of the Carbon framework; it doesn't
+ * seem to be possible to handle AppleEvents asynchronously from
+ * Cocoa.)
+ */
+
+#include "config.h"
+
+#include "eggsmclient-private.h"
+#include <glib.h>
+#include <Carbon/Carbon.h>
+#include <CoreServices/CoreServices.h>
+
+#define EGG_TYPE_SM_CLIENT_OSX (egg_sm_client_osx_get_type ())
+#define EGG_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSX))
+#define EGG_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass))
+#define EGG_IS_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_OSX))
+#define EGG_IS_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_OSX))
+#define EGG_SM_CLIENT_OSX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass))
+
+typedef struct _EggSMClientOSX EggSMClientOSX;
+typedef struct _EggSMClientOSXClass EggSMClientOSXClass;
+
+struct _EggSMClientOSX {
+ EggSMClient parent;
+
+ AppleEvent quit_event, quit_reply;
+ gboolean quit_requested, quitting;
+};
+
+struct _EggSMClientOSXClass
+{
+ EggSMClientClass parent_class;
+
+};
+
+static void sm_client_osx_startup (EggSMClient *client,
+ const char *client_id);
+static void sm_client_osx_will_quit (EggSMClient *client,
+ gboolean will_quit);
+static gboolean sm_client_osx_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+static pascal OSErr quit_requested (const AppleEvent *, AppleEvent *, long);
+
+G_DEFINE_TYPE (EggSMClientOSX, egg_sm_client_osx, EGG_TYPE_SM_CLIENT)
+
+static void
+egg_sm_client_osx_init (EggSMClientOSX *osx)
+{
+ ;
+}
+
+static void
+egg_sm_client_osx_class_init (EggSMClientOSXClass *klass)
+{
+ EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
+
+ sm_client_class->startup = sm_client_osx_startup;
+ sm_client_class->will_quit = sm_client_osx_will_quit;
+ sm_client_class->end_session = sm_client_osx_end_session;
+}
+
+EggSMClient *
+egg_sm_client_osx_new (void)
+{
+ return g_object_new (EGG_TYPE_SM_CLIENT_OSX, NULL);
+}
+
+static void
+sm_client_osx_startup (EggSMClient *client,
+ const char *client_id)
+{
+ AEInstallEventHandler (kCoreEventClass, kAEQuitApplication,
+ NewAEEventHandlerUPP (quit_requested),
+ (long)GPOINTER_TO_SIZE (client), false);
+}
+
+static gboolean
+idle_quit_requested (gpointer client)
+{
+ egg_sm_client_quit_requested (client);
+ return FALSE;
+}
+
+static pascal OSErr
+quit_requested (const AppleEvent *aevt, AppleEvent *reply, long refcon)
+{
+ EggSMClient *client = GSIZE_TO_POINTER ((gsize)refcon);
+ EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon);
+
+ g_return_val_if_fail (!osx->quit_requested, userCanceledErr);
+
+ /* FIXME AEInteractWithUser? */
+
+ osx->quit_requested = TRUE;
+ AEDuplicateDesc (aevt, &osx->quit_event);
+ AEDuplicateDesc (reply, &osx->quit_reply);
+ AESuspendTheCurrentEvent (aevt);
+
+ /* Don't emit the "quit_requested" signal immediately, since we're
+ * called from a weird point in the guts of gdkeventloop-quartz.c
+ */
+ g_idle_add (idle_quit_requested, client);
+ return noErr;
+}
+
+static pascal OSErr
+quit_requested_resumed (const AppleEvent *aevt, AppleEvent *reply, long refcon)
+{
+ EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon);
+
+ osx->quit_requested = FALSE;
+ return osx->quitting ? noErr : userCanceledErr;
+}
+
+static gboolean
+idle_will_quit (gpointer client)
+{
+ EggSMClientOSX *osx = (EggSMClientOSX *)client;
+
+ /* Resume the event with a new handler that will return a value to
+ * the system.
+ */
+ AEResumeTheCurrentEvent (&osx->quit_event, &osx->quit_reply,
+ NewAEEventHandlerUPP (quit_requested_resumed),
+ (long)GPOINTER_TO_SIZE (client));
+ AEDisposeDesc (&osx->quit_event);
+ AEDisposeDesc (&osx->quit_reply);
+
+ if (osx->quitting)
+ egg_sm_client_quit (client);
+ return FALSE;
+}
+
+static void
+sm_client_osx_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ EggSMClientOSX *osx = (EggSMClientOSX *)client;
+
+ g_return_if_fail (osx->quit_requested);
+
+ osx->quitting = will_quit;
+
+ /* Finish in an idle handler since the caller might have called
+ * egg_sm_client_will_quit() from inside the "quit_requested" signal
+ * handler, but may not expect the "quit" signal to arrive during
+ * the _will_quit() call.
+ */
+ g_idle_add (idle_will_quit, client);
+}
+
+static gboolean
+sm_client_osx_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ static const ProcessSerialNumber loginwindow_psn = { 0, kSystemProcess };
+ AppleEvent event = { typeNull, NULL }, reply = { typeNull, NULL };
+ AEAddressDesc target;
+ AEEventID id;
+ OSErr err;
+
+ switch (style)
+ {
+ case EGG_SM_CLIENT_END_SESSION_DEFAULT:
+ case EGG_SM_CLIENT_LOGOUT:
+ id = request_confirmation ? kAELogOut : kAEReallyLogOut;
+ break;
+ case EGG_SM_CLIENT_REBOOT:
+ id = request_confirmation ? kAEShowRestartDialog : kAERestart;
+ break;
+ case EGG_SM_CLIENT_SHUTDOWN:
+ id = request_confirmation ? kAEShowShutdownDialog : kAEShutDown;
+ break;
+ }
+
+ err = AECreateDesc (typeProcessSerialNumber, &loginwindow_psn,
+ sizeof (loginwindow_psn), &target);
+ if (err != noErr)
+ {
+ g_warning ("Could not create descriptor for loginwindow: %d", err);
+ return FALSE;
+ }
+
+ err = AECreateAppleEvent (kCoreEventClass, id, &target,
+ kAutoGenerateReturnID, kAnyTransactionID,
+ &event);
+ AEDisposeDesc (&target);
+ if (err != noErr)
+ {
+ g_warning ("Could not create logout AppleEvent: %d", err);
+ return FALSE;
+ }
+
+ err = AESend (&event, &reply, kAENoReply, kAENormalPriority,
+ kAEDefaultTimeout, NULL, NULL);
+ AEDisposeDesc (&event);
+ if (err == noErr)
+ AEDisposeDesc (&reply);
+
+ return err == noErr;
+}
diff --git a/pluma/smclient/eggsmclient-private.h b/pluma/smclient/eggsmclient-private.h
new file mode 100755
index 00000000..ccb10bfc
--- /dev/null
+++ b/pluma/smclient/eggsmclient-private.h
@@ -0,0 +1,53 @@
+/* eggsmclient-private.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_SM_CLIENT_PRIVATE_H__
+#define __EGG_SM_CLIENT_PRIVATE_H__
+
+#include <gdkconfig.h>
+#include "eggsmclient.h"
+
+G_BEGIN_DECLS
+
+GKeyFile *egg_sm_client_save_state (EggSMClient *client);
+void egg_sm_client_quit_requested (EggSMClient *client);
+void egg_sm_client_quit_cancelled (EggSMClient *client);
+void egg_sm_client_quit (EggSMClient *client);
+
+#if defined (GDK_WINDOWING_X11)
+# ifdef EGG_SM_CLIENT_BACKEND_XSMP
+GType egg_sm_client_xsmp_get_type (void);
+EggSMClient *egg_sm_client_xsmp_new (void);
+# endif
+# ifdef EGG_SM_CLIENT_BACKEND_DBUS
+GType egg_sm_client_dbus_get_type (void);
+EggSMClient *egg_sm_client_dbus_new (void);
+# endif
+#elif defined (GDK_WINDOWING_WIN32)
+GType egg_sm_client_win32_get_type (void);
+EggSMClient *egg_sm_client_win32_new (void);
+#elif defined (GDK_WINDOWING_QUARTZ)
+GType egg_sm_client_osx_get_type (void);
+EggSMClient *egg_sm_client_osx_new (void);
+#endif
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */
diff --git a/pluma/smclient/eggsmclient-win32.c b/pluma/smclient/eggsmclient-win32.c
new file mode 100755
index 00000000..91a25715
--- /dev/null
+++ b/pluma/smclient/eggsmclient-win32.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* EggSMClientWin32
+ *
+ * For details on the Windows XP logout process, see:
+ * http://msdn.microsoft.com/en-us/library/aa376876.aspx.
+ *
+ * Vista adds some new APIs which EggSMClient does not make use of; see
+ * http://msdn.microsoft.com/en-us/library/ms700677(VS.85).aspx
+ *
+ * When shutting down, Windows sends every top-level window a
+ * WM_QUERYENDSESSION event, which the application must respond to
+ * synchronously, saying whether or not it will quit. To avoid main
+ * loop re-entrancy problems (and to avoid having to muck about too
+ * much with the guts of the gdk-win32 main loop), we watch for this
+ * event in a separate thread, which then signals the main thread and
+ * waits for the main thread to handle the event. Since we don't want
+ * to require g_thread_init() to be called, we do this all using
+ * Windows-specific thread methods.
+ *
+ * After the application handles the WM_QUERYENDSESSION event,
+ * Windows then sends it a WM_ENDSESSION event with a TRUE or FALSE
+ * parameter indicating whether the session is or is not actually
+ * going to end now. We handle this from the other thread as well.
+ *
+ * As mentioned above, Vista introduces several additional new APIs
+ * that don't fit into the (current) EggSMClient API. Windows also has
+ * an entirely separate shutdown-notification scheme for non-GUI apps,
+ * which we also don't handle here.
+ */
+
+#include "config.h"
+
+#include "eggsmclient-private.h"
+#include <gdk/gdk.h>
+
+#define WIN32_LEAN_AND_MEAN
+#define UNICODE
+#include <windows.h>
+#include <process.h>
+
+#define EGG_TYPE_SM_CLIENT_WIN32 (egg_sm_client_win32_get_type ())
+#define EGG_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32))
+#define EGG_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class))
+#define EGG_IS_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_WIN32))
+#define EGG_IS_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_WIN32))
+#define EGG_SM_CLIENT_WIN32_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class))
+
+typedef struct _EggSMClientWin32 EggSMClientWin32;
+typedef struct _EggSMClientWin32Class EggSMClientWin32Class;
+
+struct _EggSMClientWin32 {
+ EggSMClient parent;
+
+ HANDLE message_event, response_event;
+
+ volatile GSourceFunc event;
+ volatile gboolean will_quit;
+};
+
+struct _EggSMClientWin32Class
+{
+ EggSMClientClass parent_class;
+
+};
+
+static void sm_client_win32_startup (EggSMClient *client,
+ const char *client_id);
+static void sm_client_win32_will_quit (EggSMClient *client,
+ gboolean will_quit);
+static gboolean sm_client_win32_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+static GSource *g_win32_handle_source_add (HANDLE handle, GSourceFunc callback,
+ gpointer user_data);
+static gboolean got_message (gpointer user_data);
+static void sm_client_thread (gpointer data);
+
+G_DEFINE_TYPE (EggSMClientWin32, egg_sm_client_win32, EGG_TYPE_SM_CLIENT)
+
+static void
+egg_sm_client_win32_init (EggSMClientWin32 *win32)
+{
+ ;
+}
+
+static void
+egg_sm_client_win32_class_init (EggSMClientWin32Class *klass)
+{
+ EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
+
+ sm_client_class->startup = sm_client_win32_startup;
+ sm_client_class->will_quit = sm_client_win32_will_quit;
+ sm_client_class->end_session = sm_client_win32_end_session;
+}
+
+EggSMClient *
+egg_sm_client_win32_new (void)
+{
+ return g_object_new (EGG_TYPE_SM_CLIENT_WIN32, NULL);
+}
+
+static void
+sm_client_win32_startup (EggSMClient *client,
+ const char *client_id)
+{
+ EggSMClientWin32 *win32 = (EggSMClientWin32 *)client;
+
+ win32->message_event = CreateEvent (NULL, FALSE, FALSE, NULL);
+ win32->response_event = CreateEvent (NULL, FALSE, FALSE, NULL);
+ g_win32_handle_source_add (win32->message_event, got_message, win32);
+ _beginthread (sm_client_thread, 0, client);
+}
+
+static void
+sm_client_win32_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ EggSMClientWin32 *win32 = (EggSMClientWin32 *)client;
+
+ win32->will_quit = will_quit;
+ SetEvent (win32->response_event);
+}
+
+static gboolean
+sm_client_win32_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ UINT uFlags = EWX_LOGOFF;
+
+ switch (style)
+ {
+ case EGG_SM_CLIENT_END_SESSION_DEFAULT:
+ case EGG_SM_CLIENT_LOGOUT:
+ uFlags = EWX_LOGOFF;
+ break;
+ case EGG_SM_CLIENT_REBOOT:
+ uFlags = EWX_REBOOT;
+ break;
+ case EGG_SM_CLIENT_SHUTDOWN:
+ uFlags = EWX_POWEROFF;
+ break;
+ }
+
+ /* There's no way to make ExitWindowsEx() show a logout dialog, so
+ * we ignore @request_confirmation.
+ */
+
+#ifdef SHTDN_REASON_FLAG_PLANNED
+ ExitWindowsEx (uFlags, SHTDN_REASON_FLAG_PLANNED);
+#else
+ ExitWindowsEx (uFlags, 0);
+#endif
+
+ return TRUE;
+}
+
+
+/* callbacks from logout-listener thread */
+
+static gboolean
+emit_quit_requested (gpointer smclient)
+{
+ gdk_threads_enter ();
+ egg_sm_client_quit_requested (smclient);
+ gdk_threads_leave ();
+
+ return FALSE;
+}
+
+static gboolean
+emit_quit (gpointer smclient)
+{
+ EggSMClientWin32 *win32 = smclient;
+
+ gdk_threads_enter ();
+ egg_sm_client_quit (smclient);
+ gdk_threads_leave ();
+
+ SetEvent (win32->response_event);
+ return FALSE;
+}
+
+static gboolean
+emit_quit_cancelled (gpointer smclient)
+{
+ EggSMClientWin32 *win32 = smclient;
+
+ gdk_threads_enter ();
+ egg_sm_client_quit_cancelled (smclient);
+ gdk_threads_leave ();
+
+ SetEvent (win32->response_event);
+ return FALSE;
+}
+
+static gboolean
+got_message (gpointer smclient)
+{
+ EggSMClientWin32 *win32 = smclient;
+
+ win32->event (win32);
+ return TRUE;
+}
+
+/* Windows HANDLE GSource */
+
+typedef struct {
+ GSource source;
+ GPollFD pollfd;
+} GWin32HandleSource;
+
+static gboolean
+g_win32_handle_source_prepare (GSource *source, gint *timeout)
+{
+ *timeout = -1;
+ return FALSE;
+}
+
+static gboolean
+g_win32_handle_source_check (GSource *source)
+{
+ GWin32HandleSource *hsource = (GWin32HandleSource *)source;
+
+ return hsource->pollfd.revents;
+}
+
+static gboolean
+g_win32_handle_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
+{
+ return (*callback) (user_data);
+}
+
+static void
+g_win32_handle_source_finalize (GSource *source)
+{
+ ;
+}
+
+GSourceFuncs g_win32_handle_source_funcs = {
+ g_win32_handle_source_prepare,
+ g_win32_handle_source_check,
+ g_win32_handle_source_dispatch,
+ g_win32_handle_source_finalize
+};
+
+static GSource *
+g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, gpointer user_data)
+{
+ GWin32HandleSource *hsource;
+ GSource *source;
+
+ source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource));
+ hsource = (GWin32HandleSource *)source;
+ hsource->pollfd.fd = (int)handle;
+ hsource->pollfd.events = G_IO_IN;
+ hsource->pollfd.revents = 0;
+ g_source_add_poll (source, &hsource->pollfd);
+
+ g_source_set_callback (source, callback, user_data, NULL);
+ g_source_attach (source, NULL);
+ return source;
+}
+
+/* logout-listener thread */
+
+LRESULT CALLBACK
+sm_client_win32_window_procedure (HWND hwnd,
+ UINT message,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ EggSMClientWin32 *win32 =
+ (EggSMClientWin32 *)GetWindowLongPtr (hwnd, GWLP_USERDATA);
+
+ switch (message)
+ {
+ case WM_QUERYENDSESSION:
+ win32->event = emit_quit_requested;
+ SetEvent (win32->message_event);
+
+ WaitForSingleObject (win32->response_event, INFINITE);
+ return win32->will_quit;
+
+ case WM_ENDSESSION:
+ if (wParam)
+ {
+ /* The session is ending */
+ win32->event = emit_quit;
+ }
+ else
+ {
+ /* Nope, the session *isn't* ending */
+ win32->event = emit_quit_cancelled;
+ }
+
+ SetEvent (win32->message_event);
+ WaitForSingleObject (win32->response_event, INFINITE);
+
+ return 0;
+
+ default:
+ return DefWindowProc (hwnd, message, wParam, lParam);
+ }
+}
+
+static void
+sm_client_thread (gpointer smclient)
+{
+ HINSTANCE instance;
+ WNDCLASSEXW wcl;
+ ATOM klass;
+ HWND window;
+ MSG msg;
+
+ instance = GetModuleHandle (NULL);
+
+ memset (&wcl, 0, sizeof (WNDCLASSEX));
+ wcl.cbSize = sizeof (WNDCLASSEX);
+ wcl.lpfnWndProc = sm_client_win32_window_procedure;
+ wcl.hInstance = instance;
+ wcl.lpszClassName = L"EggSmClientWindow";
+ klass = RegisterClassEx (&wcl);
+
+ window = CreateWindowEx (0, MAKEINTRESOURCE (klass),
+ L"EggSmClientWindow", 0,
+ 10, 10, 50, 50, GetDesktopWindow (),
+ NULL, instance, NULL);
+ SetWindowLongPtr (window, GWLP_USERDATA, (LONG_PTR)smclient);
+
+ /* main loop */
+ while (GetMessage (&msg, NULL, 0, 0))
+ DispatchMessage (&msg);
+}
diff --git a/pluma/smclient/eggsmclient-xsmp.c b/pluma/smclient/eggsmclient-xsmp.c
new file mode 100755
index 00000000..a6d3f11f
--- /dev/null
+++ b/pluma/smclient/eggsmclient-xsmp.c
@@ -0,0 +1,1370 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Inspired by various other pieces of code including GsmClient (C)
+ * 2001 Havoc Pennington, MateClient (C) 1998 Carsten Schaar, and twm
+ * session code (C) 1998 The Open Group.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+#include "eggdesktopfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <X11/SM/SMlib.h>
+
+#include <gdk/gdk.h>
+
+#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ())
+#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP))
+#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+
+typedef struct _EggSMClientXSMP EggSMClientXSMP;
+typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass;
+
+/* These mostly correspond to the similarly-named states in section
+ * 9.1 of the XSMP spec. Some of the states there aren't represented
+ * here, because we don't need them. SHUTDOWN_CANCELLED is slightly
+ * different from the spec; we use it when the client is IDLE after a
+ * ShutdownCancelled message, but the application is still interacting
+ * and doesn't know the shutdown has been cancelled yet.
+ */
+typedef enum
+{
+ XSMP_STATE_IDLE,
+ XSMP_STATE_SAVE_YOURSELF,
+ XSMP_STATE_INTERACT_REQUEST,
+ XSMP_STATE_INTERACT,
+ XSMP_STATE_SAVE_YOURSELF_DONE,
+ XSMP_STATE_SHUTDOWN_CANCELLED,
+ XSMP_STATE_CONNECTION_CLOSED
+} EggSMClientXSMPState;
+
+static const char *state_names[] = {
+ "idle",
+ "save-yourself",
+ "interact-request",
+ "interact",
+ "save-yourself-done",
+ "shutdown-cancelled",
+ "connection-closed"
+};
+
+#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state])
+
+struct _EggSMClientXSMP
+{
+ EggSMClient parent;
+
+ SmcConn connection;
+ char *client_id;
+
+ EggSMClientXSMPState state;
+ char **restart_command;
+ gboolean set_restart_command;
+ int restart_style;
+
+ guint idle;
+
+ /* Current SaveYourself state */
+ guint expecting_initial_save_yourself : 1;
+ guint need_save_state : 1;
+ guint need_quit_requested : 1;
+ guint interact_errors : 1;
+ guint shutting_down : 1;
+
+ /* Todo list */
+ guint waiting_to_set_initial_properties : 1;
+ guint waiting_to_emit_quit : 1;
+ guint waiting_to_emit_quit_cancelled : 1;
+ guint waiting_to_save_myself : 1;
+
+};
+
+struct _EggSMClientXSMPClass
+{
+ EggSMClientClass parent_class;
+
+};
+
+static void sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id);
+static void sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+static void sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit);
+static gboolean sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+static void xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast);
+static void xsmp_die (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data);
+
+static SmProp *array_prop (const char *name,
+ ...);
+static SmProp *ptrarray_prop (const char *name,
+ GPtrArray *values);
+static SmProp *string_prop (const char *name,
+ const char *value);
+static SmProp *card8_prop (const char *name,
+ unsigned char value);
+
+static void set_properties (EggSMClientXSMP *xsmp, ...);
+static void delete_properties (EggSMClientXSMP *xsmp, ...);
+
+static GPtrArray *generate_command (char **restart_command,
+ const char *client_id,
+ const char *state_file);
+
+static void save_state (EggSMClientXSMP *xsmp);
+static void do_save_yourself (EggSMClientXSMP *xsmp);
+static void update_pending_events (EggSMClientXSMP *xsmp);
+
+static void ice_init (void);
+static gboolean process_ice_messages (IceConn ice_conn);
+static void smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values);
+
+G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT)
+
+static void
+egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp)
+{
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ xsmp->connection = NULL;
+ xsmp->restart_style = SmRestartIfRunning;
+}
+
+static void
+egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass)
+{
+ EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
+
+ sm_client_class->startup = sm_client_xsmp_startup;
+ sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command;
+ sm_client_class->will_quit = sm_client_xsmp_will_quit;
+ sm_client_class->end_session = sm_client_xsmp_end_session;
+}
+
+EggSMClient *
+egg_sm_client_xsmp_new (void)
+{
+ if (!g_getenv ("SESSION_MANAGER"))
+ return NULL;
+
+ return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL);
+}
+
+static gboolean
+sm_client_xsmp_set_initial_properties (gpointer user_data)
+{
+ EggSMClientXSMP *xsmp = user_data;
+ EggDesktopFile *desktop_file;
+ GPtrArray *clone, *restart;
+ char pid_str[64];
+
+ if (xsmp->idle)
+ {
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+ }
+ xsmp->waiting_to_set_initial_properties = FALSE;
+
+ if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART)
+ xsmp->restart_style = SmRestartNever;
+
+ /* Parse info out of desktop file */
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GError *err = NULL;
+ char *cmdline, **argv;
+ int argc;
+
+ if (xsmp->restart_style == SmRestartIfRunning)
+ {
+ if (egg_desktop_file_get_boolean (desktop_file,
+ "X-MATE-AutoRestart", NULL))
+ xsmp->restart_style = SmRestartImmediately;
+ }
+
+ if (!xsmp->set_restart_command)
+ {
+ cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err);
+ if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err))
+ {
+ egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp),
+ argc, (const char **)argv);
+ g_strfreev (argv);
+ }
+ else
+ {
+ g_warning ("Could not parse Exec line in desktop file: %s",
+ err->message);
+ g_error_free (err);
+ }
+ g_free (cmdline);
+ }
+ }
+
+ if (!xsmp->set_restart_command)
+ xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1);
+
+ clone = generate_command (xsmp->restart_command, NULL, NULL);
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+
+ g_debug ("Setting initial properties");
+
+ /* Program, CloneCommand, RestartCommand, and UserID are required.
+ * ProcessID isn't required, but the SM may be able to do something
+ * useful with it.
+ */
+ g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ());
+ set_properties (xsmp,
+ string_prop (SmProgram, g_get_prgname ()),
+ ptrarray_prop (SmCloneCommand, clone),
+ ptrarray_prop (SmRestartCommand, restart),
+ string_prop (SmUserID, g_get_user_name ()),
+ string_prop (SmProcessID, pid_str),
+ card8_prop (SmRestartStyleHint, xsmp->restart_style),
+ NULL);
+ g_ptr_array_free (clone, TRUE);
+ g_ptr_array_free (restart, TRUE);
+
+ if (desktop_file)
+ {
+ set_properties (xsmp,
+ string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)),
+ NULL);
+ }
+
+ update_pending_events (xsmp);
+ return FALSE;
+}
+
+/* This gets called from two different places: xsmp_die() (when the
+ * server asks us to disconnect) and process_ice_messages() (when the
+ * server disconnects unexpectedly).
+ */
+static void
+sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp)
+{
+ SmcConn connection;
+
+ if (!xsmp->connection)
+ return;
+
+ g_debug ("Disconnecting");
+
+ connection = xsmp->connection;
+ xsmp->connection = NULL;
+ SmcCloseConnection (connection, 0, NULL);
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+}
+
+static void
+sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ SmcCallbacks callbacks;
+ char *ret_client_id;
+ char error_string_ret[256];
+
+ xsmp->client_id = g_strdup (client_id);
+
+ ice_init ();
+ SmcSetErrorHandler (smc_error_handler);
+
+ callbacks.save_yourself.callback = xsmp_save_yourself;
+ callbacks.die.callback = xsmp_die;
+ callbacks.save_complete.callback = xsmp_save_complete;
+ callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled;
+
+ callbacks.save_yourself.client_data = xsmp;
+ callbacks.die.client_data = xsmp;
+ callbacks.save_complete.client_data = xsmp;
+ callbacks.shutdown_cancelled.client_data = xsmp;
+
+ client_id = NULL;
+ error_string_ret[0] = '\0';
+ xsmp->connection =
+ SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor,
+ SmcSaveYourselfProcMask | SmcDieProcMask |
+ SmcSaveCompleteProcMask |
+ SmcShutdownCancelledProcMask,
+ &callbacks,
+ xsmp->client_id, &ret_client_id,
+ sizeof (error_string_ret), error_string_ret);
+
+ if (!xsmp->connection)
+ {
+ g_warning ("Failed to connect to the session manager: %s\n",
+ error_string_ret[0] ?
+ error_string_ret : "no error message given");
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ return;
+ }
+
+ /* We expect a pointless initial SaveYourself if either (a) we
+ * didn't have an initial client ID, or (b) we DID have an initial
+ * client ID, but the server rejected it and gave us a new one.
+ */
+ if (!xsmp->client_id ||
+ (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0))
+ xsmp->expecting_initial_save_yourself = TRUE;
+
+ if (ret_client_id)
+ {
+ g_free (xsmp->client_id);
+ xsmp->client_id = g_strdup (ret_client_id);
+ free (ret_client_id);
+
+ gdk_threads_enter ();
+ gdk_set_sm_client_id (xsmp->client_id);
+ gdk_threads_leave ();
+
+ g_debug ("Got client ID \"%s\"", xsmp->client_id);
+ }
+
+ xsmp->state = XSMP_STATE_IDLE;
+
+ /* Do not set the initial properties until we reach the main loop,
+ * so that the application has a chance to call
+ * egg_set_desktop_file(). (This may also help the session manager
+ * have a better idea of when the application is fully up and
+ * running.)
+ */
+ xsmp->waiting_to_set_initial_properties = TRUE;
+ xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client);
+}
+
+static void
+sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int i;
+
+ g_strfreev (xsmp->restart_command);
+
+ xsmp->restart_command = g_new (char *, argc + 1);
+ for (i = 0; i < argc; i++)
+ xsmp->restart_command[i] = g_strdup (argv[i]);
+ xsmp->restart_command[i] = NULL;
+
+ xsmp->set_restart_command = TRUE;
+}
+
+static void
+sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+
+ if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED)
+ {
+ /* The session manager has already exited! Schedule a quit
+ * signal.
+ */
+ xsmp->waiting_to_emit_quit = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* We received a ShutdownCancelled message while the application
+ * was interacting; Schedule a quit_cancelled signal.
+ */
+ xsmp->waiting_to_emit_quit_cancelled = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT);
+
+ g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True");
+ SmcInteractDone (xsmp->connection, !will_quit);
+
+ if (will_quit && xsmp->need_save_state)
+ save_state (xsmp);
+
+ g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False");
+ SmcSaveYourselfDone (xsmp->connection, will_quit);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static gboolean
+sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int save_type;
+
+ /* To end the session via XSMP, we have to send a
+ * SaveYourselfRequest. We aren't allowed to do that if anything
+ * else is going on, but we don't want to expose this fact to the
+ * application. So we do our best to patch things up here...
+ *
+ * In the worst case, this method might block for some length of
+ * time in process_ice_messages, but the only time that code path is
+ * honestly likely to get hit is if the application tries to end the
+ * session as the very first thing it does, in which case it
+ * probably won't actually block anyway. It's not worth gunking up
+ * the API to try to deal nicely with the other 0.01% of cases where
+ * this happens.
+ */
+
+ while (xsmp->state != XSMP_STATE_IDLE ||
+ xsmp->expecting_initial_save_yourself)
+ {
+ /* If we're already shutting down, we don't need to do anything. */
+ if (xsmp->shutting_down)
+ return TRUE;
+
+ switch (xsmp->state)
+ {
+ case XSMP_STATE_CONNECTION_CLOSED:
+ return FALSE;
+
+ case XSMP_STATE_SAVE_YOURSELF:
+ /* Trying to log out from the save_state callback? Whatever.
+ * Abort the save_state.
+ */
+ SmcSaveYourselfDone (xsmp->connection, FALSE);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ break;
+
+ case XSMP_STATE_INTERACT_REQUEST:
+ case XSMP_STATE_INTERACT:
+ case XSMP_STATE_SHUTDOWN_CANCELLED:
+ /* Already in a shutdown-related state, just ignore
+ * the new shutdown request...
+ */
+ return TRUE;
+
+ case XSMP_STATE_IDLE:
+ if (xsmp->waiting_to_set_initial_properties)
+ sm_client_xsmp_set_initial_properties (xsmp);
+
+ if (!xsmp->expecting_initial_save_yourself)
+ break;
+ /* else fall through */
+
+ case XSMP_STATE_SAVE_YOURSELF_DONE:
+ /* We need to wait for some response from the server.*/
+ process_ice_messages (SmcGetIceConnection (xsmp->connection));
+ break;
+
+ default:
+ /* Hm... shouldn't happen */
+ return FALSE;
+ }
+ }
+
+ /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and
+ * the user chooses to save the session. But mate-session will do
+ * the wrong thing if we pass SmSaveBoth and the user chooses NOT to
+ * save the session... Sigh.
+ */
+ if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session"))
+ save_type = SmSaveBoth;
+ else
+ save_type = SmSaveGlobal;
+
+ g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : "");
+ SmcRequestSaveYourself (xsmp->connection,
+ save_type,
+ True, /* shutdown */
+ SmInteractStyleAny,
+ !request_confirmation, /* fast */
+ True /* global */);
+ return TRUE;
+}
+
+static gboolean
+idle_do_pending_events (gpointer data)
+{
+ EggSMClientXSMP *xsmp = data;
+ EggSMClient *client = data;
+
+ gdk_threads_enter ();
+
+ xsmp->idle = 0;
+
+ if (xsmp->waiting_to_emit_quit)
+ {
+ xsmp->waiting_to_emit_quit = FALSE;
+ egg_sm_client_quit (client);
+ goto out;
+ }
+
+ if (xsmp->waiting_to_emit_quit_cancelled)
+ {
+ xsmp->waiting_to_emit_quit_cancelled = FALSE;
+ egg_sm_client_quit_cancelled (client);
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+
+ if (xsmp->waiting_to_save_myself)
+ {
+ xsmp->waiting_to_save_myself = FALSE;
+ do_save_yourself (xsmp);
+ }
+
+ out:
+ gdk_threads_leave ();
+ return FALSE;
+}
+
+static void
+update_pending_events (EggSMClientXSMP *xsmp)
+{
+ gboolean want_idle =
+ xsmp->waiting_to_emit_quit ||
+ xsmp->waiting_to_emit_quit_cancelled ||
+ xsmp->waiting_to_save_myself;
+
+ if (want_idle)
+ {
+ if (xsmp->idle == 0)
+ xsmp->idle = g_idle_add (idle_do_pending_events, xsmp);
+ }
+ else
+ {
+ if (xsmp->idle != 0)
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+ }
+}
+
+static void
+fix_broken_state (EggSMClientXSMP *xsmp, const char *message,
+ gboolean send_interact_done,
+ gboolean send_save_yourself_done)
+{
+ g_warning ("Received XSMP %s message in state %s: client or server error",
+ message, EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ /* Forget any pending SaveYourself plans we had */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+
+ if (send_interact_done)
+ SmcInteractDone (xsmp->connection, False);
+ if (send_save_yourself_done)
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE;
+}
+
+/* SM callbacks */
+
+static void
+xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ gboolean wants_quit_requested;
+
+ g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s",
+ save_type == SmSaveLocal ? "SmSaveLocal" :
+ save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
+ shutdown ? "Shutdown" : "!Shutdown",
+ interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
+ interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
+ "SmInteractStyleNone", fast ? "Fast" : "!Fast",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_IDLE &&
+ xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE);
+ return;
+ }
+
+ if (xsmp->waiting_to_set_initial_properties)
+ sm_client_xsmp_set_initial_properties (xsmp);
+
+ /* If this is the initial SaveYourself, ignore it; we've already set
+ * properties and there's no reason to actually save state too.
+ */
+ if (xsmp->expecting_initial_save_yourself)
+ {
+ xsmp->expecting_initial_save_yourself = FALSE;
+
+ if (save_type == SmSaveLocal &&
+ interact_style == SmInteractStyleNone &&
+ !shutdown && !fast)
+ {
+ g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself");
+ SmcSaveYourselfDone (xsmp->connection, True);
+ /* As explained in the comment at the end of
+ * do_save_yourself(), SAVE_YOURSELF_DONE is the correct
+ * state here, not IDLE.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ return;
+ }
+ else
+ g_warning ("First SaveYourself was not the expected one!");
+ }
+
+ /* Even ignoring the "fast" flag completely, there are still 18
+ * different combinations of save_type, shutdown and interact_style.
+ * We interpret them as follows:
+ *
+ * Type Shutdown Interact Interpretation
+ * G F A/E/N do nothing (1)
+ * G T N do nothing (1)*
+ * G T A/E quit_requested (2)
+ * L/B F A/E/N save_state (3)
+ * L/B T N save_state (3)*
+ * L/B T A/E quit_requested, then save_state (4)
+ *
+ * 1. Do nothing, because the SM asked us to do something
+ * uninteresting (save open files, but then don't quit
+ * afterward) or rude (save open files without asking the user
+ * for confirmation).
+ *
+ * 2. Request interaction and then emit ::quit_requested. This
+ * perhaps isn't quite correct for the SmInteractStyleErrors
+ * case, but we don't care.
+ *
+ * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these
+ * rows essentially get demoted to SmSaveLocal, because their
+ * Global halves correspond to "do nothing".
+ *
+ * 4. Request interaction, emit ::quit_requested, and then emit
+ * ::save_state after interacting. This is the SmSaveBoth
+ * equivalent of #2, but we also promote SmSaveLocal shutdown
+ * SaveYourselfs to SmSaveBoth here, because we want to give
+ * the user a chance to save open files before quitting.
+ *
+ * (* It would be nice if we could do something useful when the
+ * session manager sends a SaveYourself with shutdown True and
+ * SmInteractStyleNone. But we can't, so we just pretend it didn't
+ * even tell us it was shutting down. The docs for ::quit mention
+ * that it might not always be preceded by ::quit_requested.)
+ */
+
+ /* As an optimization, we don't actually request interaction and
+ * emit ::quit_requested if the application isn't listening to the
+ * signal.
+ */
+ wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE);
+
+ xsmp->need_save_state = (save_type != SmSaveGlobal);
+ xsmp->need_quit_requested = (shutdown && wants_quit_requested &&
+ interact_style != SmInteractStyleNone);
+ xsmp->interact_errors = (interact_style == SmInteractStyleErrors);
+
+ xsmp->shutting_down = shutdown;
+
+ do_save_yourself (xsmp);
+}
+
+static void
+do_save_yourself (EggSMClientXSMP *xsmp)
+{
+ if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* The SM cancelled a previous SaveYourself, but we haven't yet
+ * had a chance to tell the application, so we can't start
+ * processing this SaveYourself yet.
+ */
+ xsmp->waiting_to_save_myself = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ if (xsmp->need_quit_requested)
+ {
+ xsmp->state = XSMP_STATE_INTERACT_REQUEST;
+
+ g_debug ("Sending InteractRequest(%s)",
+ xsmp->interact_errors ? "Error" : "Normal");
+ SmcInteractRequest (xsmp->connection,
+ xsmp->interact_errors ? SmDialogError : SmDialogNormal,
+ xsmp_interact,
+ xsmp);
+ return;
+ }
+
+ if (xsmp->need_save_state)
+ {
+ save_state (xsmp);
+
+ /* Though unlikely, the client could have been disconnected
+ * while the application was saving its state.
+ */
+ if (!xsmp->connection)
+ return;
+ }
+
+ g_debug ("Sending SaveYourselfDone(True)");
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ /* The client state diagram in the XSMP spec says that after a
+ * non-shutdown SaveYourself, we go directly back to "idle". But
+ * everything else in both the XSMP spec and the libSM docs
+ * disagrees.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static void
+save_state (EggSMClientXSMP *xsmp)
+{
+ GKeyFile *state_file;
+ char *state_file_path, *data;
+ EggDesktopFile *desktop_file;
+ GPtrArray *restart;
+ int offset, fd;
+
+ /* We set xsmp->state before emitting save_state, but our caller is
+ * responsible for setting it back afterward.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF;
+
+ state_file = egg_sm_client_save_state ((EggSMClient *)xsmp);
+ if (!state_file)
+ {
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+ delete_properties (xsmp, SmDiscardCommand, NULL);
+ return;
+ }
+
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GKeyFile *merged_file;
+ char *desktop_file_path;
+
+ merged_file = g_key_file_new ();
+ desktop_file_path =
+ g_filename_from_uri (egg_desktop_file_get_source (desktop_file),
+ NULL, NULL);
+ if (desktop_file_path &&
+ g_key_file_load_from_file (merged_file, desktop_file_path,
+ G_KEY_FILE_KEEP_COMMENTS |
+ G_KEY_FILE_KEEP_TRANSLATIONS, NULL))
+ {
+ guint g, k, i;
+ char **groups, **keys, *value, *exec;
+
+ groups = g_key_file_get_groups (state_file, NULL);
+ for (g = 0; groups[g]; g++)
+ {
+ keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL);
+ for (k = 0; keys[k]; k++)
+ {
+ value = g_key_file_get_value (state_file, groups[g],
+ keys[k], NULL);
+ if (value)
+ {
+ g_key_file_set_value (merged_file, groups[g],
+ keys[k], value);
+ g_free (value);
+ }
+ }
+ g_strfreev (keys);
+ }
+ g_strfreev (groups);
+
+ g_key_file_free (state_file);
+ state_file = merged_file;
+
+ /* Update Exec key using "--sm-client-state-file %k" */
+ restart = generate_command (xsmp->restart_command,
+ NULL, "%k");
+ for (i = 0; i < restart->len; i++)
+ restart->pdata[i] = g_shell_quote (restart->pdata[i]);
+ g_ptr_array_add (restart, NULL);
+ exec = g_strjoinv (" ", (char **)restart->pdata);
+ g_strfreev ((char **)restart->pdata);
+ g_ptr_array_free (restart, FALSE);
+
+ g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ exec);
+ g_free (exec);
+ }
+ else
+ desktop_file = NULL;
+
+ g_free (desktop_file_path);
+ }
+
+ /* Now write state_file to disk. (We can't use mktemp(), because
+ * that requires the filename to end with "XXXXXX", and we want
+ * it to end with ".desktop".)
+ */
+
+ data = g_key_file_to_data (state_file, NULL, NULL);
+ g_key_file_free (state_file);
+
+ offset = 0;
+ while (1)
+ {
+ state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s",
+ g_get_user_config_dir (),
+ G_DIR_SEPARATOR, G_DIR_SEPARATOR,
+ g_get_prgname (),
+ (long)time (NULL) + offset,
+ desktop_file ? "desktop" : "state");
+
+ fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
+ if (fd == -1)
+ {
+ if (errno == EEXIST)
+ {
+ offset++;
+ g_free (state_file_path);
+ continue;
+ }
+ else if (errno == ENOTDIR || errno == ENOENT)
+ {
+ char *sep = strrchr (state_file_path, G_DIR_SEPARATOR);
+
+ *sep = '\0';
+ if (g_mkdir_with_parents (state_file_path, 0755) != 0)
+ {
+ g_warning ("Could not create directory '%s'",
+ state_file_path);
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ continue;
+ }
+
+ g_warning ("Could not create file '%s': %s",
+ state_file_path, g_strerror (errno));
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ close (fd);
+ g_file_set_contents (state_file_path, data, -1, NULL);
+ break;
+ }
+ g_free (data);
+
+ restart = generate_command (xsmp->restart_command, xsmp->client_id,
+ state_file_path);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+
+ if (state_file_path)
+ {
+ set_properties (xsmp,
+ array_prop (SmDiscardCommand,
+ "/bin/rm", "-rf", state_file_path,
+ NULL),
+ NULL);
+ g_free (state_file_path);
+ }
+}
+
+static void
+xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Interact message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_INTERACT_REQUEST)
+ {
+ fix_broken_state (xsmp, "Interact", TRUE, TRUE);
+ return;
+ }
+
+ xsmp->state = XSMP_STATE_INTERACT;
+ egg_sm_client_quit_requested (client);
+}
+
+static void
+xsmp_die (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Die message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ sm_client_xsmp_disconnect (xsmp);
+ egg_sm_client_quit (client);
+}
+
+static void
+xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+
+ g_debug ("Received SaveComplete message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ xsmp->state = XSMP_STATE_IDLE;
+ else
+ fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE);
+}
+
+static void
+xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received ShutdownCancelled message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ xsmp->shutting_down = FALSE;
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ {
+ /* We've finished interacting and now the SM has agreed to
+ * cancel the shutdown.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ egg_sm_client_quit_cancelled (client);
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* Hm... ok, so we got a shutdown SaveYourself, which got
+ * cancelled, but the application was still interacting, so we
+ * didn't tell it yet, and then *another* SaveYourself arrived,
+ * which we must still be waiting to tell the app about, except
+ * that now that SaveYourself has been cancelled too! Dizzy yet?
+ */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+ }
+ else
+ {
+ g_debug ("Sending SaveYourselfDone(False)");
+ SmcSaveYourselfDone (xsmp->connection, False);
+
+ if (xsmp->state == XSMP_STATE_INTERACT)
+ {
+ /* The application is currently interacting, so we can't
+ * tell it about the cancellation yet; we will wait until
+ * after it calls egg_sm_client_will_quit().
+ */
+ xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED;
+ }
+ else
+ {
+ /* The shutdown was cancelled before the application got a
+ * chance to interact.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+ }
+}
+
+/* Utilities */
+
+/* Create a restart/clone/Exec command based on @restart_command.
+ * If @client_id is non-%NULL, add "--sm-client-id @client_id".
+ * If @state_file is non-%NULL, add "--sm-client-state-file @state_file".
+ *
+ * None of the input strings are g_strdup()ed; the caller must keep
+ * them around until it is done with the returned GPtrArray, and must
+ * then free the array, but not its contents.
+ */
+static GPtrArray *
+generate_command (char **restart_command, const char *client_id,
+ const char *state_file)
+{
+ GPtrArray *cmd;
+ int i;
+
+ cmd = g_ptr_array_new ();
+ g_ptr_array_add (cmd, restart_command[0]);
+
+ if (client_id)
+ {
+ g_ptr_array_add (cmd, (char *)"--sm-client-id");
+ g_ptr_array_add (cmd, (char *)client_id);
+ }
+
+ if (state_file)
+ {
+ g_ptr_array_add (cmd, (char *)"--sm-client-state-file");
+ g_ptr_array_add (cmd, (char *)state_file);
+ }
+
+ for (i = 1; restart_command[i]; i++)
+ g_ptr_array_add (cmd, restart_command[i]);
+
+ return cmd;
+}
+
+/* Takes a NULL-terminated list of SmProp * values, created by
+ * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and
+ * frees them.
+ */
+static void
+set_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ SmProp *prop;
+ va_list ap;
+ guint i;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, SmProp *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ if (xsmp->connection)
+ {
+ SmcSetProperties (xsmp->connection, props->len,
+ (SmProp **)props->pdata);
+ }
+
+ for (i = 0; i < props->len; i++)
+ {
+ prop = props->pdata[i];
+ g_free (prop->vals);
+ g_free (prop);
+ }
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes a NULL-terminated list of property names and deletes them. */
+static void
+delete_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ char *prop;
+ va_list ap;
+
+ if (!xsmp->connection)
+ return;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, char *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ SmcDeleteProperties (xsmp->connection, props->len,
+ (char **)props->pdata);
+
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes an array of strings and creates a LISTofARRAY8 property. The
+ * strings are neither dupped nor freed; they need to remain valid
+ * until you're done with the SmProp.
+ */
+static SmProp *
+array_prop (const char *name, ...)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ char *value;
+ va_list ap;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = (char *)SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ va_start (ap, name);
+ while ((value = va_arg (ap, char *)))
+ {
+ pv.length = strlen (value);
+ pv.value = value;
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property.
+ * The array contents are neither dupped nor freed; they need to
+ * remain valid until you're done with the SmProp.
+ */
+static SmProp *
+ptrarray_prop (const char *name, GPtrArray *values)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ guint i;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = (char *)SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ for (i = 0; i < values->len; i++)
+ {
+ pv.length = strlen (values->pdata[i]);
+ pv.value = values->pdata[i];
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a string and creates an ARRAY8 property. The string is
+ * neither dupped nor freed; it needs to remain valid until you're
+ * done with the SmProp.
+ */
+static SmProp *
+string_prop (const char *name, const char *value)
+{
+ SmProp *prop;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = (char *)SmARRAY8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 1);
+
+ prop->vals[0].length = strlen (value);
+ prop->vals[0].value = (char *)value;
+
+ return prop;
+}
+
+/* Takes a char and creates a CARD8 property. */
+static SmProp *
+card8_prop (const char *name, unsigned char value)
+{
+ SmProp *prop;
+ char *card8val;
+
+ /* To avoid having to allocate and free prop->vals[0], we cheat and
+ * make vals a 2-element-long array and then use the second element
+ * to store value.
+ */
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = (char *)SmCARD8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 2);
+ card8val = (char *)(&prop->vals[1]);
+ card8val[0] = value;
+
+ prop->vals[0].length = 1;
+ prop->vals[0].value = card8val;
+
+ return prop;
+}
+
+/* ICE code. This makes no effort to play nice with anyone else trying
+ * to use libICE. Fortunately, no one uses libICE for anything other
+ * than SM. (DCOP uses ICE, but it has its own private copy of
+ * libICE.)
+ *
+ * When this moves to gtk, it will need to be cleverer, to avoid
+ * tripping over old apps that use MateClient or that use libSM
+ * directly.
+ */
+
+#include <X11/ICE/ICElib.h>
+#include <fcntl.h>
+
+static void ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values);
+static void ice_io_error_handler (IceConn ice_conn);
+static void ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data);
+
+static void
+ice_init (void)
+{
+ IceSetIOErrorHandler (ice_io_error_handler);
+ IceSetErrorHandler (ice_error_handler);
+ IceAddConnectionWatch (ice_connection_watch, NULL);
+}
+
+static gboolean
+process_ice_messages (IceConn ice_conn)
+{
+ IceProcessMessagesStatus status;
+
+ gdk_threads_enter ();
+ status = IceProcessMessages (ice_conn, NULL, NULL);
+ gdk_threads_leave ();
+
+ switch (status)
+ {
+ case IceProcessMessagesSuccess:
+ return TRUE;
+
+ case IceProcessMessagesIOError:
+ sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn));
+ return FALSE;
+
+ case IceProcessMessagesConnectionClosed:
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+ice_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer client_data)
+{
+ return process_ice_messages (client_data);
+}
+
+static void
+ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data)
+{
+ guint watch_id;
+
+ if (opening)
+ {
+ GIOChannel *channel;
+ int fd = IceConnectionNumber (ice_conn);
+
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+ channel = g_io_channel_unix_new (fd);
+ watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
+ ice_iochannel_watch, ice_conn);
+ g_io_channel_unref (channel);
+
+ *watch_data = GUINT_TO_POINTER (watch_id);
+ }
+ else
+ {
+ watch_id = GPOINTER_TO_UINT (*watch_data);
+ g_source_remove (watch_id);
+ }
+}
+
+static void
+ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values)
+{
+ /* Do nothing */
+}
+
+static void
+ice_io_error_handler (IceConn ice_conn)
+{
+ /* Do nothing */
+}
+
+static void
+smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values)
+{
+ /* Do nothing */
+}
diff --git a/pluma/smclient/eggsmclient.c b/pluma/smclient/eggsmclient.c
new file mode 100755
index 00000000..4b65f283
--- /dev/null
+++ b/pluma/smclient/eggsmclient.c
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+static void egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data);
+
+enum {
+ SAVE_STATE,
+ QUIT_REQUESTED,
+ QUIT_CANCELLED,
+ QUIT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+struct _EggSMClientPrivate {
+ GKeyFile *state_file;
+};
+
+#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate))
+
+G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT)
+
+static EggSMClient *global_client;
+static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL;
+
+static void
+egg_sm_client_init (EggSMClient *client)
+{
+ ;
+}
+
+static void
+egg_sm_client_class_init (EggSMClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (EggSMClientPrivate));
+
+ /**
+ * EggSMClient::save_state:
+ * @client: the client
+ * @state_file: a #GKeyFile to save state information into
+ *
+ * Emitted when the session manager has requested that the
+ * application save information about its current state. The
+ * application should save its state into @state_file, and then the
+ * session manager may then restart the application in a future
+ * session and tell it to initialize itself from that state.
+ *
+ * You should not save any data into @state_file's "start group"
+ * (ie, the %NULL group). Instead, applications should save their
+ * data into groups with names that start with the application name,
+ * and libraries that connect to this signal should save their data
+ * into groups with names that start with the library name.
+ *
+ * Alternatively, rather than (or in addition to) using @state_file,
+ * the application can save its state by calling
+ * egg_sm_client_set_restart_command() during the processing of this
+ * signal (eg, to include a list of files to open).
+ **/
+ signals[SAVE_STATE] =
+ g_signal_new ("save_state",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, save_state),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ /**
+ * EggSMClient::quit_requested:
+ * @client: the client
+ *
+ * Emitted when the session manager requests that the application
+ * exit (generally because the user is logging out). The application
+ * should decide whether or not it is willing to quit (perhaps after
+ * asking the user what to do with documents that have unsaved
+ * changes) and then call egg_sm_client_will_quit(), passing %TRUE
+ * or %FALSE to give its answer to the session manager. (It does not
+ * need to give an answer before returning from the signal handler;
+ * it can interact with the user asynchronously and then give its
+ * answer later on.) If the application does not connect to this
+ * signal, then #EggSMClient will automatically return %TRUE on its
+ * behalf.
+ *
+ * The application should not save its session state as part of
+ * handling this signal; if the user has requested that the session
+ * be saved when logging out, then ::save_state will be emitted
+ * separately.
+ *
+ * If the application agrees to quit, it should then wait for either
+ * the ::quit_cancelled or ::quit signals to be emitted.
+ **/
+ signals[QUIT_REQUESTED] =
+ g_signal_new ("quit_requested",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_requested),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit_cancelled:
+ * @client: the client
+ *
+ * Emitted when the session manager decides to cancel a logout after
+ * the application has already agreed to quit. After receiving this
+ * signal, the application can go back to what it was doing before
+ * receiving the ::quit_requested signal.
+ **/
+ signals[QUIT_CANCELLED] =
+ g_signal_new ("quit_cancelled",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit:
+ * @client: the client
+ *
+ * Emitted when the session manager wants the application to quit
+ * (generally because the user is logging out). The application
+ * should exit as soon as possible after receiving this signal; if
+ * it does not, the session manager may choose to forcibly kill it.
+ *
+ * Normally a GUI application would only be sent a ::quit if it
+ * agreed to quit in response to a ::quit_requested signal. However,
+ * this is not guaranteed; in some situations the session manager
+ * may decide to end the session without giving applications a
+ * chance to object.
+ **/
+ signals[QUIT] =
+ g_signal_new ("quit",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static gboolean sm_client_disable = FALSE;
+static char *sm_client_state_file = NULL;
+static char *sm_client_id = NULL;
+static char *sm_config_prefix = NULL;
+
+static gboolean
+sm_client_post_parse_func (GOptionContext *context,
+ GOptionGroup *group,
+ gpointer data,
+ GError **error)
+{
+ EggSMClient *client = egg_sm_client_get ();
+
+ if (sm_client_id == NULL)
+ {
+ const gchar *desktop_autostart_id;
+
+ desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
+
+ if (desktop_autostart_id != NULL)
+ sm_client_id = g_strdup (desktop_autostart_id);
+ }
+
+ /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to
+ * use the same client id. */
+ g_unsetenv ("DESKTOP_AUTOSTART_ID");
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->startup)
+ EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id);
+ return TRUE;
+}
+
+/**
+ * egg_sm_client_get_option_group:
+ *
+ * Creates a %GOptionGroup containing the session-management-related
+ * options. You should add this group to the application's
+ * %GOptionContext if you want to use #EggSMClient.
+ *
+ * Return value: the %GOptionGroup
+ **/
+GOptionGroup *
+egg_sm_client_get_option_group (void)
+{
+ const GOptionEntry entries[] = {
+ { "sm-client-disable", 0, 0,
+ G_OPTION_ARG_NONE, &sm_client_disable,
+ N_("Disable connection to session manager"), NULL },
+ { "sm-client-state-file", 0, 0,
+ G_OPTION_ARG_FILENAME, &sm_client_state_file,
+ N_("Specify file containing saved configuration"), N_("FILE") },
+ { "sm-client-id", 0, 0,
+ G_OPTION_ARG_STRING, &sm_client_id,
+ N_("Specify session management ID"), N_("ID") },
+ /* MateClient compatibility option */
+ { "sm-disable", 0, G_OPTION_FLAG_HIDDEN,
+ G_OPTION_ARG_NONE, &sm_client_disable,
+ NULL, NULL },
+ /* MateClient compatibility option. This is a dummy option that only
+ * exists so that sessions saved by apps with MateClient can be restored
+ * later when they've switched to EggSMClient. See bug #575308.
+ */
+ { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN,
+ G_OPTION_ARG_STRING, &sm_config_prefix,
+ NULL, NULL },
+ { NULL }
+ };
+ GOptionGroup *group;
+
+ /* Use our own debug handler for the "EggSMClient" domain. */
+ g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
+ egg_sm_client_debug_handler, NULL);
+
+ group = g_option_group_new ("sm-client",
+ _("Session management options:"),
+ _("Show session management options"),
+ NULL, NULL);
+ g_option_group_add_entries (group, entries);
+ g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func);
+
+ return group;
+}
+
+/**
+ * egg_sm_client_set_mode:
+ * @mode: an #EggSMClient mode
+ *
+ * Sets the "mode" of #EggSMClient as follows:
+ *
+ * %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely
+ * disabled. The application will not even connect to the session
+ * manager. (egg_sm_client_get() will still return an #EggSMClient,
+ * but it will just be a dummy object.)
+ *
+ * %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to
+ * the session manager (and thus will receive notification when the
+ * user is logging out, etc), but will request to not be
+ * automatically restarted with saved state in future sessions.
+ *
+ * %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will
+ * function normally.
+ *
+ * This must be called before the application's main loop begins.
+ **/
+void
+egg_sm_client_set_mode (EggSMClientMode mode)
+{
+ global_client_mode = mode;
+}
+
+/**
+ * egg_sm_client_get_mode:
+ *
+ * Gets the global #EggSMClientMode. See egg_sm_client_set_mode()
+ * for details.
+ *
+ * Return value: the global #EggSMClientMode
+ **/
+EggSMClientMode
+egg_sm_client_get_mode (void)
+{
+ return global_client_mode;
+}
+
+/**
+ * egg_sm_client_get:
+ *
+ * Returns the master #EggSMClient for the application.
+ *
+ * On platforms that support saved sessions (ie, POSIX/X11), the
+ * application will only request to be restarted by the session
+ * manager if you call egg_set_desktop_file() to set an application
+ * desktop file. In particular, if the desktop file contains the key
+ * "X
+ *
+ * Return value: the master #EggSMClient.
+ **/
+EggSMClient *
+egg_sm_client_get (void)
+{
+ if (!global_client)
+ {
+ if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED &&
+ !sm_client_disable)
+ {
+#if defined (GDK_WINDOWING_WIN32)
+ global_client = egg_sm_client_win32_new ();
+#elif defined (GDK_WINDOWING_QUARTZ)
+ global_client = egg_sm_client_osx_new ();
+#else
+ /* If both D-Bus and XSMP are compiled in, try XSMP first
+ * (since it supports state saving) and fall back to D-Bus
+ * if XSMP isn't available.
+ */
+# ifdef EGG_SM_CLIENT_BACKEND_XSMP
+ global_client = egg_sm_client_xsmp_new ();
+# endif
+# ifdef EGG_SM_CLIENT_BACKEND_DBUS
+ if (!global_client)
+ global_client = egg_sm_client_dbus_new ();
+# endif
+#endif
+ }
+
+ /* Fallback: create a dummy client, so that callers don't have
+ * to worry about a %NULL return value.
+ */
+ if (!global_client)
+ global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL);
+ }
+
+ return global_client;
+}
+
+/**
+ * egg_sm_client_is_resumed:
+ * @client: the client
+ *
+ * Checks whether or not the current session has been resumed from
+ * a previous saved session. If so, the application should call
+ * egg_sm_client_get_state_file() and restore its state from the
+ * returned #GKeyFile.
+ *
+ * Return value: %TRUE if the session has been resumed
+ **/
+gboolean
+egg_sm_client_is_resumed (EggSMClient *client)
+{
+ g_return_val_if_fail (client == global_client, FALSE);
+
+ return sm_client_state_file != NULL;
+}
+
+/**
+ * egg_sm_client_get_state_file:
+ * @client: the client
+ *
+ * If the application was resumed by the session manager, this will
+ * return the #GKeyFile containing its state from the previous
+ * session.
+ *
+ * Note that other libraries and #EggSMClient itself may also store
+ * state in the key file, so if you call egg_sm_client_get_groups(),
+ * on it, the return value will likely include groups that you did not
+ * put there yourself. (It is also not guaranteed that the first
+ * group created by the application will still be the "start group"
+ * when it is resumed.)
+ *
+ * Return value: the #GKeyFile containing the application's earlier
+ * state, or %NULL on error. You should not free this key file; it
+ * is owned by @client.
+ **/
+GKeyFile *
+egg_sm_client_get_state_file (EggSMClient *client)
+{
+ EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client);
+ char *state_file_path;
+ GError *err = NULL;
+
+ g_return_val_if_fail (client == global_client, NULL);
+
+ if (!sm_client_state_file)
+ return NULL;
+ if (priv->state_file)
+ return priv->state_file;
+
+ if (!strncmp (sm_client_state_file, "file://", 7))
+ state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL);
+ else
+ state_file_path = g_strdup (sm_client_state_file);
+
+ priv->state_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err))
+ {
+ g_warning ("Could not load SM state file '%s': %s",
+ sm_client_state_file, err->message);
+ g_clear_error (&err);
+ g_key_file_free (priv->state_file);
+ priv->state_file = NULL;
+ }
+
+ g_free (state_file_path);
+ return priv->state_file;
+}
+
+/**
+ * egg_sm_client_set_restart_command:
+ * @client: the client
+ * @argc: the length of @argv
+ * @argv: argument vector
+ *
+ * Sets the command used to restart @client if it does not have a
+ * .desktop file that can be used to find its restart command.
+ *
+ * This can also be used when handling the ::save_state signal, to
+ * save the current state via an updated command line. (Eg, providing
+ * a list of filenames to open when the application is resumed.)
+ **/
+void
+egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command)
+ EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv);
+}
+
+/**
+ * egg_sm_client_will_quit:
+ * @client: the client
+ * @will_quit: whether or not the application is willing to quit
+ *
+ * This MUST be called in response to the ::quit_requested signal, to
+ * indicate whether or not the application is willing to quit. The
+ * application may call it either directly from the signal handler, or
+ * at some later point (eg, after asynchronously interacting with the
+ * user).
+ *
+ * If the application does not connect to ::quit_requested,
+ * #EggSMClient will call this method on its behalf (passing %TRUE
+ * for @will_quit).
+ *
+ * After calling this method, the application should wait to receive
+ * either ::quit_cancelled or ::quit.
+ **/
+void
+egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit)
+ EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit);
+}
+
+/**
+ * egg_sm_client_end_session:
+ * @style: a hint at how to end the session
+ * @request_confirmation: whether or not the user should get a chance
+ * to confirm the action
+ *
+ * Requests that the session manager end the current session. @style
+ * indicates how the session should be ended, and
+ * @request_confirmation indicates whether or not the user should be
+ * given a chance to confirm the logout/reboot/shutdown. Both of these
+ * flags are merely hints though; the session manager may choose to
+ * ignore them.
+ *
+ * Return value: %TRUE if the request was sent; %FALSE if it could not
+ * be (eg, because it could not connect to the session manager).
+ **/
+gboolean
+egg_sm_client_end_session (EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ EggSMClient *client = egg_sm_client_get ();
+
+ g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE);
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->end_session)
+ {
+ return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style,
+ request_confirmation);
+ }
+ else
+ return FALSE;
+}
+
+/* Signal-emitting callbacks from platform-specific code */
+
+GKeyFile *
+egg_sm_client_save_state (EggSMClient *client)
+{
+ GKeyFile *state_file;
+ char *group;
+
+ g_return_val_if_fail (client == global_client, NULL);
+
+ state_file = g_key_file_new ();
+
+ g_debug ("Emitting save_state");
+ g_signal_emit (client, signals[SAVE_STATE], 0, state_file);
+ g_debug ("Done emitting save_state");
+
+ group = g_key_file_get_start_group (state_file);
+ if (group)
+ {
+ g_free (group);
+ return state_file;
+ }
+ else
+ {
+ g_key_file_free (state_file);
+ return NULL;
+ }
+}
+
+void
+egg_sm_client_quit_requested (EggSMClient *client)
+{
+ g_return_if_fail (client == global_client);
+
+ if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE))
+ {
+ g_debug ("Not emitting quit_requested because no one is listening");
+ egg_sm_client_will_quit (client, TRUE);
+ return;
+ }
+
+ g_debug ("Emitting quit_requested");
+ g_signal_emit (client, signals[QUIT_REQUESTED], 0);
+ g_debug ("Done emitting quit_requested");
+}
+
+void
+egg_sm_client_quit_cancelled (EggSMClient *client)
+{
+ g_return_if_fail (client == global_client);
+
+ g_debug ("Emitting quit_cancelled");
+ g_signal_emit (client, signals[QUIT_CANCELLED], 0);
+ g_debug ("Done emitting quit_cancelled");
+}
+
+void
+egg_sm_client_quit (EggSMClient *client)
+{
+ g_return_if_fail (client == global_client);
+
+ g_debug ("Emitting quit");
+ g_signal_emit (client, signals[QUIT], 0);
+ g_debug ("Done emitting quit");
+
+ /* FIXME: should we just call gtk_main_quit() here? */
+}
+
+static void
+egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data)
+{
+ static int debug = -1;
+
+ if (debug < 0)
+ debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL);
+
+ if (debug)
+ g_log_default_handler (log_domain, log_level, message, NULL);
+}
diff --git a/pluma/smclient/eggsmclient.h b/pluma/smclient/eggsmclient.h
new file mode 100755
index 00000000..e620b754
--- /dev/null
+++ b/pluma/smclient/eggsmclient.h
@@ -0,0 +1,117 @@
+/* eggsmclient.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_SM_CLIENT_H__
+#define __EGG_SM_CLIENT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ())
+#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient))
+#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT))
+#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT))
+#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+
+typedef struct _EggSMClient EggSMClient;
+typedef struct _EggSMClientClass EggSMClientClass;
+typedef struct _EggSMClientPrivate EggSMClientPrivate;
+
+typedef enum {
+ EGG_SM_CLIENT_END_SESSION_DEFAULT,
+ EGG_SM_CLIENT_LOGOUT,
+ EGG_SM_CLIENT_REBOOT,
+ EGG_SM_CLIENT_SHUTDOWN
+} EggSMClientEndStyle;
+
+typedef enum {
+ EGG_SM_CLIENT_MODE_DISABLED,
+ EGG_SM_CLIENT_MODE_NO_RESTART,
+ EGG_SM_CLIENT_MODE_NORMAL
+} EggSMClientMode;
+
+struct _EggSMClient
+{
+ GObject parent;
+
+};
+
+struct _EggSMClientClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*save_state) (EggSMClient *client,
+ GKeyFile *state_file);
+
+ void (*quit_requested) (EggSMClient *client);
+ void (*quit_cancelled) (EggSMClient *client);
+ void (*quit) (EggSMClient *client);
+
+ /* virtual methods */
+ void (*startup) (EggSMClient *client,
+ const char *client_id);
+ void (*set_restart_command) (EggSMClient *client,
+ int argc,
+ const char **argv);
+ void (*will_quit) (EggSMClient *client,
+ gboolean will_quit);
+ gboolean (*end_session) (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+ /* Padding for future expansion */
+ void (*_egg_reserved1) (void);
+ void (*_egg_reserved2) (void);
+ void (*_egg_reserved3) (void);
+ void (*_egg_reserved4) (void);
+};
+
+GType egg_sm_client_get_type (void) G_GNUC_CONST;
+
+GOptionGroup *egg_sm_client_get_option_group (void);
+
+/* Initialization */
+void egg_sm_client_set_mode (EggSMClientMode mode);
+EggSMClientMode egg_sm_client_get_mode (void);
+EggSMClient *egg_sm_client_get (void);
+
+/* Resuming a saved session */
+gboolean egg_sm_client_is_resumed (EggSMClient *client);
+GKeyFile *egg_sm_client_get_state_file (EggSMClient *client);
+
+/* Alternate means of saving state */
+void egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+
+/* Handling "quit_requested" signal */
+void egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit);
+
+/* Initiate a logout/reboot/shutdown */
+gboolean egg_sm_client_end_session (EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_H__ */