diff options
author | Perberos <[email protected]> | 2011-11-07 16:46:58 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-11-07 16:46:58 -0300 |
commit | 528c1e5ff51e213936e800fc5a9a25da99c0bdf2 (patch) | |
tree | 77f8aa456b09367ba81f04d4562fc935f898a951 /gedit | |
download | pluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.bz2 pluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.xz |
initial
Diffstat (limited to 'gedit')
146 files changed, 62258 insertions, 0 deletions
diff --git a/gedit/Makefile.am b/gedit/Makefile.am new file mode 100755 index 00000000..f08c6427 --- /dev/null +++ b/gedit/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 = gedit + +noinst_LTLIBRARIES = libgedit.la + +INCLUDES = \ + -I$(top_srcdir) \ + -I$(srcdir) \ + -I$(srcdir)/smclient \ + $(GEDIT_CFLAGS) \ + $(IGE_MAC_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) \ + -DDATADIR=\""$(datadir)"\" \ + -DLIBDIR=\""$(libdir)"\" + +gedit_SOURCES = \ + gedit.c + +gedit_LDADD = libgedit.la $(GEDIT_LIBS) $(IGE_MAC_LIBS) $(EGG_SMCLIENT_LIBS) + +if PLATFORM_WIN32 +gedit_LDFLAGS = -Wl,--export-all-symbols -Wl,--out-implib,libgedit-$(GEDIT_API_VERSION).a +if OS_WIN32 +gedit_LDFLAGS += -mwindows +endif +else +gedit_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*" +endif + +libgedit_la_LDFLAGS = -export-dynamic -no-undefined -export-symbols-regex "^[[^_]].*" + +libgedit_la_LIBADD = \ + dialogs/libdialogs.la \ + smclient/libeggsmclient.la + +# GEDIT_LIBS must be the last to ensure correct order on some platforms +libgedit_la_LIBADD += $(GEDIT_LIBS) + +if OS_OSX +gedit_LDFLAGS += -framework Carbon + +libgedit_la_LIBADD += osx/libosx.la +endif + +BUILT_SOURCES = \ + gedit-enum-types.c \ + gedit-enum-types.h \ + gedit-marshal.c \ + gedit-marshal.h + +if OS_WIN32 +gedit-res.o: gedit.rc + $(WINDRES) -i gedit.rc --input-format=rc -o gedit-res.o -O coff + +gedit_LDADD += gedit-res.o +endif + +NOINST_H_FILES = \ + gedit-close-button.h \ + gedit-dirs.h \ + gedit-document-input-stream.h \ + gedit-document-loader.h \ + gedit-document-output-stream.h \ + gedit-document-saver.h \ + gedit-documents-panel.h \ + gedit-gio-document-loader.h \ + gedit-gio-document-saver.h \ + gedit-history-entry.h \ + gedit-io-error-message-area.h \ + gedit-language-manager.h \ + gedit-object-module.h \ + gedit-plugin-info.h \ + gedit-plugin-info-priv.h \ + gedit-plugin-loader.h \ + gedit-plugin-manager.h \ + gedit-plugins-engine.h \ + gedit-prefs-manager-private.h \ + gedit-print-job.h \ + gedit-print-preview.h \ + gedit-session.h \ + gedit-smart-charset-converter.h \ + gedit-style-scheme-manager.h \ + gedit-tab-label.h \ + gedittextregion.h \ + gedit-ui.h \ + gedit-window-private.h + +INST_H_FILES = \ + gedit-app.h \ + gedit-commands.h \ + gedit-debug.h \ + gedit-document.h \ + gedit-encodings.h \ + gedit-encodings-combo-box.h \ + gedit-file-chooser-dialog.h \ + gedit-help.h \ + gedit-message-bus.h \ + gedit-message-type.h \ + gedit-message.h \ + gedit-notebook.h \ + gedit-panel.h \ + gedit-plugin.h \ + gedit-prefs-manager-app.h \ + gedit-prefs-manager.h \ + gedit-progress-message-area.h \ + gedit-statusbar.h \ + gedit-status-combo-box.h \ + gedit-tab.h \ + gedit-utils.h \ + gedit-view.h \ + gedit-window.h + +if !ENABLE_GVFS_METADATA +INST_H_FILES += gedit-metadata-manager.h +endif + +headerdir = $(prefix)/include/gedit-@GEDIT_API_VERSION@/gedit + +header_DATA = \ + $(INST_H_FILES) + + +libgedit_la_SOURCES = \ + $(BUILT_SOURCES) \ + $(BACON_FILES) \ + $(POSIXIO_FILES) \ + gedit-app.c \ + gedit-close-button.c \ + gedit-commands-documents.c \ + gedit-commands-edit.c \ + gedit-commands-file.c \ + gedit-commands-file-print.c \ + gedit-commands-help.c \ + gedit-commands-search.c \ + gedit-commands-view.c \ + gedit-debug.c \ + gedit-dirs.c \ + gedit-document.c \ + gedit-document-input-stream.c \ + gedit-document-loader.c \ + gedit-document-output-stream.c \ + gedit-gio-document-loader.c \ + gedit-document-saver.c \ + gedit-gio-document-saver.c \ + gedit-documents-panel.c \ + gedit-encodings.c \ + gedit-encodings-combo-box.c \ + gedit-file-chooser-dialog.c \ + gedit-help.c \ + gedit-history-entry.c \ + gedit-io-error-message-area.c \ + gedit-language-manager.c \ + gedit-message-bus.c \ + gedit-message-type.c \ + gedit-message.c \ + gedit-object-module.c \ + gedit-notebook.c \ + gedit-panel.c \ + gedit-plugin-info.c \ + gedit-plugin.c \ + gedit-plugin-loader.c \ + gedit-plugin-manager.c \ + gedit-plugins-engine.c \ + gedit-prefs-manager-app.c \ + gedit-prefs-manager.c \ + gedit-prefs-manager-private.h \ + gedit-print-job.c \ + gedit-print-preview.c \ + gedit-progress-message-area.c \ + gedit-session.c \ + gedit-smart-charset-converter.c \ + gedit-statusbar.c \ + gedit-status-combo-box.c \ + gedit-style-scheme-manager.c \ + gedit-tab.c \ + gedit-tab-label.c \ + gedit-utils.c \ + gedit-view.c \ + gedit-window.c \ + gedittextregion.c \ + $(NOINST_H_FILES) \ + $(INST_H_FILES) + +if !ENABLE_GVFS_METADATA +libgedit_la_SOURCES += gedit-metadata-manager.c +endif + +gedit-enum-types.h: gedit-enum-types.h.template $(INST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template gedit-enum-types.h.template $(INST_H_FILES)) > $@ + +gedit-enum-types.c: gedit-enum-types.c.template $(INST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template gedit-enum-types.c.template $(INST_H_FILES)) > $@ + +gedit-marshal.h: gedit-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=gedit_marshal > $@ + +gedit-marshal.c: gedit-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) echo "#include \"gedit-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=gedit_marshal >> $@ + +uidir = $(datadir)/gedit-2/ui/ +ui_DATA = \ + gedit-ui.xml \ + gedit-print-preferences.ui + +EXTRA_DIST = \ + $(ui_DATA) \ + gedit-enum-types.h.template \ + gedit-enum-types.c.template \ + gedit-marshal.list \ + gedit.rc + +CLEANFILES = $(BUILT_SOURCES) + +dist-hook: + cd $(distdir); rm -f $(BUILT_SOURCES) + +install-exec-hook: +if PLATFORM_WIN32 + $(mkinstalldirs) "$(DESTDIR)$(libdir)" + $(INSTALL_DATA) libgedit-$(GEDIT_API_VERSION).a "$(DESTDIR)$(libdir)" +else + rm -f $(DESTDIR)$(bindir)/mate-text-editor + ln -s gedit $(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)/gedit/update-from-bacon.sh +else +BACON_DIR= +endif + +if BUILD_MESSAGE_AREA +libgedit_la_SOURCES += gedit-message-area.c +INST_H_FILES += gedit-message-area.h +endif + +if BUILD_SPINNER +libgedit_la_SOURCES += gedit-spinner.c +NOINST_H_FILES += gedit-spinner.h +endif + +-include $(top_srcdir)/git.mk diff --git a/gedit/bacon-message-connection.c b/gedit/bacon-message-connection.c new file mode 100755 index 00000000..c8000de2 --- /dev/null +++ b/gedit/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/gedit/bacon-message-connection.h b/gedit/bacon-message-connection.h new file mode 100755 index 00000000..aac7a2d1 --- /dev/null +++ b/gedit/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/gedit/dialogs/Makefile.am b/gedit/dialogs/Makefile.am new file mode 100755 index 00000000..c0762571 --- /dev/null +++ b/gedit/dialogs/Makefile.am @@ -0,0 +1,31 @@ +uidir = $(datadir)/gedit-2/ui/ + +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -I$(top_srcdir)/gedit \ + -I$(top_builddir)/gedit \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +noinst_LTLIBRARIES = libdialogs.la + +libdialogs_la_SOURCES = \ + gedit-preferences-dialog.h \ + gedit-preferences-dialog.c \ + gedit-close-confirmation-dialog.c \ + gedit-close-confirmation-dialog.h \ + gedit-encodings-dialog.c \ + gedit-encodings-dialog.h \ + gedit-search-dialog.h \ + gedit-search-dialog.c + +ui_DATA = \ + gedit-encodings-dialog.ui \ + gedit-preferences-dialog.ui \ + gedit-search-dialog.ui + +EXTRA_DIST = $(ui_DATA) + +-include $(top_srcdir)/git.mk diff --git a/gedit/dialogs/gedit-close-confirmation-dialog.c b/gedit/dialogs/gedit-close-confirmation-dialog.c new file mode 100755 index 00000000..36cbf117 --- /dev/null +++ b/gedit/dialogs/gedit-close-confirmation-dialog.c @@ -0,0 +1,790 @@ +/* + * gedit-close-confirmation-dialog.c + * This file is part of gedit + * + * 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 gedit Team, 2004-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib/gi18n.h> + +#include "gedit-close-confirmation-dialog.h" +#include <gedit/gedit-app.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-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 _GeditCloseConfirmationDialogPrivate +{ + gboolean logout_mode; + + GList *unsaved_documents; + + GList *selected_documents; + + GtkTreeModel *list_store; + + gboolean disable_save_to_disk; +}; + +#define GEDIT_CLOSE_CONFIRMATION_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG, \ + GeditCloseConfirmationDialogPrivate)) + +#define GET_MODE(priv) (((priv->unsaved_documents != NULL) && \ + (priv->unsaved_documents->next == NULL)) ? \ + SINGLE_DOC_MODE : MULTIPLE_DOCS_MODE) + +G_DEFINE_TYPE(GeditCloseConfirmationDialog, gedit_close_confirmation_dialog, GTK_TYPE_DIALOG) + +static void set_unsaved_document (GeditCloseConfirmationDialog *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 (GeditCloseConfirmationDialog *dlg, + gint response_id, + gpointer data) +{ + GeditCloseConfirmationDialogPrivate *priv; + + g_return_if_fail (GEDIT_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 (GeditCloseConfirmationDialog *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); + + gedit_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) + { + GeditDocument *doc; + + doc = GEDIT_DOCUMENT (dlg->priv->unsaved_documents->data); + + if (gedit_document_get_readonly (doc) || + gedit_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 +gedit_close_confirmation_dialog_init (GeditCloseConfirmationDialog *dlg) +{ + AtkObject *atk_obj; + + dlg->priv = GEDIT_CLOSE_CONFIRMATION_DIALOG_GET_PRIVATE (dlg); + + dlg->priv->disable_save_to_disk = + gedit_app_get_lockdown (gedit_app_get_default ()) + & GEDIT_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 +gedit_close_confirmation_dialog_finalize (GObject *object) +{ + GeditCloseConfirmationDialogPrivate *priv; + + priv = GEDIT_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 (gedit_close_confirmation_dialog_parent_class)->finalize (object); +} + +static void +gedit_close_confirmation_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditCloseConfirmationDialog *dlg; + + dlg = GEDIT_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 +gedit_close_confirmation_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditCloseConfirmationDialogPrivate *priv; + + priv = GEDIT_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 +gedit_close_confirmation_dialog_class_init (GeditCloseConfirmationDialogClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gedit_close_confirmation_dialog_set_property; + gobject_class->get_property = gedit_close_confirmation_dialog_get_property; + gobject_class->finalize = gedit_close_confirmation_dialog_finalize; + + g_type_class_add_private (klass, sizeof (GeditCloseConfirmationDialogPrivate)); + + 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; + GeditDocument *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 * +gedit_close_confirmation_dialog_get_selected_documents (GeditCloseConfirmationDialog *dlg) +{ + g_return_val_if_fail (GEDIT_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL); + + return g_list_copy (dlg->priv->selected_documents); +} + +GtkWidget * +gedit_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 (GEDIT_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 (gedit_window_get_group (GEDIT_WINDOW (parent)), + GTK_WINDOW (dlg)); + + gtk_window_set_transient_for (GTK_WINDOW (dlg), parent); + } + + return dlg; +} + +GtkWidget * +gedit_close_confirmation_dialog_new_single (GtkWindow *parent, + GeditDocument *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 = gedit_close_confirmation_dialog_new (parent, + unsaved_documents, + logout_mode); + + g_list_free (unsaved_documents); + + return dlg; +} + +static gchar * +get_text_secondary_label (GeditDocument *doc) +{ + glong seconds; + gchar *secondary_msg; + + seconds = MAX (1, _gedit_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 (GeditCloseConfirmationDialog *dlg) +{ + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *primary_label; + GtkWidget *secondary_label; + GtkWidget *image; + GeditDocument *doc; + gchar *doc_name; + gchar *str; + gchar *markup_str; + + g_return_if_fail (dlg->priv->unsaved_documents->data != NULL); + doc = GEDIT_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 = gedit_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) + { + GeditDocument *doc; + gchar *name; + + doc = GEDIT_DOCUMENT (docs->data); + + name = gedit_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 (GeditCloseConfirmationDialogPrivate *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 (GeditCloseConfirmationDialog *dlg) +{ + GeditCloseConfirmationDialogPrivate *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 (GeditCloseConfirmationDialog *dlg, + const GList *list) +{ + GeditCloseConfirmationDialogPrivate *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 * +gedit_close_confirmation_dialog_get_unsaved_documents (GeditCloseConfirmationDialog *dlg) +{ + g_return_val_if_fail (GEDIT_IS_CLOSE_CONFIRMATION_DIALOG (dlg), NULL); + + return dlg->priv->unsaved_documents; +} diff --git a/gedit/dialogs/gedit-close-confirmation-dialog.h b/gedit/dialogs/gedit-close-confirmation-dialog.h new file mode 100755 index 00000000..887fc1b4 --- /dev/null +++ b/gedit/dialogs/gedit-close-confirmation-dialog.h @@ -0,0 +1,75 @@ +/* + * gedit-close-confirmation-dialog.h + * This file is part of gedit + * + * 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 gedit Team, 2004-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef __GEDIT_CLOSE_CONFIRMATION_DIALOG_H__ +#define __GEDIT_CLOSE_CONFIRMATION_DIALOG_H__ + +#include <glib.h> +#include <gtk/gtk.h> + +#include <gedit/gedit-document.h> + +#define GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG (gedit_close_confirmation_dialog_get_type ()) +#define GEDIT_CLOSE_CONFIRMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG, GeditCloseConfirmationDialog)) +#define GEDIT_CLOSE_CONFIRMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG, GeditCloseConfirmationDialogClass)) +#define GEDIT_IS_CLOSE_CONFIRMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG)) +#define GEDIT_IS_CLOSE_CONFIRMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG)) +#define GEDIT_CLOSE_CONFIRMATION_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),GEDIT_TYPE_CLOSE_CONFIRMATION_DIALOG, GeditCloseConfirmationDialogClass)) + +typedef struct _GeditCloseConfirmationDialog GeditCloseConfirmationDialog; +typedef struct _GeditCloseConfirmationDialogClass GeditCloseConfirmationDialogClass; +typedef struct _GeditCloseConfirmationDialogPrivate GeditCloseConfirmationDialogPrivate; + +struct _GeditCloseConfirmationDialog +{ + GtkDialog parent; + + /*< private > */ + GeditCloseConfirmationDialogPrivate *priv; +}; + +struct _GeditCloseConfirmationDialogClass +{ + GtkDialogClass parent_class; +}; + +GType gedit_close_confirmation_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_close_confirmation_dialog_new (GtkWindow *parent, + GList *unsaved_documents, + gboolean logout_mode); +GtkWidget *gedit_close_confirmation_dialog_new_single (GtkWindow *parent, + GeditDocument *doc, + gboolean logout_mode); + +const GList *gedit_close_confirmation_dialog_get_unsaved_documents (GeditCloseConfirmationDialog *dlg); + +GList *gedit_close_confirmation_dialog_get_selected_documents (GeditCloseConfirmationDialog *dlg); + +#endif /* __GEDIT_CLOSE_CONFIRMATION_DIALOG_H__ */ + diff --git a/gedit/dialogs/gedit-encodings-dialog.c b/gedit/dialogs/gedit-encodings-dialog.c new file mode 100755 index 00000000..b4800805 --- /dev/null +++ b/gedit/dialogs/gedit-encodings-dialog.c @@ -0,0 +1,499 @@ +/* + * gedit-encodings-dialog.c + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-encodings-dialog.h" +#include "gedit-encodings.h" +#include "gedit-prefs-manager.h" +#include "gedit-utils.h" +#include "gedit-debug.h" +#include "gedit-help.h" +#include "gedit-dirs.h" + +#define GEDIT_ENCODINGS_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_ENCODINGS_DIALOG, \ + GeditEncodingsDialogPrivate)) + +struct _GeditEncodingsDialogPrivate +{ + 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(GeditEncodingsDialog, gedit_encodings_dialog, GTK_TYPE_DIALOG) + +static void +gedit_encodings_dialog_finalize (GObject *object) +{ + GeditEncodingsDialogPrivate *priv = GEDIT_ENCODINGS_DIALOG (object)->priv; + + g_slist_free (priv->show_in_menu_list); + + G_OBJECT_CLASS (gedit_encodings_dialog_parent_class)->finalize (object); +} + +static void +gedit_encodings_dialog_class_init (GeditEncodingsDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_encodings_dialog_finalize; + + g_type_class_add_private (object_class, sizeof (GeditEncodingsDialogPrivate)); +} + +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, + GeditEncodingsDialog *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, + GeditEncodingsDialog *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 GeditEncoding *enc; + + charset = NULL; + gtk_tree_model_get (model, iter, COLUMN_CHARSET, &charset, -1); + + enc = gedit_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 GeditEncoding *enc; + + enc = (const GeditEncoding*) list->data; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_CHARSET, + gedit_encoding_get_charset (enc), + COLUMN_NAME, + gedit_encoding_get_name (enc), -1); + + list = g_slist_next (list); + } +} + +static void +add_button_clicked_callback (GtkWidget *button, + GeditEncodingsDialog *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, + GeditEncodingsDialog *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 (GeditEncodingsDialog *dialog) +{ + GtkTreeIter iter; + GSList *list, *tmp; + + /* add data to the list store */ + list = gedit_prefs_manager_get_shown_in_menu_encodings (); + + tmp = list; + + while (tmp != NULL) + { + const GeditEncoding *enc; + + enc = (const GeditEncoding *) 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, + gedit_encoding_get_charset (enc), + COLUMN_NAME, + gedit_encoding_get_name (enc), -1); + + tmp = g_slist_next (tmp); + } + + g_slist_free (list); +} + +static void +response_handler (GtkDialog *dialog, + gint response_id, + GeditEncodingsDialog *dlg) +{ + if (response_id == GTK_RESPONSE_HELP) + { + gedit_help_display (GTK_WINDOW (dialog), "gedit", NULL); + g_signal_stop_emission_by_name (dialog, "response"); + return; + } + + if (response_id == GTK_RESPONSE_OK) + { + g_return_if_fail (gedit_prefs_manager_shown_in_menu_encodings_can_set ()); + gedit_prefs_manager_set_shown_in_menu_encodings (dlg->priv->show_in_menu_list); + } +} + +static void +gedit_encodings_dialog_init (GeditEncodingsDialog *dlg) +{ + GtkWidget *content; + GtkCellRenderer *cell_renderer; + GtkTreeModel *sort_model; + GtkTreeViewColumn *column; + GtkTreeIter parent_iter; + GtkTreeSelection *selection; + const GeditEncoding *enc; + GtkWidget *error_widget; + int i; + gboolean ret; + gchar *file; + gchar *root_objects[] = { + "encodings-dialog-contents", + NULL + }; + + dlg->priv = GEDIT_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 = gedit_dirs_get_ui_file ("gedit-encodings-dialog.ui"); + ret = gedit_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 = gedit_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, + gedit_encoding_get_charset (enc), + COLUMN_NAME, + gedit_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 * +gedit_encodings_dialog_new (void) +{ + GtkWidget *dlg; + + dlg = GTK_WIDGET (g_object_new (GEDIT_TYPE_ENCODINGS_DIALOG, NULL)); + + return dlg; +} + diff --git a/gedit/dialogs/gedit-encodings-dialog.h b/gedit/dialogs/gedit-encodings-dialog.h new file mode 100755 index 00000000..a09cbe5c --- /dev/null +++ b/gedit/dialogs/gedit-encodings-dialog.h @@ -0,0 +1,86 @@ +/* + * gedit-encodings-dialog.h + * This file is part of gedit + * + * 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 gedit Team, 2003-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_ENCODINGS_DIALOG_H__ +#define __GEDIT_ENCODINGS_DIALOG_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_ENCODINGS_DIALOG (gedit_encodings_dialog_get_type()) +#define GEDIT_ENCODINGS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_ENCODINGS_DIALOG, GeditEncodingsDialog)) +#define GEDIT_ENCODINGS_DIALOG_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_ENCODINGS_DIALOG, GeditEncodingsDialog const)) +#define GEDIT_ENCODINGS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_ENCODINGS_DIALOG, GeditEncodingsDialogClass)) +#define GEDIT_IS_ENCODINGS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_ENCODINGS_DIALOG)) +#define GEDIT_IS_ENCODINGS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_ENCODINGS_DIALOG)) +#define GEDIT_ENCODINGS_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_ENCODINGS_DIALOG, GeditEncodingsDialogClass)) + + +/* Private structure type */ +typedef struct _GeditEncodingsDialogPrivate GeditEncodingsDialogPrivate; + +/* + * Main object structure + */ +typedef struct _GeditEncodingsDialog GeditEncodingsDialog; + +struct _GeditEncodingsDialog +{ + GtkDialog dialog; + + /*< private > */ + GeditEncodingsDialogPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditEncodingsDialogClass GeditEncodingsDialogClass; + +struct _GeditEncodingsDialogClass +{ + GtkDialogClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_encodings_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_encodings_dialog_new (void); + +G_END_DECLS + +#endif /* __GEDIT_ENCODINGS_DIALOG_H__ */ + diff --git a/gedit/dialogs/gedit-encodings-dialog.ui b/gedit/dialogs/gedit-encodings-dialog.ui new file mode 100755 index 00000000..0f37b432 --- /dev/null +++ b/gedit/dialogs/gedit-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/gedit/dialogs/gedit-preferences-dialog.c b/gedit/dialogs/gedit-preferences-dialog.c new file mode 100755 index 00000000..52f180fa --- /dev/null +++ b/gedit/dialogs/gedit-preferences-dialog.c @@ -0,0 +1,1189 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-preferences-dialog.c + * This file is part of gedit + * + * 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 gedit Team, 2001-2003. See the AUTHORS file for a + * list of people on the gedit 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 <gedit/gedit-prefs-manager.h> + +#include "gedit-preferences-dialog.h" +#include "gedit-utils.h" +#include "gedit-debug.h" +#include "gedit-document.h" +#include "gedit-style-scheme-manager.h" +#include "gedit-plugin-manager.h" +#include "gedit-help.h" +#include "gedit-dirs.h" + +/* + * gedit-preferences dialog is a singleton since we don't + * want two dialogs showing an inconsistent state of the + * preferences. + * When gedit_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 GEDIT_PREFERENCES_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_PREFERENCES_DIALOG, \ + GeditPreferencesDialogPrivate)) + +struct _GeditPreferencesDialogPrivate +{ + 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(GeditPreferencesDialog, gedit_preferences_dialog, GTK_TYPE_DIALOG) + + +static void +gedit_preferences_dialog_class_init (GeditPreferencesDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (GeditPreferencesDialogPrivate)); +} + +static void +dialog_response_handler (GtkDialog *dlg, + gint res_id) +{ + gedit_debug (DEBUG_PREFS); + + switch (res_id) + { + case GTK_RESPONSE_HELP: + gedit_help_display (GTK_WINDOW (dlg), + NULL, + "gedit-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, + GeditPreferencesDialog *dlg) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (spin_button == GTK_SPIN_BUTTON (dlg->priv->tabs_width_spinbutton)); + + gedit_prefs_manager_set_tabs_size (gtk_spin_button_get_value_as_int (spin_button)); +} + +static void +insert_spaces_checkbutton_toggled (GtkToggleButton *button, + GeditPreferencesDialog *dlg) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->insert_spaces_checkbutton)); + + gedit_prefs_manager_set_insert_spaces (gtk_toggle_button_get_active (button)); +} + +static void +auto_indent_checkbutton_toggled (GtkToggleButton *button, + GeditPreferencesDialog *dlg) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->auto_indent_checkbutton)); + + gedit_prefs_manager_set_auto_indent (gtk_toggle_button_get_active (button)); +} + +static void +auto_save_checkbutton_toggled (GtkToggleButton *button, + GeditPreferencesDialog *dlg) +{ + gedit_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, + gedit_prefs_manager_auto_save_interval_can_set()); + + gedit_prefs_manager_set_auto_save (TRUE); + } + else + { + gtk_widget_set_sensitive (dlg->priv->auto_save_spinbutton, FALSE); + gedit_prefs_manager_set_auto_save (FALSE); + } +} + +static void +backup_copy_checkbutton_toggled (GtkToggleButton *button, + GeditPreferencesDialog *dlg) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->backup_copy_checkbutton)); + + gedit_prefs_manager_set_create_backup_copy (gtk_toggle_button_get_active (button)); +} + +static void +auto_save_spinbutton_value_changed (GtkSpinButton *spin_button, + GeditPreferencesDialog *dlg) +{ + g_return_if_fail (spin_button == GTK_SPIN_BUTTON (dlg->priv->auto_save_spinbutton)); + + gedit_prefs_manager_set_auto_save_interval ( + MAX (1, gtk_spin_button_get_value_as_int (spin_button))); +} + +static void +setup_editor_page (GeditPreferencesDialog *dlg) +{ + gboolean auto_save; + gint auto_save_interval; + + gedit_debug (DEBUG_PREFS); + + /* Set initial state */ + gtk_spin_button_set_value (GTK_SPIN_BUTTON (dlg->priv->tabs_width_spinbutton), + (guint) gedit_prefs_manager_get_tabs_size ()); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->insert_spaces_checkbutton), + gedit_prefs_manager_get_insert_spaces ()); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->auto_indent_checkbutton), + gedit_prefs_manager_get_auto_indent ()); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->backup_copy_checkbutton), + gedit_prefs_manager_get_create_backup_copy ()); + + auto_save = gedit_prefs_manager_get_auto_save (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->auto_save_checkbutton), + auto_save); + + auto_save_interval = gedit_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, + gedit_prefs_manager_tabs_size_can_set ()); + gtk_widget_set_sensitive (dlg->priv->insert_spaces_checkbutton, + gedit_prefs_manager_insert_spaces_can_set ()); + gtk_widget_set_sensitive (dlg->priv->auto_indent_checkbutton, + gedit_prefs_manager_auto_indent_can_set ()); + gtk_widget_set_sensitive (dlg->priv->backup_copy_checkbutton, + gedit_prefs_manager_create_backup_copy_can_set ()); + gtk_widget_set_sensitive (dlg->priv->autosave_hbox, + gedit_prefs_manager_auto_save_can_set ()); + gtk_widget_set_sensitive (dlg->priv->auto_save_spinbutton, + auto_save && + gedit_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, + GeditPreferencesDialog *dlg) +{ + g_return_if_fail (button == + GTK_TOGGLE_BUTTON (dlg->priv->display_line_numbers_checkbutton)); + + gedit_prefs_manager_set_display_line_numbers (gtk_toggle_button_get_active (button)); +} + +static void +highlight_current_line_checkbutton_toggled (GtkToggleButton *button, + GeditPreferencesDialog *dlg) +{ + g_return_if_fail (button == + GTK_TOGGLE_BUTTON (dlg->priv->highlight_current_line_checkbutton)); + + gedit_prefs_manager_set_highlight_current_line (gtk_toggle_button_get_active (button)); +} + +static void +bracket_matching_checkbutton_toggled (GtkToggleButton *button, + GeditPreferencesDialog *dlg) +{ + g_return_if_fail (button == + GTK_TOGGLE_BUTTON (dlg->priv->bracket_matching_checkbutton)); + + gedit_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, + GeditPreferencesDialog *dlg) +{ + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dlg->priv->wrap_text_checkbutton))) + { + gedit_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; + + gedit_prefs_manager_set_wrap_mode (GTK_WRAP_WORD); + } + else + { + split_button_state = FALSE; + + gedit_prefs_manager_set_wrap_mode (GTK_WRAP_CHAR); + } + } +} + +static void +right_margin_checkbutton_toggled (GtkToggleButton *button, + GeditPreferencesDialog *dlg) +{ + gboolean active; + + g_return_if_fail (button == GTK_TOGGLE_BUTTON (dlg->priv->right_margin_checkbutton)); + + active = gtk_toggle_button_get_active (button); + + gedit_prefs_manager_set_display_right_margin (active); + + gtk_widget_set_sensitive (dlg->priv->right_margin_position_hbox, + active && + gedit_prefs_manager_right_margin_position_can_set ()); +} + +static void +right_margin_position_spinbutton_value_changed (GtkSpinButton *spin_button, + GeditPreferencesDialog *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); + + gedit_prefs_manager_set_right_margin_position (value); +} + +static void +setup_view_page (GeditPreferencesDialog *dlg) +{ + GtkWrapMode wrap_mode; + gboolean display_right_margin; + gboolean wrap_mode_can_set; + + gedit_debug (DEBUG_PREFS); + + /* Set initial state */ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->display_line_numbers_checkbutton), + gedit_prefs_manager_get_display_line_numbers ()); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->highlight_current_line_checkbutton), + gedit_prefs_manager_get_highlight_current_line ()); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->bracket_matching_checkbutton), + gedit_prefs_manager_get_bracket_matching ()); + + wrap_mode = gedit_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 = gedit_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 (gedit_prefs_manager_get_right_margin_position (), 1, 160)); + + /* Set widgets sensitivity */ + gtk_widget_set_sensitive (dlg->priv->display_line_numbers_checkbutton, + gedit_prefs_manager_display_line_numbers_can_set ()); + gtk_widget_set_sensitive (dlg->priv->highlight_current_line_checkbutton, + gedit_prefs_manager_highlight_current_line_can_set ()); + gtk_widget_set_sensitive (dlg->priv->bracket_matching_checkbutton, + gedit_prefs_manager_bracket_matching_can_set ()); + wrap_mode_can_set = gedit_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, + gedit_prefs_manager_display_right_margin_can_set ()); + gtk_widget_set_sensitive (dlg->priv->right_margin_position_hbox, + display_right_margin && + gedit_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, + GeditPreferencesDialog *dlg) +{ + gedit_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); + gedit_prefs_manager_set_use_default_font (TRUE); + } + else + { + gtk_widget_set_sensitive (dlg->priv->font_hbox, + gedit_prefs_manager_editor_font_can_set ()); + gedit_prefs_manager_set_use_default_font (FALSE); + } +} + +static void +editor_font_button_font_set (GtkFontButton *font_button, + GeditPreferencesDialog *dlg) +{ + const gchar *font_name; + + gedit_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; + } + + gedit_prefs_manager_set_editor_font (font_name); +} + +static void +setup_font_colors_page_font_section (GeditPreferencesDialog *dlg) +{ + gboolean use_default_font; + gchar *editor_font = NULL; + gchar *label; + + gedit_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")); + + gedit_utils_set_atk_relation (dlg->priv->font_button, + dlg->priv->default_font_checkbutton, + ATK_RELATION_CONTROLLED_BY); + gedit_utils_set_atk_relation (dlg->priv->default_font_checkbutton, + dlg->priv->font_button, + ATK_RELATION_CONTROLLER_FOR); + + editor_font = gedit_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 = gedit_prefs_manager_get_use_default_font (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dlg->priv->default_font_checkbutton), + use_default_font); + + editor_font = gedit_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, + gedit_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, + gedit_prefs_manager_editor_font_can_set ()); +} + +static void +set_buttons_sensisitivity_according_to_scheme (GeditPreferencesDialog *dlg, + const gchar *scheme_id) +{ + gboolean editable; + + editable = (scheme_id != NULL) && + _gedit_style_scheme_manager_scheme_is_gedit_user_scheme ( + gedit_get_style_scheme_manager (), + scheme_id); + + gtk_widget_set_sensitive (dlg->priv->uninstall_scheme_button, + editable); +} + +static void +style_scheme_changed (GtkWidget *treeview, + GeditPreferencesDialog *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); + + gedit_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 = gedit_get_style_scheme_manager (); + + if (id == NULL) + { + gchar *pref_id; + + pref_id = gedit_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 + * gedit_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 (GeditPreferencesDialog *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 = gedit_style_scheme_manager_list_schemes_sorted (gedit_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, + GeditPreferencesDialog *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 = _gedit_style_scheme_manager_install_scheme ( + gedit_get_style_scheme_manager (), + filename); + g_free (filename); + + if (scheme_id == NULL) + { + gedit_warning (GTK_WINDOW (dlg), + _("The selected color scheme cannot be installed.")); + + return; + } + + gedit_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, + GeditPreferencesDialog *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); + + gedit_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, + GeditPreferencesDialog *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 (!_gedit_style_scheme_manager_uninstall_scheme (gedit_get_style_scheme_manager (), id)) + { + gedit_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) + gedit_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 (GeditPreferencesDialog *dlg) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + const gchar *def_id; + + gedit_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 (GeditPreferencesDialog *dlg) +{ + setup_font_colors_page_font_section (dlg); + setup_font_colors_page_style_scheme_section (dlg); +} + +static void +setup_plugins_page (GeditPreferencesDialog *dlg) +{ + GtkWidget *page_content; + + gedit_debug (DEBUG_PREFS); + + page_content = gedit_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 +gedit_preferences_dialog_init (GeditPreferencesDialog *dlg) +{ + GtkWidget *error_widget; + gboolean ret; + gchar *file; + gchar *root_objects[] = { + "notebook", + "adjustment1", + "adjustment2", + "adjustment3", + "install_scheme_image", + NULL + }; + + gedit_debug (DEBUG_PREFS); + + dlg->priv = GEDIT_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), _("gedit 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 = gedit_dirs_get_ui_file ("gedit-preferences-dialog.ui"); + ret = gedit_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 +gedit_show_preferences_dialog (GeditWindow *parent) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (GEDIT_IS_WINDOW (parent)); + + if (preferences_dialog == NULL) + { + preferences_dialog = GTK_WIDGET (g_object_new (GEDIT_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/gedit/dialogs/gedit-preferences-dialog.h b/gedit/dialogs/gedit-preferences-dialog.h new file mode 100755 index 00000000..bafebb06 --- /dev/null +++ b/gedit/dialogs/gedit-preferences-dialog.h @@ -0,0 +1,87 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-preferences-dialog.c + * This file is part of gedit + * + * 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 gedit Team, 2003. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PREFERENCES_DIALOG_H__ +#define __GEDIT_PREFERENCES_DIALOG_H__ + +#include "gedit-window.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_PREFERENCES_DIALOG (gedit_preferences_dialog_get_type()) +#define GEDIT_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PREFERENCES_DIALOG, GeditPreferencesDialog)) +#define GEDIT_PREFERENCES_DIALOG_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PREFERENCES_DIALOG, GeditPreferencesDialog const)) +#define GEDIT_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_PREFERENCES_DIALOG, GeditPreferencesDialogClass)) +#define GEDIT_IS_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_PREFERENCES_DIALOG)) +#define GEDIT_IS_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PREFERENCES_DIALOG)) +#define GEDIT_PREFERENCES_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_PREFERENCES_DIALOG, GeditPreferencesDialogClass)) + + +/* Private structure type */ +typedef struct _GeditPreferencesDialogPrivate GeditPreferencesDialogPrivate; + +/* + * Main object structure + */ +typedef struct _GeditPreferencesDialog GeditPreferencesDialog; + +struct _GeditPreferencesDialog +{ + GtkDialog dialog; + + /*< private > */ + GeditPreferencesDialogPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditPreferencesDialogClass GeditPreferencesDialogClass; + +struct _GeditPreferencesDialogClass +{ + GtkDialogClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_preferences_dialog_get_type (void) G_GNUC_CONST; + +void gedit_show_preferences_dialog (GeditWindow *parent); + +G_END_DECLS + +#endif /* __GEDIT_PREFERENCES_DIALOG_H__ */ + diff --git a/gedit/dialogs/gedit-preferences-dialog.ui b/gedit/dialogs/gedit-preferences-dialog.ui new file mode 100755 index 00000000..42d41e31 --- /dev/null +++ b/gedit/dialogs/gedit-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 & 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/gedit/dialogs/gedit-search-dialog.c b/gedit/dialogs/gedit-search-dialog.c new file mode 100755 index 00000000..5a059932 --- /dev/null +++ b/gedit/dialogs/gedit-search-dialog.c @@ -0,0 +1,634 @@ +/* + * gedit-search-dialog.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-search-dialog.h" +#include "gedit-history-entry.h" +#include "gedit-utils.h" +#include "gedit-marshal.h" +#include "gedit-dirs.h" + +#define GEDIT_SEARCH_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_SEARCH_DIALOG, \ + GeditSearchDialogPrivate)) + +/* Signals */ +enum +{ + SHOW_REPLACE, + LAST_SIGNAL +}; + +static guint dialog_signals [LAST_SIGNAL] = { 0 }; + +struct _GeditSearchDialogPrivate +{ + 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(GeditSearchDialog, gedit_search_dialog, GTK_TYPE_DIALOG) + +enum +{ + PROP_0, + PROP_SHOW_REPLACE +}; + +static void +gedit_search_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditSearchDialog *dlg = GEDIT_SEARCH_DIALOG (object); + + switch (prop_id) + { + case PROP_SHOW_REPLACE: + gedit_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 +gedit_search_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditSearchDialog *dlg = GEDIT_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 +gedit_search_dialog_present_with_time (GeditSearchDialog *dialog, + guint32 timestamp) +{ + g_return_if_fail (GEDIT_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 (GeditSearchDialog *dlg) +{ + gedit_search_dialog_set_show_replace (dlg, TRUE); + + return TRUE; +} + +static void +gedit_search_dialog_class_init (GeditSearchDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkBindingSet *binding_set; + + object_class->set_property = gedit_search_dialog_set_property; + object_class->get_property = gedit_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 (GeditSearchDialogClass, show_replace), + NULL, NULL, + gedit_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 (GeditSearchDialogPrivate)); + + 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 = gedit_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, + GeditSearchDialog *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), + GEDIT_SEARCH_DIALOG_FIND_RESPONSE, TRUE); + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GEDIT_SEARCH_DIALOG_REPLACE_ALL_RESPONSE, TRUE); + } + else + { + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GEDIT_SEARCH_DIALOG_FIND_RESPONSE, FALSE); + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE, FALSE); + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GEDIT_SEARCH_DIALOG_REPLACE_ALL_RESPONSE, FALSE); + } +} + +static void +response_handler (GeditSearchDialog *dialog, + gint response_id, + gpointer data) +{ + const gchar *str; + + switch (response_id) + { + case GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE: + case GEDIT_SEARCH_DIALOG_REPLACE_ALL_RESPONSE: + str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_text_entry)); + if (*str != '\0') + { + gchar *text; + + text = gedit_utils_unescape_search_text (str); + gedit_history_entry_prepend_text + (GEDIT_HISTORY_ENTRY (dialog->priv->replace_entry), + text); + + g_free (text); + } + /* fall through, so that we also save the find entry */ + case GEDIT_SEARCH_DIALOG_FIND_RESPONSE: + str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->search_text_entry)); + if (*str != '\0') + { + gchar *text; + + text = gedit_utils_unescape_search_text (str); + gedit_history_entry_prepend_text + (GEDIT_HISTORY_ENTRY (dialog->priv->search_entry), + text); + + g_free (text); + } + } +} + +static void +show_replace_widgets (GeditSearchDialog *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 +gedit_search_dialog_init (GeditSearchDialog *dlg) +{ + GtkWidget *content; + GtkWidget *error_widget; + gboolean ret; + gchar *file; + gchar *root_objects[] = { + "search_dialog_content", + NULL + }; + + dlg->priv = GEDIT_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 = gedit_dirs_get_ui_file ("gedit-search-dialog.ui"); + ret = gedit_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 = gedit_history_entry_new ("gedit2_search_for_entry", + TRUE); + gtk_widget_set_size_request (dlg->priv->search_entry, 300, -1); + gedit_history_entry_set_escape_func + (GEDIT_HISTORY_ENTRY (dlg->priv->search_entry), + (GeditHistoryEntryEscapeFunc) gedit_utils_escape_search_text); + + dlg->priv->search_text_entry = gedit_history_entry_get_entry + (GEDIT_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 = gedit_history_entry_new ("gedit2_replace_with_entry", + TRUE); + gedit_history_entry_set_escape_func + (GEDIT_HISTORY_ENTRY (dlg->priv->replace_entry), + (GeditHistoryEntryEscapeFunc) gedit_utils_escape_search_text); + + dlg->priv->replace_text_entry = gedit_history_entry_get_entry + (GEDIT_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 = gedit_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, + GEDIT_SEARCH_DIALOG_REPLACE_ALL_RESPONSE); + gtk_dialog_add_action_widget (GTK_DIALOG (dlg), + dlg->priv->replace_button, + GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE); + gtk_dialog_add_action_widget (GTK_DIALOG (dlg), + dlg->priv->find_button, + GEDIT_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), + GEDIT_SEARCH_DIALOG_FIND_RESPONSE); + + /* insensitive by default */ + gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg), + GEDIT_SEARCH_DIALOG_FIND_RESPONSE, + FALSE); + gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg), + GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE, + FALSE); + gtk_dialog_set_response_sensitive (GTK_DIALOG (dlg), + GEDIT_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 * +gedit_search_dialog_new (GtkWindow *parent, + gboolean show_replace) +{ + GeditSearchDialog *dlg; + + dlg = g_object_new (GEDIT_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 +gedit_search_dialog_get_show_replace (GeditSearchDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog), FALSE); + + return dialog->priv->show_replace; +} + +void +gedit_search_dialog_set_show_replace (GeditSearchDialog *dialog, + gboolean show_replace) +{ + g_return_if_fail (GEDIT_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 +gedit_search_dialog_set_search_text (GeditSearchDialog *dialog, + const gchar *text) +{ + g_return_if_fail (GEDIT_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), + GEDIT_SEARCH_DIALOG_FIND_RESPONSE, + (text != '\0')); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GEDIT_SEARCH_DIALOG_REPLACE_ALL_RESPONSE, + (text != '\0')); +} + +/* + * The text must be unescaped before searching. + */ +const gchar * +gedit_search_dialog_get_search_text (GeditSearchDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog), NULL); + + return gtk_entry_get_text (GTK_ENTRY (dialog->priv->search_text_entry)); +} + +void +gedit_search_dialog_set_replace_text (GeditSearchDialog *dialog, + const gchar *text) +{ + g_return_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog)); + g_return_if_fail (text != NULL); + + gtk_entry_set_text (GTK_ENTRY (dialog->priv->replace_text_entry), + text); +} + +const gchar * +gedit_search_dialog_get_replace_text (GeditSearchDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog), NULL); + + return gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_text_entry)); +} + +void +gedit_search_dialog_set_match_case (GeditSearchDialog *dialog, + gboolean match_case) +{ + g_return_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->match_case_checkbutton), + match_case); +} + +gboolean +gedit_search_dialog_get_match_case (GeditSearchDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog), FALSE); + + return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->match_case_checkbutton)); +} + +void +gedit_search_dialog_set_entire_word (GeditSearchDialog *dialog, + gboolean entire_word) +{ + g_return_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->entire_word_checkbutton), + entire_word); +} + +gboolean +gedit_search_dialog_get_entire_word (GeditSearchDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog), FALSE); + + return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->entire_word_checkbutton)); +} + +void +gedit_search_dialog_set_backwards (GeditSearchDialog *dialog, + gboolean backwards) +{ + g_return_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->backwards_checkbutton), + backwards); +} + +gboolean +gedit_search_dialog_get_backwards (GeditSearchDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog), FALSE); + + return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->backwards_checkbutton)); +} + +void +gedit_search_dialog_set_wrap_around (GeditSearchDialog *dialog, + gboolean wrap_around) +{ + g_return_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->wrap_around_checkbutton), + wrap_around); +} + +gboolean +gedit_search_dialog_get_wrap_around (GeditSearchDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_SEARCH_DIALOG (dialog), FALSE); + + return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->wrap_around_checkbutton)); +} diff --git a/gedit/dialogs/gedit-search-dialog.h b/gedit/dialogs/gedit-search-dialog.h new file mode 100755 index 00000000..a225be14 --- /dev/null +++ b/gedit/dialogs/gedit-search-dialog.h @@ -0,0 +1,128 @@ +/* + * gedit-search-dialog.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_SEARCH_DIALOG_H__ +#define __GEDIT_SEARCH_DIALOG_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_SEARCH_DIALOG (gedit_search_dialog_get_type()) +#define GEDIT_SEARCH_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_SEARCH_DIALOG, GeditSearchDialog)) +#define GEDIT_SEARCH_DIALOG_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_SEARCH_DIALOG, GeditSearchDialog const)) +#define GEDIT_SEARCH_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_SEARCH_DIALOG, GeditSearchDialogClass)) +#define GEDIT_IS_SEARCH_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_SEARCH_DIALOG)) +#define GEDIT_IS_SEARCH_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SEARCH_DIALOG)) +#define GEDIT_SEARCH_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_SEARCH_DIALOG, GeditSearchDialogClass)) + +/* Private structure type */ +typedef struct _GeditSearchDialogPrivate GeditSearchDialogPrivate; + +/* + * Main object structure + */ +typedef struct _GeditSearchDialog GeditSearchDialog; + +struct _GeditSearchDialog +{ + GtkDialog dialog; + + /*< private > */ + GeditSearchDialogPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditSearchDialogClass GeditSearchDialogClass; + +struct _GeditSearchDialogClass +{ + GtkDialogClass parent_class; + + /* Key bindings */ + gboolean (* show_replace) (GeditSearchDialog *dlg); +}; + +enum +{ + GEDIT_SEARCH_DIALOG_FIND_RESPONSE = 100, + GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE, + GEDIT_SEARCH_DIALOG_REPLACE_ALL_RESPONSE +}; + +/* + * Public methods + */ +GType gedit_search_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_search_dialog_new (GtkWindow *parent, + gboolean show_replace); + +void gedit_search_dialog_present_with_time (GeditSearchDialog *dialog, + guint32 timestamp); + +gboolean gedit_search_dialog_get_show_replace (GeditSearchDialog *dialog); + +void gedit_search_dialog_set_show_replace (GeditSearchDialog *dialog, + gboolean show_replace); + + +void gedit_search_dialog_set_search_text (GeditSearchDialog *dialog, + const gchar *text); +const gchar *gedit_search_dialog_get_search_text (GeditSearchDialog *dialog); + +void gedit_search_dialog_set_replace_text (GeditSearchDialog *dialog, + const gchar *text); +const gchar *gedit_search_dialog_get_replace_text (GeditSearchDialog *dialog); + +void gedit_search_dialog_set_match_case (GeditSearchDialog *dialog, + gboolean match_case); +gboolean gedit_search_dialog_get_match_case (GeditSearchDialog *dialog); + +void gedit_search_dialog_set_entire_word (GeditSearchDialog *dialog, + gboolean entire_word); +gboolean gedit_search_dialog_get_entire_word (GeditSearchDialog *dialog); + +void gedit_search_dialog_set_backwards (GeditSearchDialog *dialog, + gboolean backwards); +gboolean gedit_search_dialog_get_backwards (GeditSearchDialog *dialog); + +void gedit_search_dialog_set_wrap_around (GeditSearchDialog *dialog, + gboolean wrap_around); +gboolean gedit_search_dialog_get_wrap_around (GeditSearchDialog *dialog); + +G_END_DECLS + +#endif /* __GEDIT_SEARCH_DIALOG_H__ */ diff --git a/gedit/dialogs/gedit-search-dialog.ui b/gedit/dialogs/gedit-search-dialog.ui new file mode 100755 index 00000000..35b6c390 --- /dev/null +++ b/gedit/dialogs/gedit-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/gedit/gedit-app.c b/gedit/gedit-app.c new file mode 100755 index 00000000..14148fef --- /dev/null +++ b/gedit/gedit-app.c @@ -0,0 +1,911 @@ +/* + * gedit-app.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-app.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-commands.h" +#include "gedit-notebook.h" +#include "gedit-debug.h" +#include "gedit-utils.h" +#include "gedit-enum-types.h" +#include "gedit-dirs.h" + +#ifdef OS_OSX +#include <ige-mac-integration.h> +#endif + +#define GEDIT_PAGE_SETUP_FILE "gedit-page-setup" +#define GEDIT_PRINT_SETTINGS_FILE "gedit-print-settings" + +#define GEDIT_APP_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_APP, GeditAppPrivate)) + +/* Properties */ +enum +{ + PROP_0, + PROP_LOCKDOWN +}; + +struct _GeditAppPrivate +{ + GList *windows; + GeditWindow *active_window; + + GeditLockdownMask lockdown; + + GtkPageSetup *page_setup; + GtkPrintSettings *print_settings; +}; + +G_DEFINE_TYPE(GeditApp, gedit_app, G_TYPE_OBJECT) + +static void +gedit_app_finalize (GObject *object) +{ + GeditApp *app = GEDIT_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 (gedit_app_parent_class)->finalize (object); +} + +static void +gedit_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditApp *app = GEDIT_APP (object); + + switch (prop_id) + { + case PROP_LOCKDOWN: + g_value_set_flags (value, gedit_app_get_lockdown (app)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_app_class_init (GeditAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_app_finalize; + object_class->get_property = gedit_app_get_property; + + g_object_class_install_property (object_class, + PROP_LOCKDOWN, + g_param_spec_flags ("lockdown", + "Lockdown", + "The lockdown mask", + GEDIT_TYPE_LOCKDOWN_MASK, + 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (object_class, sizeof(GeditAppPrivate)); +} + +static gboolean +ensure_user_config_dir (void) +{ + gchar *config_dir; + gboolean ret = TRUE; + gint res; + + config_dir = gedit_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 = gedit_dirs_get_user_accels_file (); + if (filename != NULL) + { + gedit_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 = gedit_dirs_get_user_accels_file (); + if (filename != NULL) + { + gedit_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 = gedit_dirs_get_user_config_dir (); + + if (config_dir != NULL) + { + setup = g_build_filename (config_dir, + GEDIT_PAGE_SETUP_FILE, + NULL); + g_free (config_dir); + } + + return setup; +} + +static void +load_page_setup (GeditApp *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 (GeditApp *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 = gedit_dirs_get_user_config_dir (); + + if (config_dir != NULL) + { + settings = g_build_filename (config_dir, + GEDIT_PRINT_SETTINGS_FILE, + NULL); + g_free (config_dir); + } + + return settings; +} + +static void +load_print_settings (GeditApp *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 (GeditApp *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 +gedit_app_init (GeditApp *app) +{ + app->priv = GEDIT_APP_GET_PRIVATE (app); + + load_accels (); + + /* initial lockdown state */ + app->priv->lockdown = gedit_prefs_manager_get_lockdown (); +} + +static void +app_weak_notify (gpointer data, + GObject *where_the_app_was) +{ + gtk_main_quit (); +} + +/** + * gedit_app_get_default: + * + * Returns the #GeditApp object. This object is a singleton and + * represents the running gedit instance. + * + * Return value: the #GeditApp pointer + */ +GeditApp * +gedit_app_get_default (void) +{ + static GeditApp *app = NULL; + + if (app != NULL) + return app; + + app = GEDIT_APP (g_object_new (GEDIT_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 (GeditApp *app, + GeditWindow *window) +{ + app->priv->active_window = window; +} + +static gboolean +window_focus_in_event (GeditWindow *window, + GdkEventFocus *event, + GeditApp *app) +{ + /* updates active_view and active_child when a new toplevel receives focus */ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), FALSE); + + set_active_window (app, window); + + return FALSE; +} + +static gboolean +window_delete_event (GeditWindow *window, + GdkEvent *event, + GeditApp *app) +{ + GeditWindowState ws; + + ws = gedit_window_get_state (window); + + if (ws & + (GEDIT_WINDOW_STATE_SAVING | + GEDIT_WINDOW_STATE_PRINTING | + GEDIT_WINDOW_STATE_SAVING_SESSION)) + return TRUE; + + _gedit_cmd_file_quit (NULL, window); + + /* Do not destroy the window */ + return TRUE; +} + +static void +window_destroy (GeditWindow *window, + GeditApp *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), "gedit-is-quitting-all"))) + { + /* Create hidden proxy window on OS X to handle the menu */ + gedit_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 ("gedit-window-%ld-%ld-%d-%s", + result.tv_sec, + result.tv_usec, + serial++, + g_get_host_name ()); +} + +static GeditWindow * +gedit_app_create_window_real (GeditApp *app, + gboolean set_geometry, + const gchar *role) +{ + GeditWindow *window; + + gedit_debug (DEBUG_APP); + + /* + * We need to be careful here, there is a race condition: + * when another gedit 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 (GEDIT_TYPE_WINDOW, NULL); + set_active_window (app, window); + } + else + { + window = g_object_new (GEDIT_TYPE_WINDOW, NULL); + } + + app->priv->windows = g_list_prepend (app->priv->windows, + window); + + gedit_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 = gedit_prefs_manager_get_window_state (); + + if ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0) + { + gedit_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 + { + gedit_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; +} + +/** + * gedit_app_create_window: + * @app: the #GeditApp + * + * Create a new #GeditWindow part of @app. + * + * Return value: the new #GeditWindow + */ +GeditWindow * +gedit_app_create_window (GeditApp *app, + GdkScreen *screen) +{ + GeditWindow *window; + + window = gedit_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. + */ +GeditWindow * +_gedit_app_restore_window (GeditApp *app, + const gchar *role) +{ + GeditWindow *window; + + window = gedit_app_create_window_real (app, FALSE, role); + + return window; +} + +/** + * gedit_app_get_windows: + * @app: the #GeditApp + * + * Returns all the windows currently present in #GeditApp. + * + * Return value: the list of #GeditWindows objects. The list + * should not be freed + */ +const GList * +gedit_app_get_windows (GeditApp *app) +{ + g_return_val_if_fail (GEDIT_IS_APP (app), NULL); + + return app->priv->windows; +} + +/** + * gedit_app_get_active_window: + * @app: the #GeditApp + * + * Retrives the #GeditWindow currently active. + * + * Return value: the active #GeditWindow + */ +GeditWindow * +gedit_app_get_active_window (GeditApp *app) +{ + g_return_val_if_fail (GEDIT_IS_APP (app), NULL); + + /* make sure our active window is always realized: + * this is needed on startup if we launch two gedit 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 (GeditWindow *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 = gedit_utils_get_window_workspace (GTK_WINDOW (window)); + if (ws != workspace && ws != GEDIT_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 + + gedit_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; +} + +/** + * _gedit_app_get_window_in_viewport + * @app: the #GeditApp + * @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 #GeditWindow in + * the given viewport of the given workspace. + * + * Return value: the #GeditWindow in the given viewport of the given workspace. + */ +GeditWindow * +_gedit_app_get_window_in_viewport (GeditApp *app, + GdkScreen *screen, + gint workspace, + gint viewport_x, + gint viewport_y) +{ + GeditWindow *window; + + GList *l; + + g_return_val_if_fail (GEDIT_IS_APP (app), NULL); + + /* first try if the active window */ + window = app->priv->active_window; + + g_return_val_if_fail (GEDIT_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 gedit_app_create_window (app, screen); +} + +/** + * gedit_app_get_documents: + * @app: the #GeditApp + * + * Returns all the documents currently open in #GeditApp. + * + * Return value: a newly allocated list of #GeditDocument objects + */ +GList * +gedit_app_get_documents (GeditApp *app) +{ + GList *res = NULL; + GList *windows; + + g_return_val_if_fail (GEDIT_IS_APP (app), NULL); + + windows = app->priv->windows; + + while (windows != NULL) + { + res = g_list_concat (res, + gedit_window_get_documents (GEDIT_WINDOW (windows->data))); + + windows = g_list_next (windows); + } + + return res; +} + +/** + * gedit_app_get_views: + * @app: the #GeditApp + * + * Returns all the views currently present in #GeditApp. + * + * Return value: a newly allocated list of #GeditView objects + */ +GList * +gedit_app_get_views (GeditApp *app) +{ + GList *res = NULL; + GList *windows; + + g_return_val_if_fail (GEDIT_IS_APP (app), NULL); + + windows = app->priv->windows; + + while (windows != NULL) + { + res = g_list_concat (res, + gedit_window_get_views (GEDIT_WINDOW (windows->data))); + + windows = g_list_next (windows); + } + + return res; +} + +/** + * gedit_app_get_lockdown: + * @app: a #GeditApp + * + * Gets the lockdown mask (see #GeditLockdownMask) for the application. + * The lockdown mask determines which functions are locked down using + * the MATE-wise lockdown MateConf keys. + **/ +GeditLockdownMask +gedit_app_get_lockdown (GeditApp *app) +{ + g_return_val_if_fail (GEDIT_IS_APP (app), GEDIT_LOCKDOWN_ALL); + + return app->priv->lockdown; +} + +static void +app_lockdown_changed (GeditApp *app) +{ + GList *l; + + for (l = app->priv->windows; l != NULL; l = l->next) + _gedit_window_set_lockdown (GEDIT_WINDOW (l->data), + app->priv->lockdown); + + g_object_notify (G_OBJECT (app), "lockdown"); +} + +void +_gedit_app_set_lockdown (GeditApp *app, + GeditLockdownMask lockdown) +{ + g_return_if_fail (GEDIT_IS_APP (app)); + + app->priv->lockdown = lockdown; + + app_lockdown_changed (app); +} + +void +_gedit_app_set_lockdown_bit (GeditApp *app, + GeditLockdownMask bit, + gboolean value) +{ + g_return_if_fail (GEDIT_IS_APP (app)); + + if (value) + app->priv->lockdown |= bit; + else + app->priv->lockdown &= ~bit; + + app_lockdown_changed (app); +} + +/* Returns a copy */ +GtkPageSetup * +_gedit_app_get_default_page_setup (GeditApp *app) +{ + g_return_val_if_fail (GEDIT_IS_APP (app), NULL); + + if (app->priv->page_setup == NULL) + load_page_setup (app); + + return gtk_page_setup_copy (app->priv->page_setup); +} + +void +_gedit_app_set_default_page_setup (GeditApp *app, + GtkPageSetup *page_setup) +{ + g_return_if_fail (GEDIT_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 * +_gedit_app_get_default_print_settings (GeditApp *app) +{ + g_return_val_if_fail (GEDIT_IS_APP (app), NULL); + + if (app->priv->print_settings == NULL) + load_print_settings (app); + + return gtk_print_settings_copy (app->priv->print_settings); +} + +void +_gedit_app_set_default_print_settings (GeditApp *app, + GtkPrintSettings *settings) +{ + g_return_if_fail (GEDIT_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/gedit/gedit-app.h b/gedit/gedit-app.h new file mode 100755 index 00000000..5311930a --- /dev/null +++ b/gedit/gedit-app.h @@ -0,0 +1,142 @@ +/* + * gedit-app.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_APP_H__ +#define __GEDIT_APP_H__ + +#include <gtk/gtk.h> + +#include <gedit/gedit-window.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_APP (gedit_app_get_type()) +#define GEDIT_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_APP, GeditApp)) +#define GEDIT_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_APP, GeditAppClass)) +#define GEDIT_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_APP)) +#define GEDIT_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_APP)) +#define GEDIT_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_APP, GeditAppClass)) + +/* Private structure type */ +typedef struct _GeditAppPrivate GeditAppPrivate; + +/* + * Main object structure + */ +typedef struct _GeditApp GeditApp; + +struct _GeditApp +{ + GObject object; + + /*< private > */ + GeditAppPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditAppClass GeditAppClass; + +struct _GeditAppClass +{ + GObjectClass parent_class; +}; + +/* + * Lockdown mask definition + */ +typedef enum +{ + GEDIT_LOCKDOWN_COMMAND_LINE = 1 << 0, + GEDIT_LOCKDOWN_PRINTING = 1 << 1, + GEDIT_LOCKDOWN_PRINT_SETUP = 1 << 2, + GEDIT_LOCKDOWN_SAVE_TO_DISK = 1 << 3, + GEDIT_LOCKDOWN_ALL = 0xF +} GeditLockdownMask; + +/* + * Public methods + */ +GType gedit_app_get_type (void) G_GNUC_CONST; + +GeditApp *gedit_app_get_default (void); + +GeditWindow *gedit_app_create_window (GeditApp *app, + GdkScreen *screen); + +const GList *gedit_app_get_windows (GeditApp *app); +GeditWindow *gedit_app_get_active_window (GeditApp *app); + +/* Returns a newly allocated list with all the documents */ +GList *gedit_app_get_documents (GeditApp *app); + +/* Returns a newly allocated list with all the views */ +GList *gedit_app_get_views (GeditApp *app); + +/* Lockdown state */ +GeditLockdownMask gedit_app_get_lockdown (GeditApp *app); + +/* + * Non exported functions + */ +GeditWindow *_gedit_app_restore_window (GeditApp *app, + const gchar *role); +GeditWindow *_gedit_app_get_window_in_viewport (GeditApp *app, + GdkScreen *screen, + gint workspace, + gint viewport_x, + gint viewport_y); +void _gedit_app_set_lockdown (GeditApp *app, + GeditLockdownMask lockdown); +void _gedit_app_set_lockdown_bit (GeditApp *app, + GeditLockdownMask bit, + gboolean value); +/* + * This one is a gedit-window function, but we declare it here to avoid + * #include headaches since it needs the GeditLockdownMask declaration. + */ +void _gedit_window_set_lockdown (GeditWindow *window, + GeditLockdownMask lockdown); + +/* global print config */ +GtkPageSetup *_gedit_app_get_default_page_setup (GeditApp *app); +void _gedit_app_set_default_page_setup (GeditApp *app, + GtkPageSetup *page_setup); +GtkPrintSettings *_gedit_app_get_default_print_settings (GeditApp *app); +void _gedit_app_set_default_print_settings (GeditApp *app, + GtkPrintSettings *settings); + +G_END_DECLS + +#endif /* __GEDIT_APP_H__ */ diff --git a/gedit/gedit-close-button.c b/gedit/gedit-close-button.c new file mode 100755 index 00000000..0e9f157e --- /dev/null +++ b/gedit/gedit-close-button.c @@ -0,0 +1,80 @@ +/* + * gedit-close-button.c + * This file is part of gedit + * + * 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 "gedit-close-button.h" + +G_DEFINE_TYPE (GeditCloseButton, gedit_close_button, GTK_TYPE_BUTTON) + +static void +gedit_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 (gedit_close_button_parent_class)->style_set (button, previous_style); +} + +static void +gedit_close_button_class_init (GeditCloseButtonClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->style_set = gedit_close_button_style_set; +} + +static void +gedit_close_button_init (GeditCloseButton *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 * +gedit_close_button_new () +{ + GeditCloseButton *button; + + button = g_object_new (GEDIT_TYPE_CLOSE_BUTTON, + "relief", GTK_RELIEF_NONE, + "focus-on-click", FALSE, + NULL); + + return GTK_WIDGET (button); +} + diff --git a/gedit/gedit-close-button.h b/gedit/gedit-close-button.h new file mode 100755 index 00000000..4f631380 --- /dev/null +++ b/gedit/gedit-close-button.h @@ -0,0 +1,56 @@ +/* + * gedit-close-button.h + * This file is part of gedit + * + * 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 __GEDIT_CLOSE_BUTTON_H__ +#define __GEDIT_CLOSE_BUTTON_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_CLOSE_BUTTON (gedit_close_button_get_type ()) +#define GEDIT_CLOSE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_CLOSE_BUTTON, GeditCloseButton)) +#define GEDIT_CLOSE_BUTTON_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_CLOSE_BUTTON, GeditCloseButton const)) +#define GEDIT_CLOSE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_CLOSE_BUTTON, GeditCloseButtonClass)) +#define GEDIT_IS_CLOSE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_CLOSE_BUTTON)) +#define GEDIT_IS_CLOSE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_CLOSE_BUTTON)) +#define GEDIT_CLOSE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_CLOSE_BUTTON, GeditCloseButtonClass)) + +typedef struct _GeditCloseButton GeditCloseButton; +typedef struct _GeditCloseButtonClass GeditCloseButtonClass; +typedef struct _GeditCloseButtonPrivate GeditCloseButtonPrivate; + +struct _GeditCloseButton { + GtkButton parent; +}; + +struct _GeditCloseButtonClass { + GtkButtonClass parent_class; +}; + +GType gedit_close_button_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_close_button_new (void); + +G_END_DECLS + +#endif /* __GEDIT_CLOSE_BUTTON_H__ */ diff --git a/gedit/gedit-commands-documents.c b/gedit/gedit-commands-documents.c new file mode 100755 index 00000000..016ebbe8 --- /dev/null +++ b/gedit/gedit-commands-documents.c @@ -0,0 +1,87 @@ +/* + * gedit-documents-commands.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> + +#include "gedit-commands.h" +#include "gedit-window.h" +#include "gedit-notebook.h" +#include "gedit-debug.h" + +void +_gedit_cmd_documents_previous_document (GtkAction *action, + GeditWindow *window) +{ + GtkNotebook *notebook; + + gedit_debug (DEBUG_COMMANDS); + + notebook = GTK_NOTEBOOK (_gedit_window_get_notebook (window)); + gtk_notebook_prev_page (notebook); +} + +void +_gedit_cmd_documents_next_document (GtkAction *action, + GeditWindow *window) +{ + GtkNotebook *notebook; + + gedit_debug (DEBUG_COMMANDS); + + notebook = GTK_NOTEBOOK (_gedit_window_get_notebook (window)); + gtk_notebook_next_page (notebook); +} + +void +_gedit_cmd_documents_move_to_new_window (GtkAction *action, + GeditWindow *window) +{ + GeditNotebook *old_notebook; + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_window_get_active_tab (window); + + if (tab == NULL) + return; + + old_notebook = GEDIT_NOTEBOOK (_gedit_window_get_notebook (window)); + + g_return_if_fail (gtk_notebook_get_n_pages (GTK_NOTEBOOK (old_notebook)) > 1); + + _gedit_window_move_tab_to_new_window (window, tab); +} diff --git a/gedit/gedit-commands-edit.c b/gedit/gedit-commands-edit.c new file mode 100755 index 00000000..e2cedeb6 --- /dev/null +++ b/gedit/gedit-commands-edit.c @@ -0,0 +1,174 @@ +/* + * gedit-commands-edit.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> + +#include "gedit-commands.h" +#include "gedit-window.h" +#include "gedit-debug.h" +#include "gedit-view.h" +#include "dialogs/gedit-preferences-dialog.h" + +void +_gedit_cmd_edit_undo (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + GtkSourceBuffer *active_document; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_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); + + gedit_view_scroll_to_cursor (active_view); + + gtk_widget_grab_focus (GTK_WIDGET (active_view)); +} + +void +_gedit_cmd_edit_redo (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + GtkSourceBuffer *active_document; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_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); + + gedit_view_scroll_to_cursor (active_view); + + gtk_widget_grab_focus (GTK_WIDGET (active_view)); +} + +void +_gedit_cmd_edit_cut (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_window_get_active_view (window); + g_return_if_fail (active_view); + + gedit_view_cut_clipboard (active_view); + + gtk_widget_grab_focus (GTK_WIDGET (active_view)); +} + +void +_gedit_cmd_edit_copy (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_window_get_active_view (window); + g_return_if_fail (active_view); + + gedit_view_copy_clipboard (active_view); + + gtk_widget_grab_focus (GTK_WIDGET (active_view)); +} + +void +_gedit_cmd_edit_paste (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_window_get_active_view (window); + g_return_if_fail (active_view); + + gedit_view_paste_clipboard (active_view); + + gtk_widget_grab_focus (GTK_WIDGET (active_view)); +} + +void +_gedit_cmd_edit_delete (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_window_get_active_view (window); + g_return_if_fail (active_view); + + gedit_view_delete_selection (active_view); + + gtk_widget_grab_focus (GTK_WIDGET (active_view)); +} + +void +_gedit_cmd_edit_select_all (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_window_get_active_view (window); + g_return_if_fail (active_view); + + gedit_view_select_all (active_view); + + gtk_widget_grab_focus (GTK_WIDGET (active_view)); +} + +void +_gedit_cmd_edit_preferences (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + gedit_show_preferences_dialog (window); +} diff --git a/gedit/gedit-commands-file-print.c b/gedit/gedit-commands-file-print.c new file mode 100755 index 00000000..455e2e21 --- /dev/null +++ b/gedit/gedit-commands-file-print.c @@ -0,0 +1,91 @@ +/* + * gedit-commands-file-print.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-commands.h" +#include "gedit-window.h" +#include "gedit-tab.h" +#include "gedit-debug.h" + +#if !GTK_CHECK_VERSION (2, 17, 4) +void +_gedit_cmd_file_page_setup (GtkAction *action, + GeditWindow *window) +{ + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_window_get_active_tab (window); + if (tab == NULL) + return; + + _gedit_tab_page_setup (tab); +} +#endif + +void +_gedit_cmd_file_print_preview (GtkAction *action, + GeditWindow *window) +{ + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_window_get_active_tab (window); + if (tab == NULL) + return; + + _gedit_tab_print_preview (tab); +} + +void +_gedit_cmd_file_print (GtkAction *action, + GeditWindow *window) +{ + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_window_get_active_tab (window); + if (tab == NULL) + return; + + _gedit_tab_print (tab); +} + diff --git a/gedit/gedit-commands-file.c b/gedit/gedit-commands-file.c new file mode 100755 index 00000000..985b0417 --- /dev/null +++ b/gedit/gedit-commands-file.c @@ -0,0 +1,1885 @@ +/* + * gedit-commands-file.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-commands.h" +#include "gedit-window.h" +#include "gedit-window-private.h" +#include "gedit-statusbar.h" +#include "gedit-debug.h" +#include "gedit-utils.h" +#include "gedit-file-chooser-dialog.h" +#include "dialogs/gedit-close-confirmation-dialog.h" + + +/* Defined constants */ +#define GEDIT_OPEN_DIALOG_KEY "gedit-open-dialog-key" +#define GEDIT_TAB_TO_SAVE_AS "gedit-tab-to-save-as" +#define GEDIT_LIST_OF_TABS_TO_SAVE_AS "gedit-list-of-tabs-to-save-as" +#define GEDIT_IS_CLOSING_ALL "gedit-is-closing-all" +#define GEDIT_IS_QUITTING "gedit-is-quitting" +#define GEDIT_IS_CLOSING_TAB "gedit-is-closing-tab" +#define GEDIT_IS_QUITTING_ALL "gedit-is-quitting-all" + +static void tab_state_changed_while_saving (GeditTab *tab, + GParamSpec *pspec, + GeditWindow *window); + +void +_gedit_cmd_file_new (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + gedit_window_create_tab (window, TRUE); +} + +static GeditTab * +get_tab_from_file (GList *docs, GFile *file) +{ + GeditTab *tab = NULL; + + while (docs != NULL) + { + GeditDocument *d; + GFile *l; + + d = GEDIT_DOCUMENT (docs->data); + + l = gedit_document_get_location (d); + if (l != NULL) + { + if (g_file_equal (l, file)) + { + tab = gedit_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 (GeditWindow *window, + GSList *files, + const GeditEncoding *encoding, + gint line_pos, + gboolean create) +{ + GeditTab *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; + + gedit_debug (DEBUG_COMMANDS); + + win_docs = gedit_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) + { + gedit_window_set_active_tab (window, tab); + jump_to = FALSE; + + if (line_pos > 0) + { + GeditDocument *doc; + GeditView *view; + + doc = gedit_tab_get_document (tab); + view = gedit_tab_get_view (tab); + + /* document counts lines starting from 0 */ + gedit_document_goto_line (doc, line_pos - 1); + gedit_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 = gedit_window_get_active_tab (window); + if (tab != NULL) + { + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + if (gedit_document_is_untouched (doc) && + (gedit_tab_get_state (tab) == GEDIT_TAB_STATE_NORMAL)) + { + gchar *uri; + + // FIXME: pass the GFile to tab when api is there + uri = g_file_get_uri (l->data); + _gedit_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 = gedit_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) + { + GeditDocument *doc; + gchar *uri_for_display; + + g_return_val_if_fail (tab != NULL, loaded_files); + + doc = gedit_tab_get_document (tab); + uri_for_display = gedit_document_get_uri_for_display (doc); + + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->generic_message_cid, + _("Loading file '%s'\342\200\246"), + uri_for_display); + + g_free (uri_for_display); + } + else + { + gedit_statusbar_flash_message (GEDIT_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 (GeditWindow *window, + const GSList *uris, + const GeditEncoding *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 (gedit_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; +} + +/** + * gedit_commands_load_uri: + * + * Do nothing if URI does not exist + */ +void +gedit_commands_load_uri (GeditWindow *window, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos) +{ + GSList *uris = NULL; + + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (uri != NULL); + g_return_if_fail (gedit_utils_is_valid_uri (uri)); + + gedit_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); +} + +/** + * gedit_commands_load_uris: + * + * Ignore non-existing URIs + */ +gint +gedit_commands_load_uris (GeditWindow *window, + const GSList *uris, + const GeditEncoding *encoding, + gint line_pos) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), 0); + g_return_val_if_fail ((uris != NULL) && (uris->data != NULL), 0); + + gedit_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 +gedit_commands_load_files (GeditWindow *window, + GSList *files, + const GeditEncoding *encoding, + gint line_pos) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), 0); + g_return_val_if_fail ((files != NULL) && (files->data != NULL), 0); + + gedit_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 +_gedit_cmd_load_files_from_prompt (GeditWindow *window, + GSList *files, + const GeditEncoding *encoding, + gint line_pos) +{ + gedit_debug (DEBUG_COMMANDS); + + return load_file_list (window, files, encoding, line_pos, TRUE); +} + +static void +open_dialog_destroyed (GeditWindow *window, + GeditFileChooserDialog *dialog) +{ + gedit_debug (DEBUG_COMMANDS); + + g_object_set_data (G_OBJECT (window), + GEDIT_OPEN_DIALOG_KEY, + NULL); +} + +static void +open_dialog_response_cb (GeditFileChooserDialog *dialog, + gint response_id, + GeditWindow *window) +{ + GSList *files; + const GeditEncoding *encoding; + + gedit_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 = gedit_file_chooser_dialog_get_encoding (dialog); + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + /* Remember the folder we navigated to */ + _gedit_window_set_default_location (window, files->data); + + gedit_commands_load_files (window, + files, + encoding, + 0); + + g_slist_foreach (files, (GFunc) g_object_unref, NULL); + g_slist_free (files); +} + +void +_gedit_cmd_file_open (GtkAction *action, + GeditWindow *window) +{ + GtkWidget *open_dialog; + gpointer data; + GeditDocument *doc; + GFile *default_path = NULL; + + gedit_debug (DEBUG_COMMANDS); + + data = g_object_get_data (G_OBJECT (window), GEDIT_OPEN_DIALOG_KEY); + + if (data != NULL) + { + g_return_if_fail (GEDIT_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 = gedit_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), + GEDIT_OPEN_DIALOG_KEY, + open_dialog); + + g_object_weak_ref (G_OBJECT (open_dialog), + (GWeakNotify) open_dialog_destroyed, + window); + + /* Set the curret folder uri */ + doc = gedit_window_get_active_document (window); + if (doc != NULL) + { + GFile *file; + + file = gedit_document_get_location (doc); + + if (file != NULL) + { + default_path = g_file_get_parent (file); + g_object_unref (file); + } + } + + if (default_path == NULL) + default_path = _gedit_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 (GeditTab *tab, GeditWindow *window); + +static gboolean +is_read_only (GFile *location) +{ + gboolean ret = TRUE; /* default to read only */ + GFileInfo *info; + + gedit_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; + + gedit_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 = gedit_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); + + gedit_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 (GeditFileChooserDialog *dialog, + gint response_id, + GeditWindow *window) +{ + GFile *file; + const GeditEncoding *encoding; + GeditTab *tab; + gpointer data; + GSList *tabs_to_save_as; + GeditDocumentNewlineType newline_type; + + gedit_debug (DEBUG_COMMANDS); + + tab = GEDIT_TAB (g_object_get_data (G_OBJECT (dialog), + GEDIT_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 = gedit_file_chooser_dialog_get_encoding (dialog); + newline_type = gedit_file_chooser_dialog_get_newline_type (dialog); + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + if (tab != NULL) + { + GeditDocument *doc; + gchar *parse_name; + gchar *uri; + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + parse_name = g_file_get_parse_name (file); + + gedit_statusbar_flash_message (GEDIT_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... */ + _gedit_window_set_default_location (window, file); + + // FIXME: pass the GFile to tab when api is there + uri = g_file_get_uri (file); + _gedit_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), + GEDIT_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 == GEDIT_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), + GEDIT_LIST_OF_TABS_TO_SAVE_AS, + tabs_to_save_as); + + if (tabs_to_save_as != NULL) + { + tab = GEDIT_TAB (tabs_to_save_as->data); + + if (GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (tab), + GEDIT_IS_CLOSING_TAB)) == TRUE) + { + g_object_set_data (G_OBJECT (tab), + GEDIT_IS_CLOSING_TAB, + NULL); + + /* Trace tab state changes */ + g_signal_connect (tab, + "notify::state", + G_CALLBACK (tab_state_changed_while_saving), + window); + } + + gedit_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; + + gedit_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 (GeditTab *tab, + GeditWindow *window) +{ + GtkWidget *save_dialog; + GtkWindowGroup *wg; + GeditDocument *doc; + GFile *file; + gboolean uri_set = FALSE; + const GeditEncoding *encoding; + GeditDocumentNewlineType newline_type; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + gedit_debug (DEBUG_COMMANDS); + + save_dialog = gedit_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 = gedit_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 = gedit_tab_get_document (tab); + file = gedit_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 = _gedit_window_get_default_location (window); + docname = gedit_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 = gedit_document_get_encoding (doc); + g_return_if_fail (encoding != NULL); + + newline_type = gedit_document_get_newline_type (doc); + + gedit_file_chooser_dialog_set_encoding (GEDIT_FILE_CHOOSER_DIALOG (save_dialog), + encoding); + + gedit_file_chooser_dialog_set_newline_type (GEDIT_FILE_CHOOSER_DIALOG (save_dialog), + newline_type); + + g_object_set_data (G_OBJECT (save_dialog), + GEDIT_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 (GeditTab *tab, + GeditWindow *window) +{ + GeditDocument *doc; + gchar *uri_for_display; + + gedit_debug (DEBUG_COMMANDS); + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + if (gedit_document_is_untitled (doc) || + gedit_document_get_readonly (doc)) + { + gedit_debug_message (DEBUG_COMMANDS, "Untitled or Readonly"); + + file_save_as (tab, window); + + return; + } + + uri_for_display = gedit_document_get_uri_for_display (doc); + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->generic_message_cid, + _("Saving file '%s'\342\200\246"), + uri_for_display); + + g_free (uri_for_display); + + _gedit_tab_save (tab); +} + +void +_gedit_cmd_file_save (GtkAction *action, + GeditWindow *window) +{ + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_window_get_active_tab (window); + if (tab == NULL) + return; + + file_save (tab, window); +} + +void +_gedit_cmd_file_save_as (GtkAction *action, + GeditWindow *window) +{ + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_window_get_active_tab (window); + if (tab == NULL) + return; + + file_save_as (tab, window); +} + +static gboolean +document_needs_saving (GeditDocument *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 (gedit_document_is_local (doc) && gedit_document_get_deleted (doc)) + return TRUE; + + return FALSE; +} + +/* + * The docs in the list must belong to the same GeditWindow. + */ +void +_gedit_cmd_file_save_documents_list (GeditWindow *window, + GList *docs) +{ + GList *l; + GSList *tabs_to_save_as = NULL; + + gedit_debug (DEBUG_COMMANDS); + + g_return_if_fail (!(gedit_window_get_state (window) & + (GEDIT_WINDOW_STATE_PRINTING | + GEDIT_WINDOW_STATE_SAVING_SESSION))); + + l = docs; + while (l != NULL) + { + GeditDocument *doc; + GeditTab *t; + GeditTabState state; + + g_return_if_fail (GEDIT_IS_DOCUMENT (l->data)); + + doc = GEDIT_DOCUMENT (l->data); + t = gedit_tab_get_from_document (doc); + state = gedit_tab_get_state (t); + + g_return_if_fail (state != GEDIT_TAB_STATE_PRINTING); + g_return_if_fail (state != GEDIT_TAB_STATE_PRINT_PREVIEWING); + g_return_if_fail (state != GEDIT_TAB_STATE_CLOSING); + + if ((state == GEDIT_TAB_STATE_NORMAL) || + (state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) || + (state == GEDIT_TAB_STATE_GENERIC_NOT_EDITABLE)) + { + /* FIXME: manage the case of local readonly files owned by the + user is running gedit - Paolo (Dec. 8, 2005) */ + if (gedit_document_is_untitled (doc) || + gedit_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: + - GEDIT_TAB_STATE_LOADING: we do not save since we are sure the file is unmodified + - GEDIT_TAB_STATE_REVERTING: we do not save since the user wants + to return back to the version of the file she previously saved + - GEDIT_TAB_STATE_SAVING: well, we are already saving (no need to save again) + - GEDIT_TAB_STATE_PRINTING, GEDIT_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. + - GEDIT_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) + - GEDIT_TAB_STATE_LOADING_ERROR: there is nothing to save + - GEDIT_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 + - GEDIT_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 + - GEDIT_TAB_STATE_CLOSING: this state is invalid in this case + */ + + gchar *uri_for_display; + + uri_for_display = gedit_document_get_uri_for_display (doc); + gedit_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) + { + GeditTab *tab; + + tabs_to_save_as = g_slist_reverse (tabs_to_save_as ); + + g_return_if_fail (g_object_get_data (G_OBJECT (window), + GEDIT_LIST_OF_TABS_TO_SAVE_AS) == NULL); + + g_object_set_data (G_OBJECT (window), + GEDIT_LIST_OF_TABS_TO_SAVE_AS, + tabs_to_save_as); + + tab = GEDIT_TAB (tabs_to_save_as->data); + + gedit_window_set_active_tab (window, tab); + file_save_as (tab, window); + } +} + +void +gedit_commands_save_all_documents (GeditWindow *window) +{ + GList *docs; + + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + gedit_debug (DEBUG_COMMANDS); + + docs = gedit_window_get_documents (window); + + _gedit_cmd_file_save_documents_list (window, docs); + + g_list_free (docs); +} + +void +_gedit_cmd_file_save_all (GtkAction *action, + GeditWindow *window) +{ + gedit_commands_save_all_documents (window); +} + +void +gedit_commands_save_document (GeditWindow *window, + GeditDocument *document) +{ + GeditTab *tab; + + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (GEDIT_IS_DOCUMENT (document)); + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_tab_get_from_document (document); + file_save (tab, window); +} + +/* File revert */ +static void +do_revert (GeditWindow *window, + GeditTab *tab) +{ + GeditDocument *doc; + gchar *docname; + + gedit_debug (DEBUG_COMMANDS); + + doc = gedit_tab_get_document (tab); + docname = gedit_document_get_short_name_for_display (doc); + + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->generic_message_cid, + _("Reverting the document '%s'\342\200\246"), + docname); + + g_free (docname); + + _gedit_tab_revert (tab); +} + +static void +revert_dialog_response_cb (GtkDialog *dialog, + gint response_id, + GeditWindow *window) +{ + GeditTab *tab; + + gedit_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 = gedit_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 (GeditWindow *window, + GeditDocument *doc) +{ + GtkWidget *dialog; + gchar *docname; + gchar *primary_msg; + gchar *secondary_msg; + glong seconds; + + gedit_debug (DEBUG_COMMANDS); + + docname = gedit_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, _gedit_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); + + gedit_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 +_gedit_cmd_file_revert (GtkAction *action, + GeditWindow *window) +{ + GeditTab *tab; + GeditDocument *doc; + GtkWidget *dialog; + GtkWindowGroup *wg; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_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 (gedit_tab_get_state (tab) == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) + { + do_revert (window, tab); + return; + } + + doc = gedit_tab_get_document (tab); + g_return_if_fail (doc != NULL); + g_return_if_fail (!gedit_document_is_untitled (doc)); + + dialog = revert_dialog (window, doc); + + wg = gedit_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 (GeditTab *tab) +{ + GtkWidget *toplevel; + GeditWindow *window; + + gedit_debug (DEBUG_COMMANDS); + + g_return_val_if_fail (gedit_tab_get_state (tab) == GEDIT_TAB_STATE_CLOSING, + FALSE); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tab)); + g_return_val_if_fail (GEDIT_IS_WINDOW (toplevel), FALSE); + + window = GEDIT_WINDOW (toplevel); + + gedit_window_close_tab (window, tab); + + if (gedit_window_get_active_tab (window) == NULL) + { + gboolean is_quitting; + + is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window), + GEDIT_IS_QUITTING)); + + if (is_quitting) + gtk_widget_destroy (GTK_WIDGET (window)); + } + + return FALSE; +} + +static void +tab_state_changed_while_saving (GeditTab *tab, + GParamSpec *pspec, + GeditWindow *window) +{ + GeditTabState ts; + + ts = gedit_tab_get_state (tab); + + gedit_debug_message (DEBUG_COMMANDS, "State while saving: %d\n", ts); + + /* When the state become NORMAL, it means the saving operation is + finished */ + if (ts == GEDIT_TAB_STATE_NORMAL) + { + GeditDocument *doc; + + g_signal_handlers_disconnect_by_func (tab, + G_CALLBACK (tab_state_changed_while_saving), + window); + + doc = gedit_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 */ + _gedit_tab_mark_for_closing (tab); + + g_idle_add_full (G_PRIORITY_HIGH_IDLE, + (GSourceFunc)really_close_tab, + tab, + NULL); + } +} + +static void +save_and_close (GeditTab *tab, + GeditWindow *window) +{ + gedit_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 (GeditTab *tab, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + g_object_set_data (G_OBJECT (tab), + GEDIT_IS_CLOSING_TAB, + NULL); + + /* Trace tab state changes */ + g_signal_connect (tab, + "notify::state", + G_CALLBACK (tab_state_changed_while_saving), + window); + + gedit_window_set_active_tab (window, tab); + file_save_as (tab, window); +} + +static void +save_and_close_all_documents (const GList *docs, + GeditWindow *window) +{ + GList *tabs; + GList *l; + GSList *sl; + GSList *tabs_to_save_as; + GSList *tabs_to_save_and_close; + GList *tabs_to_close; + + gedit_debug (DEBUG_COMMANDS); + + g_return_if_fail (!(gedit_window_get_state (window) & GEDIT_WINDOW_STATE_PRINTING)); + + tabs = gtk_container_get_children ( + GTK_CONTAINER (_gedit_window_get_notebook (window))); + + tabs_to_save_as = NULL; + tabs_to_save_and_close = NULL; + tabs_to_close = NULL; + + l = tabs; + while (l != NULL) + { + GeditTab *t; + GeditTabState state; + GeditDocument *doc; + + t = GEDIT_TAB (l->data); + + state = gedit_tab_get_state (t); + doc = gedit_tab_get_document (t); + + /* If the state is: ([*] invalid states) + - GEDIT_TAB_STATE_NORMAL: close (and if needed save) + - GEDIT_TAB_STATE_LOADING: close, we are sure the file is unmodified + - GEDIT_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) + - [*] GEDIT_TAB_STATE_SAVING: invalid, ClosAll + and Quit are unsensitive if the window state is SAVING. + - [*] GEDIT_TAB_STATE_PRINTING, GEDIT_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. + - GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW: close (and if needed save) + - GEDIT_TAB_STATE_LOADING_ERROR: close without saving (if the state is LOADING_ERROR then the + document is not modified) + - GEDIT_TAB_STATE_REVERTING_ERROR: we do not close since the document contains errors + - GEDIT_TAB_STATE_SAVING_ERROR: we do not close since the document contains errors + - GEDIT_TAB_STATE_GENERIC_ERROR: we do not close since the document contains + errors (CHECK: we should problably remove this state) + - [*] GEDIT_TAB_STATE_CLOSING: this state is invalid in this case + */ + + g_return_if_fail (state != GEDIT_TAB_STATE_PRINTING); + g_return_if_fail (state != GEDIT_TAB_STATE_PRINT_PREVIEWING); + g_return_if_fail (state != GEDIT_TAB_STATE_CLOSING); + g_return_if_fail (state != GEDIT_TAB_STATE_SAVING); + + if ((state != GEDIT_TAB_STATE_SAVING_ERROR) && + (state != GEDIT_TAB_STATE_GENERIC_ERROR) && + (state != GEDIT_TAB_STATE_REVERTING_ERROR)) + { + if ((g_list_index ((GList *)docs, doc) >= 0) && + (state != GEDIT_TAB_STATE_LOADING) && + (state != GEDIT_TAB_STATE_LOADING_ERROR) && + (state != GEDIT_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 gedit - Paolo (Dec. 8, 2005) */ + if (gedit_document_is_untitled (doc) || + gedit_document_get_readonly (doc)) + { + g_object_set_data (G_OBJECT (t), + GEDIT_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) */ + gedit_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 (GEDIT_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) + { + GeditTab *tab; + + tabs_to_save_as = g_slist_reverse (tabs_to_save_as ); + + g_return_if_fail (g_object_get_data (G_OBJECT (window), + GEDIT_LIST_OF_TABS_TO_SAVE_AS) == NULL); + + g_object_set_data (G_OBJECT (window), + GEDIT_LIST_OF_TABS_TO_SAVE_AS, + tabs_to_save_as); + + tab = GEDIT_TAB (tabs_to_save_as->data); + + save_as_and_close (tab, window); + } +} + +static void +save_and_close_document (const GList *docs, + GeditWindow *window) +{ + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + g_return_if_fail (docs->next == NULL); + + tab = gedit_tab_get_from_document (GEDIT_DOCUMENT (docs->data)); + g_return_if_fail (tab != NULL); + + save_and_close (tab, window); +} + +static void +close_all_tabs (GeditWindow *window) +{ + gboolean is_quitting; + + gedit_debug (DEBUG_COMMANDS); + + /* There is no document to save -> close all tabs */ + gedit_window_close_all_tabs (window); + + is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window), + GEDIT_IS_QUITTING)); + + if (is_quitting) + gtk_widget_destroy (GTK_WIDGET (window)); + + return; +} + +static void +close_document (GeditWindow *window, + GeditDocument *doc) +{ + GeditTab *tab; + + gedit_debug (DEBUG_COMMANDS); + + tab = gedit_tab_get_from_document (doc); + g_return_if_fail (tab != NULL); + + gedit_window_close_tab (window, tab); +} + +static void +close_confirmation_dialog_response_handler (GeditCloseConfirmationDialog *dlg, + gint response_id, + GeditWindow *window) +{ + GList *selected_documents; + gboolean is_closing_all; + + gedit_debug (DEBUG_COMMANDS); + + is_closing_all = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window), + GEDIT_IS_CLOSING_ALL)); + + gtk_widget_hide (GTK_WIDGET (dlg)); + + switch (response_id) + { + case GTK_RESPONSE_YES: /* Save and Close */ + selected_documents = gedit_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 gedit 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 gedit window */ + gtk_widget_destroy (GTK_WIDGET (dlg)); + + close_all_tabs (window); + + return; + } + else + { + const GList *unsaved_documents; + + unsaved_documents = gedit_close_confirmation_dialog_get_unsaved_documents (dlg); + g_return_if_fail (unsaved_documents->next == NULL); + + close_document (window, + GEDIT_DOCUMENT (unsaved_documents->data)); + } + + break; + default: /* Do not close */ + + /* Reset is_quitting flag */ + g_object_set_data (G_OBJECT (window), + GEDIT_IS_QUITTING, + GBOOLEAN_TO_POINTER (FALSE)); + +#ifdef OS_OSX + g_object_set_data (G_OBJECT (window), + GEDIT_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 (GeditTab *tab, + GtkWindow *window) +{ + GeditDocument *doc; + + gedit_debug (DEBUG_COMMANDS); + + doc = gedit_tab_get_document (tab); + + if (!_gedit_tab_can_close (tab)) + { + GtkWidget *dlg; + + dlg = gedit_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 + * gedit_window_close_tab always run the confirm dialog? + * we should not allow closing a tab without resetting the + * GEDIT_IS_CLOSING_ALL flag! + */ +void +_gedit_cmd_file_close_tab (GeditTab *tab, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + g_return_if_fail (GTK_WIDGET (window) == gtk_widget_get_toplevel (GTK_WIDGET (tab))); + + g_object_set_data (G_OBJECT (window), + GEDIT_IS_CLOSING_ALL, + GBOOLEAN_TO_POINTER (FALSE)); + + g_object_set_data (G_OBJECT (window), + GEDIT_IS_QUITTING, + GBOOLEAN_TO_POINTER (FALSE)); + + g_object_set_data (G_OBJECT (window), + GEDIT_IS_QUITTING_ALL, + GINT_TO_POINTER (FALSE)); + + + if (tab_can_close (tab, GTK_WINDOW (window))) + gedit_window_close_tab (window, tab); +} + +void +_gedit_cmd_file_close (GtkAction *action, + GeditWindow *window) +{ + GeditTab *active_tab; + + gedit_debug (DEBUG_COMMANDS); + + active_tab = gedit_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; + } + + _gedit_cmd_file_close_tab (active_tab, window); +} + +/* Close all tabs */ +static void +file_close_all (GeditWindow *window, + gboolean is_quitting) +{ + GList *unsaved_docs; + GtkWidget *dlg; + + gedit_debug (DEBUG_COMMANDS); + + g_return_if_fail (!(gedit_window_get_state (window) & + (GEDIT_WINDOW_STATE_SAVING | + GEDIT_WINDOW_STATE_PRINTING | + GEDIT_WINDOW_STATE_SAVING_SESSION))); + + g_object_set_data (G_OBJECT (window), + GEDIT_IS_CLOSING_ALL, + GBOOLEAN_TO_POINTER (TRUE)); + + g_object_set_data (G_OBJECT (window), + GEDIT_IS_QUITTING, + GBOOLEAN_TO_POINTER (is_quitting)); + + unsaved_docs = gedit_window_get_unsaved_documents (window); + + if (unsaved_docs == NULL) + { + /* There is no document to save -> close all tabs */ + gedit_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 */ + GeditTab *tab; + GeditDocument *doc; + + doc = GEDIT_DOCUMENT (unsaved_docs->data); + + tab = gedit_tab_get_from_document (doc); + g_return_if_fail (tab != NULL); + + gedit_window_set_active_tab (window, tab); + + dlg = gedit_close_confirmation_dialog_new_single ( + GTK_WINDOW (window), + doc, + FALSE); + } + else + { + dlg = gedit_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 +_gedit_cmd_file_close_all (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + g_return_if_fail (!(gedit_window_get_state (window) & + (GEDIT_WINDOW_STATE_SAVING | + GEDIT_WINDOW_STATE_PRINTING | + GEDIT_WINDOW_STATE_SAVING_SESSION))); + + file_close_all (window, FALSE); +} + +/* Quit */ +#ifdef OS_OSX +static void +quit_all () +{ + GList *windows; + GList *item; + GeditApp *app; + + app = gedit_app_get_default (); + windows = g_list_copy ((GList *)gedit_app_get_windows (app)); + + for (item = windows; item; item = g_list_next (item)) + { + GeditWindow *window = GEDIT_WINDOW (item->data); + + g_object_set_data (G_OBJECT (window), + GEDIT_IS_QUITTING_ALL, + GINT_TO_POINTER (TRUE)); + + if (!(gedit_window_get_state (window) & + (GEDIT_WINDOW_STATE_SAVING | + GEDIT_WINDOW_STATE_PRINTING | + GEDIT_WINDOW_STATE_SAVING_SESSION))) + { + file_close_all (window, TRUE); + } + } + + g_list_free (windows); +} +#endif + +void +_gedit_cmd_file_quit (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + +#ifdef OS_OSX + if (action != NULL) + { + quit_all (); + return; + } +#endif + + g_return_if_fail (!(gedit_window_get_state (window) & + (GEDIT_WINDOW_STATE_SAVING | + GEDIT_WINDOW_STATE_PRINTING | + GEDIT_WINDOW_STATE_SAVING_SESSION))); + + file_close_all (window, TRUE); +} diff --git a/gedit/gedit-commands-help.c b/gedit/gedit-commands-help.c new file mode 100755 index 00000000..3b71106e --- /dev/null +++ b/gedit/gedit-commands-help.c @@ -0,0 +1,115 @@ +/* + * gedit-help-commands.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-commands.h" +#include "gedit-debug.h" +#include "gedit-help.h" +#include "gedit-dirs.h" + +void +_gedit_cmd_help_contents (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + gedit_help_display (GTK_WINDOW (window), NULL, NULL); +} + +void +_gedit_cmd_help_about (GtkAction *action, + GeditWindow *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"; + + static const gchar comments[] = \ + N_("gedit is a small and lightweight text editor for the " + "MATE Desktop"); + + GdkPixbuf *logo; + gchar *data_dir; + gchar *logo_file; + + gedit_debug (DEBUG_COMMANDS); + + data_dir = gedit_dirs_get_gedit_data_dir (); + logo_file = g_build_filename (data_dir, + "logo", + "gedit-logo.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", "gedit", + "authors", authors, + "comments", _(comments), + "copyright", copyright, + "documenters", documenters, + "logo", logo, + "translator-credits", _("translator-credits"), + "version", VERSION, + "website", "http://www.gedit.org", + NULL); + + if (logo) + g_object_unref (logo); +} diff --git a/gedit/gedit-commands-search.c b/gedit/gedit-commands-search.c new file mode 100755 index 00000000..3a44a218 --- /dev/null +++ b/gedit/gedit-commands-search.c @@ -0,0 +1,716 @@ +/* + * gedit-search-commands.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2006. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-commands.h" +#include "gedit-debug.h" +#include "gedit-statusbar.h" +#include "gedit-window.h" +#include "gedit-window-private.h" +#include "gedit-utils.h" +#include "dialogs/gedit-search-dialog.h" + +#define GEDIT_SEARCH_DIALOG_KEY "gedit-search-dialog-key" +#define GEDIT_LAST_SEARCH_DATA_KEY "gedit-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 (GeditSearchDialog *dlg) +{ + LastSearchData *data; + + data = g_object_get_data (G_OBJECT (dlg), GEDIT_LAST_SEARCH_DATA_KEY); + + if (data != NULL) + { + gtk_window_move (GTK_WINDOW (dlg), + data->x, + data->y); + } +} + +static void +last_search_data_store_position (GeditSearchDialog *dlg) +{ + LastSearchData *data; + + data = g_object_get_data (G_OBJECT (dlg), GEDIT_LAST_SEARCH_DATA_KEY); + + if (data == NULL) + { + data = g_slice_new (LastSearchData); + + g_object_set_data_full (G_OBJECT (dlg), + GEDIT_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 (GeditWindow *window, + gint occurrences) +{ + if (occurrences > 1) + { + gedit_statusbar_flash_message (GEDIT_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) + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->generic_message_cid, + _("Found and replaced one occurrence")); + else + gedit_statusbar_flash_message (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->generic_message_cid, + " "); + } +} + +#define MAX_MSG_LENGTH 40 +static void +text_not_found (GeditWindow *window, + const gchar *text) +{ + gchar *searched; + + searched = gedit_utils_str_end_truncate (text, MAX_MSG_LENGTH); + + gedit_statusbar_flash_message (GEDIT_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 (GeditView *view, + gboolean wrap_around, + gboolean search_backwards) +{ + GeditDocument *doc; + GtkTextIter start_iter; + GtkTextIter match_start; + GtkTextIter match_end; + gboolean found = FALSE; + + doc = GEDIT_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 = gedit_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 = gedit_document_search_backward (doc, + NULL, + &start_iter, + &match_start, + &match_end); + } + + if (!found && wrap_around) + { + if (!search_backwards) + found = gedit_document_search_forward (doc, + NULL, + NULL, /* FIXME: set the end_inter */ + &match_start, + &match_end); + else + found = gedit_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); + + gedit_view_scroll_to_cursor (view); + } + else + { + gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (doc), + &start_iter); + } + + return found; +} + +static void +do_find (GeditSearchDialog *dialog, + GeditWindow *window) +{ + GeditView *active_view; + GeditDocument *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 = gedit_window_get_active_view (window); + if (active_view == NULL) + return; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view))); + + entry_text = gedit_search_dialog_get_search_text (dialog); + + match_case = gedit_search_dialog_get_match_case (dialog); + entire_word = gedit_search_dialog_get_entire_word (dialog); + search_backwards = gedit_search_dialog_get_backwards (dialog); + wrap_around = gedit_search_dialog_get_wrap_around (dialog); + + GEDIT_SEARCH_SET_CASE_SENSITIVE (flags, match_case); + GEDIT_SEARCH_SET_ENTIRE_WORD (flags, entire_word); + + search_text = gedit_document_get_search_text (doc, &old_flags); + + if ((search_text == NULL) || + (strcmp (search_text, entry_text) != 0) || + (flags != old_flags)) + { + gedit_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), + GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE, + found); +} + +/* FIXME: move in gedit-document.c and share it with gedit-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 (GeditSearchDialog *dialog, + GeditWindow *window) +{ + GeditDocument *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 = gedit_window_get_active_document (window); + if (doc == NULL) + return; + + search_entry_text = gedit_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 = gedit_search_dialog_get_replace_text (dialog); + g_return_if_fail ((replace_entry_text) != NULL); + + unescaped_search_text = gedit_utils_unescape_search_text (search_entry_text); + + get_selected_text (GTK_TEXT_BUFFER (doc), + &selected_text, + NULL); + + match_case = gedit_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 = gedit_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 (GeditSearchDialog *dialog, + GeditWindow *window) +{ + GeditView *active_view; + GeditDocument *doc; + const gchar *search_entry_text; + const gchar *replace_entry_text; + gboolean match_case; + gboolean entire_word; + guint flags = 0; + gint count; + + active_view = gedit_window_get_active_view (window); + if (active_view == NULL) + return; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (active_view))); + + search_entry_text = gedit_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 = gedit_search_dialog_get_replace_text (dialog); + g_return_if_fail ((replace_entry_text) != NULL); + + match_case = gedit_search_dialog_get_match_case (dialog); + entire_word = gedit_search_dialog_get_entire_word (dialog); + + GEDIT_SEARCH_SET_CASE_SENSITIVE (flags, match_case); + GEDIT_SEARCH_SET_ENTIRE_WORD (flags, entire_word); + + count = gedit_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), + GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE, + FALSE); +} + +static void +search_dialog_response_cb (GeditSearchDialog *dialog, + gint response_id, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + switch (response_id) + { + case GEDIT_SEARCH_DIALOG_FIND_RESPONSE: + do_find (dialog, window); + break; + case GEDIT_SEARCH_DIALOG_REPLACE_RESPONSE: + do_replace (dialog, window); + break; + case GEDIT_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) +{ + gedit_debug (DEBUG_COMMANDS); + + /* prevent destruction */ + return TRUE; +} + +static void +search_dialog_destroyed (GeditWindow *window, + GeditSearchDialog *dialog) +{ + gedit_debug (DEBUG_COMMANDS); + + g_object_set_data (G_OBJECT (window), + GEDIT_SEARCH_DIALOG_KEY, + NULL); + g_object_set_data (G_OBJECT (dialog), + GEDIT_LAST_SEARCH_DATA_KEY, + NULL); +} + +static GtkWidget * +create_dialog (GeditWindow *window, gboolean show_replace) +{ + GtkWidget *dialog; + + dialog = gedit_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), + GEDIT_SEARCH_DIALOG_KEY, + dialog); + + g_object_weak_ref (G_OBJECT (dialog), + (GWeakNotify) search_dialog_destroyed, + window); + + return dialog; +} + +void +_gedit_cmd_search_find (GtkAction *action, + GeditWindow *window) +{ + gpointer data; + GtkWidget *search_dialog; + GeditDocument *doc; + gboolean selection_exists; + gchar *find_text = NULL; + gint sel_len; + + gedit_debug (DEBUG_COMMANDS); + + data = g_object_get_data (G_OBJECT (window), GEDIT_SEARCH_DIALOG_KEY); + + if (data == NULL) + { + search_dialog = create_dialog (window, FALSE); + } + else + { + g_return_if_fail (GEDIT_IS_SEARCH_DIALOG (data)); + + search_dialog = GTK_WIDGET (data); + + /* turn the dialog into a find dialog if needed */ + if (gedit_search_dialog_get_show_replace (GEDIT_SEARCH_DIALOG (search_dialog))) + gedit_search_dialog_set_show_replace (GEDIT_SEARCH_DIALOG (search_dialog), + FALSE); + } + + doc = gedit_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) + { + gedit_search_dialog_set_search_text (GEDIT_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 (GEDIT_SEARCH_DIALOG (search_dialog)); + gedit_search_dialog_present_with_time (GEDIT_SEARCH_DIALOG (search_dialog), + GDK_CURRENT_TIME); +} + +void +_gedit_cmd_search_replace (GtkAction *action, + GeditWindow *window) +{ + gpointer data; + GtkWidget *replace_dialog; + GeditDocument *doc; + gboolean selection_exists; + gchar *find_text = NULL; + gint sel_len; + + gedit_debug (DEBUG_COMMANDS); + + data = g_object_get_data (G_OBJECT (window), GEDIT_SEARCH_DIALOG_KEY); + + if (data == NULL) + { + replace_dialog = create_dialog (window, TRUE); + } + else + { + g_return_if_fail (GEDIT_IS_SEARCH_DIALOG (data)); + + replace_dialog = GTK_WIDGET (data); + + /* turn the dialog into a find dialog if needed */ + if (!gedit_search_dialog_get_show_replace (GEDIT_SEARCH_DIALOG (replace_dialog))) + gedit_search_dialog_set_show_replace (GEDIT_SEARCH_DIALOG (replace_dialog), + TRUE); + } + + doc = gedit_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) + { + gedit_search_dialog_set_search_text (GEDIT_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 (GEDIT_SEARCH_DIALOG (replace_dialog)); + gedit_search_dialog_present_with_time (GEDIT_SEARCH_DIALOG (replace_dialog), + GDK_CURRENT_TIME); +} + +static void +do_find_again (GeditWindow *window, + gboolean backward) +{ + GeditView *active_view; + gboolean wrap_around = TRUE; + gpointer data; + + active_view = gedit_window_get_active_view (window); + g_return_if_fail (active_view != NULL); + + data = g_object_get_data (G_OBJECT (window), GEDIT_SEARCH_DIALOG_KEY); + + if (data != NULL) + wrap_around = gedit_search_dialog_get_wrap_around (GEDIT_SEARCH_DIALOG (data)); + + run_search (active_view, + wrap_around, + backward); +} + +void +_gedit_cmd_search_find_next (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + do_find_again (window, FALSE); +} + +void +_gedit_cmd_search_find_prev (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + do_find_again (window, TRUE); +} + +void +_gedit_cmd_search_clear_highlight (GtkAction *action, + GeditWindow *window) +{ + GeditDocument *doc; + + gedit_debug (DEBUG_COMMANDS); + + doc = gedit_window_get_active_document (window); + gedit_document_set_search_text (GEDIT_DOCUMENT (doc), + "", + GEDIT_SEARCH_DONT_SET_FLAGS); +} + +void +_gedit_cmd_search_goto_line (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_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 GeditView, just activate + * the corrisponding binding. + */ + gtk_bindings_activate (GTK_OBJECT (active_view), + GDK_i, + GDK_CONTROL_MASK); +} + +void +_gedit_cmd_search_incremental_search (GtkAction *action, + GeditWindow *window) +{ + GeditView *active_view; + + gedit_debug (DEBUG_COMMANDS); + + active_view = gedit_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 GeditView, just activate + * the corrisponding binding. + */ + gtk_bindings_activate (GTK_OBJECT (active_view), + GDK_k, + GDK_CONTROL_MASK); +} diff --git a/gedit/gedit-commands-view.c b/gedit/gedit-commands-view.c new file mode 100755 index 00000000..2366e49c --- /dev/null +++ b/gedit/gedit-commands-view.c @@ -0,0 +1,154 @@ +/* + * gedit-view-commands.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> + +#include "gedit-commands.h" +#include "gedit-debug.h" +#include "gedit-window.h" +#include "gedit-window-private.h" + + +void +_gedit_cmd_view_show_toolbar (GtkAction *action, + GeditWindow *window) +{ + gboolean visible; + + gedit_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 +_gedit_cmd_view_show_statusbar (GtkAction *action, + GeditWindow *window) +{ + gboolean visible; + + gedit_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 +_gedit_cmd_view_show_side_pane (GtkAction *action, + GeditWindow *window) +{ + gboolean visible; + GeditPanel *panel; + + gedit_debug (DEBUG_COMMANDS); + + visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + panel = gedit_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 +_gedit_cmd_view_show_bottom_pane (GtkAction *action, + GeditWindow *window) +{ + gboolean visible; + GeditPanel *panel; + + gedit_debug (DEBUG_COMMANDS); + + visible = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + panel = gedit_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 +_gedit_cmd_view_toggle_fullscreen_mode (GtkAction *action, + GeditWindow *window) +{ + gedit_debug (DEBUG_COMMANDS); + + if (_gedit_window_is_fullscreen (window)) + _gedit_window_unfullscreen (window); + else + _gedit_window_fullscreen (window); +} + +void +_gedit_cmd_view_leave_fullscreen_mode (GtkAction *action, + GeditWindow *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 (_gedit_cmd_view_toggle_fullscreen_mode), + window); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (view_action), + FALSE); + _gedit_window_unfullscreen (window); + g_signal_handlers_unblock_by_func + (view_action, G_CALLBACK (_gedit_cmd_view_toggle_fullscreen_mode), + window); +} diff --git a/gedit/gedit-commands.h b/gedit/gedit-commands.h new file mode 100755 index 00000000..08e70885 --- /dev/null +++ b/gedit/gedit-commands.h @@ -0,0 +1,166 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-commands.h + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_COMMANDS_H__ +#define __GEDIT_COMMANDS_H__ + +#include <gtk/gtk.h> +#include <gedit/gedit-window.h> + +G_BEGIN_DECLS + +/* Do nothing if URI does not exist */ +void gedit_commands_load_uri (GeditWindow *window, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos); + +/* Ignore non-existing URIs */ +gint gedit_commands_load_uris (GeditWindow *window, + const GSList *uris, + const GeditEncoding *encoding, + gint line_pos); + +void gedit_commands_save_document (GeditWindow *window, + GeditDocument *document); + +void gedit_commands_save_all_documents (GeditWindow *window); + +/* + * Non-exported functions + */ + +/* Create titled documens for non-existing URIs */ +gint _gedit_cmd_load_files_from_prompt (GeditWindow *window, + GSList *files, + const GeditEncoding *encoding, + gint line_pos); + +void _gedit_cmd_file_new (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_open (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_save (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_save_as (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_save_all (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_revert (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_open_uri (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_print_preview (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_print (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_close (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_close_all (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_file_quit (GtkAction *action, + GeditWindow *window); + +void _gedit_cmd_edit_undo (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_edit_redo (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_edit_cut (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_edit_copy (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_edit_paste (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_edit_delete (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_edit_select_all (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_edit_preferences (GtkAction *action, + GeditWindow *window); + +void _gedit_cmd_view_show_toolbar (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_view_show_statusbar (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_view_show_side_pane (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_view_show_bottom_pane (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_view_toggle_fullscreen_mode (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_view_leave_fullscreen_mode (GtkAction *action, + GeditWindow *window); + +void _gedit_cmd_search_find (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_search_find_next (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_search_find_prev (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_search_replace (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_search_clear_highlight (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_search_goto_line (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_search_incremental_search (GtkAction *action, + GeditWindow *window); + +void _gedit_cmd_documents_previous_document (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_documents_next_document (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_documents_move_to_new_window (GtkAction *action, + GeditWindow *window); + +void _gedit_cmd_help_contents (GtkAction *action, + GeditWindow *window); +void _gedit_cmd_help_about (GtkAction *action, + GeditWindow *window); + +void _gedit_cmd_file_close_tab (GeditTab *tab, + GeditWindow *window); + +void _gedit_cmd_file_save_documents_list (GeditWindow *window, + GList *docs); + + +#if !GTK_CHECK_VERSION (2, 17, 4) +void _gedit_cmd_file_page_setup (GtkAction *action, + GeditWindow *window); +#endif + + +G_END_DECLS + +#endif /* __GEDIT_COMMANDS_H__ */ diff --git a/gedit/gedit-debug.c b/gedit/gedit-debug.c new file mode 100755 index 00000000..163a5a54 --- /dev/null +++ b/gedit/gedit-debug.c @@ -0,0 +1,159 @@ +/* + * gedit-debug.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include "gedit-debug.h" + +#define ENABLE_PROFILING + +#ifdef ENABLE_PROFILING +static GTimer *timer = NULL; +static gdouble last = 0.0; +#endif + +static GeditDebugSection debug = GEDIT_NO_DEBUG; + +void +gedit_debug_init (void) +{ + if (g_getenv ("GEDIT_DEBUG") != NULL) + { + /* enable all debugging */ + debug = ~GEDIT_NO_DEBUG; + goto out; + } + + if (g_getenv ("GEDIT_DEBUG_VIEW") != NULL) + debug = debug | GEDIT_DEBUG_VIEW; + if (g_getenv ("GEDIT_DEBUG_SEARCH") != NULL) + debug = debug | GEDIT_DEBUG_SEARCH; + if (g_getenv ("GEDIT_DEBUG_PREFS") != NULL) + debug = debug | GEDIT_DEBUG_PREFS; + if (g_getenv ("GEDIT_DEBUG_PRINT") != NULL) + debug = debug | GEDIT_DEBUG_PRINT; + if (g_getenv ("GEDIT_DEBUG_PLUGINS") != NULL) + debug = debug | GEDIT_DEBUG_PLUGINS; + if (g_getenv ("GEDIT_DEBUG_TAB") != NULL) + debug = debug | GEDIT_DEBUG_TAB; + if (g_getenv ("GEDIT_DEBUG_DOCUMENT") != NULL) + debug = debug | GEDIT_DEBUG_DOCUMENT; + if (g_getenv ("GEDIT_DEBUG_COMMANDS") != NULL) + debug = debug | GEDIT_DEBUG_COMMANDS; + if (g_getenv ("GEDIT_DEBUG_APP") != NULL) + debug = debug | GEDIT_DEBUG_APP; + if (g_getenv ("GEDIT_DEBUG_SESSION") != NULL) + debug = debug | GEDIT_DEBUG_SESSION; + if (g_getenv ("GEDIT_DEBUG_UTILS") != NULL) + debug = debug | GEDIT_DEBUG_UTILS; + if (g_getenv ("GEDIT_DEBUG_METADATA") != NULL) + debug = debug | GEDIT_DEBUG_METADATA; + if (g_getenv ("GEDIT_DEBUG_WINDOW") != NULL) + debug = debug | GEDIT_DEBUG_WINDOW; + if (g_getenv ("GEDIT_DEBUG_LOADER") != NULL) + debug = debug | GEDIT_DEBUG_LOADER; + if (g_getenv ("GEDIT_DEBUG_SAVER") != NULL) + debug = debug | GEDIT_DEBUG_SAVER; + +out: + +#ifdef ENABLE_PROFILING + if (debug != GEDIT_NO_DEBUG) + timer = g_timer_new (); +#endif + return; +} + +void +gedit_debug_message (GeditDebugSection 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 gedit_debug (GeditDebugSection 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/gedit/gedit-debug.h b/gedit/gedit-debug.h new file mode 100755 index 00000000..af7d7370 --- /dev/null +++ b/gedit/gedit-debug.h @@ -0,0 +1,93 @@ +/* + * gedit-debug.h + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_DEBUG_H__ +#define __GEDIT_DEBUG_H__ + +#include <glib.h> + +/* + * Set an environmental var of the same name to turn on + * debugging output. Setting GEDIT_DEBUG will turn on all + * sections. + */ +typedef enum { + GEDIT_NO_DEBUG = 0, + GEDIT_DEBUG_VIEW = 1 << 0, + GEDIT_DEBUG_SEARCH = 1 << 1, + GEDIT_DEBUG_PRINT = 1 << 2, + GEDIT_DEBUG_PREFS = 1 << 3, + GEDIT_DEBUG_PLUGINS = 1 << 4, + GEDIT_DEBUG_TAB = 1 << 5, + GEDIT_DEBUG_DOCUMENT = 1 << 6, + GEDIT_DEBUG_COMMANDS = 1 << 7, + GEDIT_DEBUG_APP = 1 << 8, + GEDIT_DEBUG_SESSION = 1 << 9, + GEDIT_DEBUG_UTILS = 1 << 10, + GEDIT_DEBUG_METADATA = 1 << 11, + GEDIT_DEBUG_WINDOW = 1 << 12, + GEDIT_DEBUG_LOADER = 1 << 13, + GEDIT_DEBUG_SAVER = 1 << 14 +} GeditDebugSection; + + +#define DEBUG_VIEW GEDIT_DEBUG_VIEW, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_SEARCH GEDIT_DEBUG_SEARCH, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_PRINT GEDIT_DEBUG_PRINT, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_PREFS GEDIT_DEBUG_PREFS, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_PLUGINS GEDIT_DEBUG_PLUGINS, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_TAB GEDIT_DEBUG_TAB, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_DOCUMENT GEDIT_DEBUG_DOCUMENT,__FILE__, __LINE__, G_STRFUNC +#define DEBUG_COMMANDS GEDIT_DEBUG_COMMANDS,__FILE__, __LINE__, G_STRFUNC +#define DEBUG_APP GEDIT_DEBUG_APP, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_SESSION GEDIT_DEBUG_SESSION, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_UTILS GEDIT_DEBUG_UTILS, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_METADATA GEDIT_DEBUG_METADATA,__FILE__, __LINE__, G_STRFUNC +#define DEBUG_WINDOW GEDIT_DEBUG_WINDOW, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_LOADER GEDIT_DEBUG_LOADER, __FILE__, __LINE__, G_STRFUNC +#define DEBUG_SAVER GEDIT_DEBUG_SAVER, __FILE__, __LINE__, G_STRFUNC + +void gedit_debug_init (void); + +void gedit_debug (GeditDebugSection section, + const gchar *file, + gint line, + const gchar *function); + +void gedit_debug_message (GeditDebugSection section, + const gchar *file, + gint line, + const gchar *function, + const gchar *format, ...) G_GNUC_PRINTF(5, 6); + + +#endif /* __GEDIT_DEBUG_H__ */ diff --git a/gedit/gedit-dirs.c b/gedit/gedit-dirs.c new file mode 100755 index 00000000..a1467f61 --- /dev/null +++ b/gedit/gedit-dirs.c @@ -0,0 +1,320 @@ +/* + * gedit-dirs.c + * This file is part of gedit + * + * 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 "gedit-dirs.h" + +#ifdef OS_OSX +#include <ige-mac-bundle.h> +#endif + +gchar * +gedit_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, + "gedit", + NULL); + + } + else + { + home = g_get_home_dir (); + + if (home != NULL) + { + config_dir = g_build_filename (home, + ".mate2", + "gedit", + NULL); + } + } +#else + config_dir = g_build_filename (g_get_user_config_dir (), + "gedit", + NULL); +#endif + + return config_dir; +} + +gchar * +gedit_dirs_get_user_cache_dir (void) +{ + const gchar *cache_dir; + + cache_dir = g_get_user_cache_dir (); + + return g_build_filename (cache_dir, + "gedit", + NULL); +} + +gchar * +gedit_dirs_get_user_plugins_dir (void) +{ + gchar *config_dir; + gchar *plugin_dir; + + config_dir = gedit_dirs_get_user_config_dir (); + + plugin_dir = g_build_filename (config_dir, + "plugins", + NULL); + g_free (config_dir); + + return plugin_dir; +} + +gchar * +gedit_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", + "gedit", + NULL); + } + else + { + home = g_get_home_dir (); + + if (home != NULL) + { + accels = g_build_filename (home, + ".mate2", + "accels", + "gedit", + NULL); + } + } +#else + { + gchar *config_dir = NULL; + + config_dir = gedit_dirs_get_user_config_dir (); + accels = g_build_filename (config_dir, + "accels", + "gedit", + NULL); + + g_free (config_dir); + } +#endif + + return accels; +} + +gchar * +gedit_dirs_get_gedit_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", + "gedit-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, + "gedit-2", + NULL); + } + else + { + data_dir = g_build_filename (DATADIR, "gedit-2", NULL); + } +#else + data_dir = g_build_filename (DATADIR, + "gedit-2", + NULL); +#endif + + return data_dir; +} + +gchar * +gedit_dirs_get_gedit_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 * +gedit_dirs_get_gedit_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", + "gedit-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", + "gedit-2", + NULL); + } + else + { + lib_dir = g_build_filename (LIBDIR, + "gedit-2", + NULL); + } +#else + lib_dir = g_build_filename (LIBDIR, + "gedit-2", + NULL); +#endif + + return lib_dir; +} + +gchar * +gedit_dirs_get_gedit_plugins_dir (void) +{ + gchar *lib_dir; + gchar *plugin_dir; + + lib_dir = gedit_dirs_get_gedit_lib_dir (); + + plugin_dir = g_build_filename (lib_dir, + "plugins", + NULL); + g_free (lib_dir); + + return plugin_dir; +} + +gchar * +gedit_dirs_get_gedit_plugin_loaders_dir (void) +{ + gchar *lib_dir; + gchar *loader_dir; + + lib_dir = gedit_dirs_get_gedit_lib_dir (); + + loader_dir = g_build_filename (lib_dir, + "plugin-loaders", + NULL); + g_free (lib_dir); + + return loader_dir; +} + +gchar * +gedit_dirs_get_ui_file (const gchar *file) +{ + gchar *datadir; + gchar *ui_file; + + g_return_val_if_fail (file != NULL, NULL); + + datadir = gedit_dirs_get_gedit_data_dir (); + ui_file = g_build_filename (datadir, + "ui", + file, + NULL); + g_free (datadir); + + return ui_file; +} diff --git a/gedit/gedit-dirs.h b/gedit/gedit-dirs.h new file mode 100755 index 00000000..eddd00d0 --- /dev/null +++ b/gedit/gedit-dirs.h @@ -0,0 +1,54 @@ +/* + * gedit-dirs.h + * This file is part of gedit + * + * 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 __GEDIT_DIRS_H__ +#define __GEDIT_DIRS_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +gchar *gedit_dirs_get_user_config_dir (void); + +gchar *gedit_dirs_get_user_cache_dir (void); + +gchar *gedit_dirs_get_user_plugins_dir (void); + +gchar *gedit_dirs_get_user_accels_file (void); + +gchar *gedit_dirs_get_gedit_data_dir (void); + +gchar *gedit_dirs_get_gedit_locale_dir (void); + +gchar *gedit_dirs_get_gedit_lib_dir (void); + +gchar *gedit_dirs_get_gedit_plugins_dir (void); + +gchar *gedit_dirs_get_gedit_plugin_loaders_dir + (void); + +gchar *gedit_dirs_get_ui_file (const gchar *file); + +G_END_DECLS + +#endif /* __GEDIT_DIRS_H__ */ diff --git a/gedit/gedit-document-input-stream.c b/gedit/gedit-document-input-stream.c new file mode 100755 index 00000000..5a1f9614 --- /dev/null +++ b/gedit/gedit-document-input-stream.c @@ -0,0 +1,479 @@ +/* + * gedit-document-input-stream.c + * This file is part of gedit + * + * Copyright (C) 2010 - Ignacio Casal Quinteiro + * + * gedit 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. + * + * gedit 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 gedit; 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 "gedit-document-input-stream.h" +#include "gedit-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 (GeditDocumentInputStream, gedit_document_input_stream, G_TYPE_INPUT_STREAM); + +struct _GeditDocumentInputStreamPrivate +{ + GtkTextBuffer *buffer; + GtkTextMark *pos; + gint bytes_partial; + + GeditDocumentNewlineType newline_type; + + guint newline_added : 1; + guint is_initialized : 1; +}; + +enum +{ + PROP_0, + PROP_BUFFER, + PROP_NEWLINE_TYPE +}; + +static gssize gedit_document_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean gedit_document_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error); + +static void +gedit_document_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocumentInputStream *stream = GEDIT_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 +gedit_document_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocumentInputStream *stream = GEDIT_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 +gedit_document_input_stream_class_init (GeditDocumentInputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GeditDocumentInputStreamPrivate)); + + gobject_class->get_property = gedit_document_input_stream_get_property; + gobject_class->set_property = gedit_document_input_stream_set_property; + + stream_class->read_fn = gedit_document_input_stream_read; + stream_class->close_fn = gedit_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)); + + /** + * GeditDocumentInputStream: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", + GEDIT_TYPE_DOCUMENT_NEWLINE_TYPE, + GEDIT_DOCUMENT_NEWLINE_TYPE_LF, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gedit_document_input_stream_init (GeditDocumentInputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + GEDIT_TYPE_DOCUMENT_INPUT_STREAM, + GeditDocumentInputStreamPrivate); +} + +static gsize +get_new_line_size (GeditDocumentInputStream *stream) +{ + gsize ret; + + switch (stream->priv->newline_type) + { + case GEDIT_DOCUMENT_NEWLINE_TYPE_CR: + case GEDIT_DOCUMENT_NEWLINE_TYPE_LF: + ret = 1; + break; + + case GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF: + ret = 2; + break; + + default: + g_warn_if_reached (); + ret = 1; + break; + } + + return ret; +} + +/** + * gedit_document_input_stream_new: + * @buffer: a #GtkTextBuffer + * + * Reads the data from @buffer. + * + * Returns: a new #GInputStream to read @buffer + */ +GInputStream * +gedit_document_input_stream_new (GtkTextBuffer *buffer, + GeditDocumentNewlineType type) +{ + GeditDocumentInputStream *stream; + + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL); + + stream = g_object_new (GEDIT_TYPE_DOCUMENT_INPUT_STREAM, + "buffer", buffer, + "newline-type", type, + NULL); + + return G_INPUT_STREAM (stream); +} + +gsize +gedit_document_input_stream_get_total_size (GeditDocumentInputStream *stream) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_INPUT_STREAM (stream), 0); + + return gtk_text_buffer_get_char_count (stream->priv->buffer); +} + +gsize +gedit_document_input_stream_tell (GeditDocumentInputStream *stream) +{ + g_return_val_if_fail (GEDIT_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 (GeditDocumentInputStream *stream) +{ + const gchar *ret; + + switch (stream->priv->newline_type) + { + case GEDIT_DOCUMENT_NEWLINE_TYPE_CR: + ret = "\r"; + break; + + case GEDIT_DOCUMENT_NEWLINE_TYPE_LF: + ret = "\n"; + break; + + case GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF: + ret = "\r\n"; + break; + + default: + g_warn_if_reached (); + ret = "\n"; + break; + } + + return ret; +} + +static gsize +read_line (GeditDocumentInputStream *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 +gedit_document_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GeditDocumentInputStream *dstream; + GtkTextIter iter; + gssize space_left, read, n; + + dstream = GEDIT_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 +gedit_document_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GeditDocumentInputStream *dstream = GEDIT_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/gedit/gedit-document-input-stream.h b/gedit/gedit-document-input-stream.h new file mode 100755 index 00000000..01f6a491 --- /dev/null +++ b/gedit/gedit-document-input-stream.h @@ -0,0 +1,68 @@ +/* + * gedit-document-input-stream.h + * This file is part of gedit + * + * Copyright (C) 2010 - Ignacio Casal Quinteiro + * + * gedit 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. + * + * gedit 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 gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef __GEDIT_DOCUMENT_INPUT_STREAM_H__ +#define __GEDIT_DOCUMENT_INPUT_STREAM_H__ + +#include <gio/gio.h> +#include <gtk/gtk.h> + +#include "gedit-document.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_DOCUMENT_INPUT_STREAM (gedit_document_input_stream_get_type ()) +#define GEDIT_DOCUMENT_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENT_INPUT_STREAM, GeditDocumentInputStream)) +#define GEDIT_DOCUMENT_INPUT_STREAM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENT_INPUT_STREAM, GeditDocumentInputStream const)) +#define GEDIT_DOCUMENT_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_DOCUMENT_INPUT_STREAM, GeditDocumentInputStreamClass)) +#define GEDIT_IS_DOCUMENT_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_DOCUMENT_INPUT_STREAM)) +#define GEDIT_IS_DOCUMENT_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENT_INPUT_STREAM)) +#define GEDIT_DOCUMENT_INPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_DOCUMENT_INPUT_STREAM, GeditDocumentInputStreamClass)) + +typedef struct _GeditDocumentInputStream GeditDocumentInputStream; +typedef struct _GeditDocumentInputStreamClass GeditDocumentInputStreamClass; +typedef struct _GeditDocumentInputStreamPrivate GeditDocumentInputStreamPrivate; + +struct _GeditDocumentInputStream +{ + GInputStream parent; + + GeditDocumentInputStreamPrivate *priv; +}; + +struct _GeditDocumentInputStreamClass +{ + GInputStreamClass parent_class; +}; + +GType gedit_document_input_stream_get_type (void) G_GNUC_CONST; + +GInputStream *gedit_document_input_stream_new (GtkTextBuffer *buffer, + GeditDocumentNewlineType type); + +gsize gedit_document_input_stream_get_total_size (GeditDocumentInputStream *stream); + +gsize gedit_document_input_stream_tell (GeditDocumentInputStream *stream); + +G_END_DECLS + +#endif /* __GEDIT_DOCUMENT_INPUT_STREAM_H__ */ diff --git a/gedit/gedit-document-loader.c b/gedit/gedit-document-loader.c new file mode 100755 index 00000000..05368c8b --- /dev/null +++ b/gedit/gedit-document-loader.c @@ -0,0 +1,357 @@ +/* + * gedit-document-loader.c + * This file is part of gedit + * + * 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 gedit Team, 2005-2007. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib/gi18n.h> + +#include "gedit-document-loader.h" +#include "gedit-debug.h" +#include "gedit-metadata-manager.h" +#include "gedit-utils.h" +#include "gedit-marshal.h" +#include "gedit-enum-types.h" + +/* Those are for the the gedit_document_loader_new() factory */ +#include "gedit-gio-document-loader.h" + +G_DEFINE_ABSTRACT_TYPE(GeditDocumentLoader, gedit_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 +gedit_document_loader_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocumentLoader *loader = GEDIT_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 +gedit_document_loader_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocumentLoader *loader = GEDIT_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, gedit_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 +gedit_document_loader_finalize (GObject *object) +{ + GeditDocumentLoader *loader = GEDIT_DOCUMENT_LOADER (object); + + g_free (loader->uri); + + if (loader->info) + g_object_unref (loader->info); + + G_OBJECT_CLASS (gedit_document_loader_parent_class)->finalize (object); +} + +static void +gedit_document_loader_dispose (GObject *object) +{ + GeditDocumentLoader *loader = GEDIT_DOCUMENT_LOADER (object); + + if (loader->info != NULL) + { + g_object_unref (loader->info); + loader->info = NULL; + } + + G_OBJECT_CLASS (gedit_document_loader_parent_class)->dispose (object); +} + +static void +gedit_document_loader_class_init (GeditDocumentLoaderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_document_loader_finalize; + object_class->dispose = gedit_document_loader_dispose; + object_class->get_property = gedit_document_loader_get_property; + object_class->set_property = gedit_document_loader_set_property; + + g_object_class_install_property (object_class, + PROP_DOCUMENT, + g_param_spec_object ("document", + "Document", + "The GeditDocument this GeditDocumentLoader is associated with", + GEDIT_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 GeditDocumentLoader 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", + GEDIT_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", + GEDIT_TYPE_DOCUMENT_NEWLINE_TYPE, + GEDIT_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 (GeditDocumentLoaderClass, loading), + NULL, NULL, + gedit_marshal_VOID__BOOLEAN_POINTER, + G_TYPE_NONE, + 2, + G_TYPE_BOOLEAN, + G_TYPE_POINTER); +} + +static void +gedit_document_loader_init (GeditDocumentLoader *loader) +{ + loader->used = FALSE; + loader->auto_detected_newline_type = GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT; +} + +void +gedit_document_loader_loading (GeditDocumentLoader *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) + gedit_debug_message (DEBUG_LOADER, "load completed"); + else + gedit_debug_message (DEBUG_LOADER, "load failed"); + + g_object_unref (loader); + } +} + +/* This is a factory method that returns an appopriate loader + * for the given uri. + */ +GeditDocumentLoader * +gedit_document_loader_new (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding) +{ + GeditDocumentLoader *loader; + GType loader_type; + + g_return_val_if_fail (GEDIT_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 = GEDIT_TYPE_GIO_DOCUMENT_LOADER; + + loader = GEDIT_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 +gedit_document_loader_load (GeditDocumentLoader *loader) +{ + gedit_debug (DEBUG_LOADER); + + g_return_if_fail (GEDIT_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; + + GEDIT_DOCUMENT_LOADER_GET_CLASS (loader)->load (loader); +} + +gboolean +gedit_document_loader_cancel (GeditDocumentLoader *loader) +{ + gedit_debug (DEBUG_LOADER); + + g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), FALSE); + + return GEDIT_DOCUMENT_LOADER_GET_CLASS (loader)->cancel (loader); +} + +GeditDocument * +gedit_document_loader_get_document (GeditDocumentLoader *loader) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL); + + return loader->document; +} + +/* Returns STDIN_URI if loading from stdin */ +const gchar * +gedit_document_loader_get_uri (GeditDocumentLoader *loader) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL); + + return loader->uri; +} + +goffset +gedit_document_loader_get_bytes_read (GeditDocumentLoader *loader) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), 0); + + return GEDIT_DOCUMENT_LOADER_GET_CLASS (loader)->get_bytes_read (loader); +} + +const GeditEncoding * +gedit_document_loader_get_encoding (GeditDocumentLoader *loader) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL); + + if (loader->encoding != NULL) + return loader->encoding; + + g_return_val_if_fail (loader->auto_detected_encoding != NULL, + gedit_encoding_get_current ()); + + return loader->auto_detected_encoding; +} + +GeditDocumentNewlineType +gedit_document_loader_get_newline_type (GeditDocumentLoader *loader) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), + GEDIT_DOCUMENT_NEWLINE_TYPE_LF); + + return loader->auto_detected_newline_type; +} + +GFileInfo * +gedit_document_loader_get_info (GeditDocumentLoader *loader) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL); + + return loader->info; +} diff --git a/gedit/gedit-document-loader.h b/gedit/gedit-document-loader.h new file mode 100755 index 00000000..a627fba0 --- /dev/null +++ b/gedit/gedit-document-loader.h @@ -0,0 +1,130 @@ +/* + * gedit-document-loader.h + * This file is part of gedit + * + * 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 gedit Team, 2005-2007. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_DOCUMENT_LOADER_H__ +#define __GEDIT_DOCUMENT_LOADER_H__ + +#include <gedit/gedit-document.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_DOCUMENT_LOADER (gedit_document_loader_get_type()) +#define GEDIT_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_DOCUMENT_LOADER, GeditDocumentLoader)) +#define GEDIT_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_DOCUMENT_LOADER, GeditDocumentLoaderClass)) +#define GEDIT_IS_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_DOCUMENT_LOADER)) +#define GEDIT_IS_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENT_LOADER)) +#define GEDIT_DOCUMENT_LOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_DOCUMENT_LOADER, GeditDocumentLoaderClass)) + +/* Private structure type */ +typedef struct _GeditDocumentLoaderPrivate GeditDocumentLoaderPrivate; + +/* + * Main object structure + */ +typedef struct _GeditDocumentLoader GeditDocumentLoader; + +struct _GeditDocumentLoader +{ + GObject object; + + GeditDocument *document; + gboolean used; + + /* Info on the current file */ + GFileInfo *info; + gchar *uri; + const GeditEncoding *encoding; + const GeditEncoding *auto_detected_encoding; + GeditDocumentNewlineType auto_detected_newline_type; +}; + +/* + * Class definition + */ +typedef struct _GeditDocumentLoaderClass GeditDocumentLoaderClass; + +struct _GeditDocumentLoaderClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* loading) (GeditDocumentLoader *loader, + gboolean completed, + const GError *error); + + /* VTable */ + void (* load) (GeditDocumentLoader *loader); + gboolean (* cancel) (GeditDocumentLoader *loader); + goffset (* get_bytes_read) (GeditDocumentLoader *loader); +}; + +/* + * Public methods + */ +GType gedit_document_loader_get_type (void) G_GNUC_CONST; + +/* If enconding == NULL, the encoding will be autodetected */ +GeditDocumentLoader *gedit_document_loader_new (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding); + +void gedit_document_loader_loading (GeditDocumentLoader *loader, + gboolean completed, + GError *error); + +void gedit_document_loader_load (GeditDocumentLoader *loader); +#if 0 +gboolean gedit_document_loader_load_from_stdin (GeditDocumentLoader *loader); +#endif +gboolean gedit_document_loader_cancel (GeditDocumentLoader *loader); + +GeditDocument *gedit_document_loader_get_document (GeditDocumentLoader *loader); + +/* Returns STDIN_URI if loading from stdin */ +#define STDIN_URI "stdin:" +const gchar *gedit_document_loader_get_uri (GeditDocumentLoader *loader); + +const GeditEncoding *gedit_document_loader_get_encoding (GeditDocumentLoader *loader); + +GeditDocumentNewlineType gedit_document_loader_get_newline_type (GeditDocumentLoader *loader); + +goffset gedit_document_loader_get_bytes_read (GeditDocumentLoader *loader); + +/* You can get from the info: content_type, time_modified, standard_size, access_can_write + and also the metadata*/ +GFileInfo *gedit_document_loader_get_info (GeditDocumentLoader *loader); + +G_END_DECLS + +#endif /* __GEDIT_DOCUMENT_LOADER_H__ */ diff --git a/gedit/gedit-document-output-stream.c b/gedit/gedit-document-output-stream.c new file mode 100755 index 00000000..e1ef5af5 --- /dev/null +++ b/gedit/gedit-document-output-stream.c @@ -0,0 +1,391 @@ +/* + * gedit-document-output-stream.c + * This file is part of gedit + * + * Copyright (C) 2010 - Ignacio Casal Quinteiro + * + * gedit 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. + * + * gedit 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 gedit; 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 "gedit-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 GEDIT_DOCUMENT_OUTPUT_STREAM_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object),\ + GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM,\ + GeditDocumentOutputStreamPrivate)) + +#define MAX_UNICHAR_LEN 6 + +struct _GeditDocumentOutputStreamPrivate +{ + GeditDocument *doc; + GtkTextIter pos; + + gchar *buffer; + gsize buflen; + + guint is_initialized : 1; + guint is_closed : 1; +}; + +enum +{ + PROP_0, + PROP_DOCUMENT +}; + +G_DEFINE_TYPE (GeditDocumentOutputStream, gedit_document_output_stream, G_TYPE_OUTPUT_STREAM) + +static gssize gedit_document_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); + +static gboolean gedit_document_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error); + +static void +gedit_document_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocumentOutputStream *stream = GEDIT_DOCUMENT_OUTPUT_STREAM (object); + + switch (prop_id) + { + case PROP_DOCUMENT: + stream->priv->doc = GEDIT_DOCUMENT (g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_document_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocumentOutputStream *stream = GEDIT_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 +gedit_document_output_stream_finalize (GObject *object) +{ + GeditDocumentOutputStream *stream = GEDIT_DOCUMENT_OUTPUT_STREAM (object); + + g_free (stream->priv->buffer); + + G_OBJECT_CLASS (gedit_document_output_stream_parent_class)->finalize (object); +} + +static void +gedit_document_output_stream_constructed (GObject *object) +{ + GeditDocumentOutputStream *stream = GEDIT_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 +gedit_document_output_stream_class_init (GeditDocumentOutputStreamClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass); + + object_class->get_property = gedit_document_output_stream_get_property; + object_class->set_property = gedit_document_output_stream_set_property; + object_class->finalize = gedit_document_output_stream_finalize; + object_class->constructed = gedit_document_output_stream_constructed; + + stream_class->write_fn = gedit_document_output_stream_write; + stream_class->close_fn = gedit_document_output_stream_close; + + g_object_class_install_property (object_class, + PROP_DOCUMENT, + g_param_spec_object ("document", + "Document", + "The document which is written", + GEDIT_TYPE_DOCUMENT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (object_class, sizeof (GeditDocumentOutputStreamPrivate)); +} + +static void +gedit_document_output_stream_init (GeditDocumentOutputStream *stream) +{ + stream->priv = GEDIT_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 GeditDocumentNewlineType +get_newline_type (GtkTextIter *end) +{ + GeditDocumentNewlineType res; + GtkTextIter copy; + gunichar c; + + copy = *end; + c = gtk_text_iter_get_char (©); + + if (g_unichar_break_type (c) == G_UNICODE_BREAK_CARRIAGE_RETURN) + { + if (gtk_text_iter_forward_char (©) && + g_unichar_break_type (gtk_text_iter_get_char (©)) == G_UNICODE_BREAK_LINE_FEED) + { + res = GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF; + } + else + { + res = GEDIT_DOCUMENT_NEWLINE_TYPE_CR; + } + } + else + { + res = GEDIT_DOCUMENT_NEWLINE_TYPE_LF; + } + + return res; +} + +GOutputStream * +gedit_document_output_stream_new (GeditDocument *doc) +{ + return G_OUTPUT_STREAM (g_object_new (GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, + "document", doc, NULL)); +} + +GeditDocumentNewlineType +gedit_document_output_stream_detect_newline_type (GeditDocumentOutputStream *stream) +{ + GeditDocumentNewlineType type; + GtkTextIter iter; + + g_return_val_if_fail (GEDIT_IS_DOCUMENT_OUTPUT_STREAM (stream), + GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT); + + type = GEDIT_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 (GeditDocumentOutputStream *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 (GeditDocumentOutputStream *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 +gedit_document_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GeditDocumentOutputStream *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 = GEDIT_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 +gedit_document_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GeditDocumentOutputStream *ostream = GEDIT_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/gedit/gedit-document-output-stream.h b/gedit/gedit-document-output-stream.h new file mode 100755 index 00000000..a3f99fce --- /dev/null +++ b/gedit/gedit-document-output-stream.h @@ -0,0 +1,64 @@ +/* + * gedit-document-output-stream.h + * This file is part of gedit + * + * Copyright (C) 2010 - Ignacio Casal Quinteiro + * + * gedit 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. + * + * gedit 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 gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + +#ifndef __GEDIT_DOCUMENT_OUTPUT_STREAM_H__ +#define __GEDIT_DOCUMENT_OUTPUT_STREAM_H__ + +#include <gio/gio.h> +#include "gedit-document.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM (gedit_document_output_stream_get_type ()) +#define GEDIT_DOCUMENT_OUTPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStream)) +#define GEDIT_DOCUMENT_OUTPUT_STREAM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStream const)) +#define GEDIT_DOCUMENT_OUTPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStreamClass)) +#define GEDIT_IS_DOCUMENT_OUTPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM)) +#define GEDIT_IS_DOCUMENT_OUTPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM)) +#define GEDIT_DOCUMENT_OUTPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStreamClass)) + +typedef struct _GeditDocumentOutputStream GeditDocumentOutputStream; +typedef struct _GeditDocumentOutputStreamClass GeditDocumentOutputStreamClass; +typedef struct _GeditDocumentOutputStreamPrivate GeditDocumentOutputStreamPrivate; + +struct _GeditDocumentOutputStream +{ + GOutputStream parent; + + GeditDocumentOutputStreamPrivate *priv; +}; + +struct _GeditDocumentOutputStreamClass +{ + GOutputStreamClass parent_class; +}; + +GType gedit_document_output_stream_get_type (void) G_GNUC_CONST; + +GOutputStream *gedit_document_output_stream_new (GeditDocument *doc); + +GeditDocumentNewlineType gedit_document_output_stream_detect_newline_type (GeditDocumentOutputStream *stream); + +G_END_DECLS + +#endif /* __GEDIT_DOCUMENT_OUTPUT_STREAM_H__ */ diff --git a/gedit/gedit-document-saver.c b/gedit/gedit-document-saver.c new file mode 100755 index 00000000..1a064da6 --- /dev/null +++ b/gedit/gedit-document-saver.c @@ -0,0 +1,359 @@ +/* + * gedit-document-saver.c + * This file is part of gedit + * + * 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 gedit Team, 2005-2006. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-document-saver.h" +#include "gedit-debug.h" +#include "gedit-prefs-manager.h" +#include "gedit-marshal.h" +#include "gedit-utils.h" +#include "gedit-enum-types.h" +#include "gedit-gio-document-saver.h" + +G_DEFINE_ABSTRACT_TYPE(GeditDocumentSaver, gedit_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 +gedit_document_saver_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocumentSaver *saver = GEDIT_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 +gedit_document_saver_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocumentSaver *saver = GEDIT_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 +gedit_document_saver_finalize (GObject *object) +{ + GeditDocumentSaver *saver = GEDIT_DOCUMENT_SAVER (object); + + g_free (saver->uri); + + G_OBJECT_CLASS (gedit_document_saver_parent_class)->finalize (object); +} + +static void +gedit_document_saver_dispose (GObject *object) +{ + GeditDocumentSaver *saver = GEDIT_DOCUMENT_SAVER (object); + + if (saver->info != NULL) + { + g_object_unref (saver->info); + saver->info = NULL; + } + + G_OBJECT_CLASS (gedit_document_saver_parent_class)->dispose (object); +} + +static void +gedit_document_saver_class_init (GeditDocumentSaverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_document_saver_finalize; + object_class->dispose = gedit_document_saver_dispose; + object_class->set_property = gedit_document_saver_set_property; + object_class->get_property = gedit_document_saver_get_property; + + g_object_class_install_property (object_class, + PROP_DOCUMENT, + g_param_spec_object ("document", + "Document", + "The GeditDocument this GeditDocumentSaver is associated with", + GEDIT_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 GeditDocumentSaver 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", + GEDIT_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", + GEDIT_TYPE_DOCUMENT_NEWLINE_TYPE, + GEDIT_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", + GEDIT_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 (GeditDocumentSaverClass, saving), + NULL, NULL, + gedit_marshal_VOID__BOOLEAN_POINTER, + G_TYPE_NONE, + 2, + G_TYPE_BOOLEAN, + G_TYPE_POINTER); +} + +static void +gedit_document_saver_init (GeditDocumentSaver *saver) +{ + saver->used = FALSE; +} + +GeditDocumentSaver * +gedit_document_saver_new (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentNewlineType newline_type, + GeditDocumentSaveFlags flags) +{ + GeditDocumentSaver *saver; + GType saver_type; + + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + saver_type = GEDIT_TYPE_GIO_DOCUMENT_SAVER; + + if (encoding == NULL) + encoding = gedit_encoding_get_utf8 (); + + saver = GEDIT_DOCUMENT_SAVER (g_object_new (saver_type, + "document", doc, + "uri", uri, + "encoding", encoding, + "newline_type", newline_type, + "flags", flags, + NULL)); + + return saver; +} + +void +gedit_document_saver_saving (GeditDocumentSaver *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) + gedit_debug_message (DEBUG_SAVER, "save completed"); + else + gedit_debug_message (DEBUG_SAVER, "save failed"); + + g_object_unref (saver); + } +} + +void +gedit_document_saver_save (GeditDocumentSaver *saver, + GTimeVal *old_mtime) +{ + gedit_debug (DEBUG_SAVER); + + g_return_if_fail (GEDIT_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 & GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP) != 0) + saver->keep_backup = FALSE; + else + saver->keep_backup = gedit_prefs_manager_get_create_backup_copy (); + + GEDIT_DOCUMENT_SAVER_GET_CLASS (saver)->save (saver, old_mtime); +} + +GeditDocument * +gedit_document_saver_get_document (GeditDocumentSaver *saver) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), NULL); + + return saver->document; +} + +const gchar * +gedit_document_saver_get_uri (GeditDocumentSaver *saver) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), NULL); + + return saver->uri; +} + +/* Returns 0 if file size is unknown */ +goffset +gedit_document_saver_get_file_size (GeditDocumentSaver *saver) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), 0); + + return GEDIT_DOCUMENT_SAVER_GET_CLASS (saver)->get_file_size (saver); +} + +goffset +gedit_document_saver_get_bytes_written (GeditDocumentSaver *saver) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), 0); + + return GEDIT_DOCUMENT_SAVER_GET_CLASS (saver)->get_bytes_written (saver); +} + +GFileInfo * +gedit_document_saver_get_info (GeditDocumentSaver *saver) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), NULL); + + return saver->info; +} diff --git a/gedit/gedit-document-saver.h b/gedit/gedit-document-saver.h new file mode 100755 index 00000000..ccc0b5c7 --- /dev/null +++ b/gedit/gedit-document-saver.h @@ -0,0 +1,133 @@ +/* + * gedit-document-saver.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_DOCUMENT_SAVER_H__ +#define __GEDIT_DOCUMENT_SAVER_H__ + +#include <gedit/gedit-document.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_DOCUMENT_SAVER (gedit_document_saver_get_type()) +#define GEDIT_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_DOCUMENT_SAVER, GeditDocumentSaver)) +#define GEDIT_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_DOCUMENT_SAVER, GeditDocumentSaverClass)) +#define GEDIT_IS_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_DOCUMENT_SAVER)) +#define GEDIT_IS_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENT_SAVER)) +#define GEDIT_DOCUMENT_SAVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_DOCUMENT_SAVER, GeditDocumentSaverClass)) + +/* + * Main object structure + */ +typedef struct _GeditDocumentSaver GeditDocumentSaver; + +struct _GeditDocumentSaver +{ + GObject object; + + /*< private >*/ + GFileInfo *info; + GeditDocument *document; + gboolean used; + + gchar *uri; + const GeditEncoding *encoding; + GeditDocumentNewlineType newline_type; + + GeditDocumentSaveFlags flags; + + gboolean keep_backup; +}; + +/* + * Class definition + */ +typedef struct _GeditDocumentSaverClass GeditDocumentSaverClass; + +struct _GeditDocumentSaverClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* saving) (GeditDocumentSaver *saver, + gboolean completed, + const GError *error); + + /* VTable */ + void (* save) (GeditDocumentSaver *saver, + GTimeVal *old_mtime); + goffset (* get_file_size) (GeditDocumentSaver *saver); + goffset (* get_bytes_written) (GeditDocumentSaver *saver); +}; + +/* + * Public methods + */ +GType gedit_document_saver_get_type (void) G_GNUC_CONST; + +/* If enconding == NULL, the encoding will be autodetected */ +GeditDocumentSaver *gedit_document_saver_new (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentNewlineType newline_type, + GeditDocumentSaveFlags flags); + +void gedit_document_saver_saving (GeditDocumentSaver *saver, + gboolean completed, + GError *error); +void gedit_document_saver_save (GeditDocumentSaver *saver, + GTimeVal *old_mtime); + +#if 0 +void gedit_document_saver_cancel (GeditDocumentSaver *saver); +#endif + +GeditDocument *gedit_document_saver_get_document (GeditDocumentSaver *saver); + +const gchar *gedit_document_saver_get_uri (GeditDocumentSaver *saver); + +/* If backup_uri is NULL no backup will be made */ +const gchar *gedit_document_saver_get_backup_uri (GeditDocumentSaver *saver); +void *gedit_document_saver_set_backup_uri (GeditDocumentSaver *saver, + const gchar *backup_uri); + +/* Returns 0 if file size is unknown */ +goffset gedit_document_saver_get_file_size (GeditDocumentSaver *saver); + +goffset gedit_document_saver_get_bytes_written (GeditDocumentSaver *saver); + +GFileInfo *gedit_document_saver_get_info (GeditDocumentSaver *saver); + +G_END_DECLS + +#endif /* __GEDIT_DOCUMENT_SAVER_H__ */ diff --git a/gedit/gedit-document.c b/gedit/gedit-document.c new file mode 100755 index 00000000..d9612263 --- /dev/null +++ b/gedit/gedit-document.c @@ -0,0 +1,2732 @@ +/* + * gedit-document.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-prefs-manager-app.h" +#include "gedit-document.h" +#include "gedit-debug.h" +#include "gedit-utils.h" +#include "gedit-language-manager.h" +#include "gedit-style-scheme-manager.h" +#include "gedit-document-loader.h" +#include "gedit-document-saver.h" +#include "gedit-marshal.h" +#include "gedit-enum-types.h" +#include "gedittextregion.h" + +#ifndef ENABLE_GVFS_METADATA +#include "gedit-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 GEDIT_MAX_PATH_LEN MAXPATHLEN +#elif defined (PATH_MAX) +#define GEDIT_MAX_PATH_LEN PATH_MAX +#else +#define GEDIT_MAX_PATH_LEN 2048 +#endif + +#define GEDIT_DOCUMENT_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_DOCUMENT, GeditDocumentPrivate)) + +static void gedit_document_load_real (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create); +static void gedit_document_save_real (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentSaveFlags flags); +static void to_search_region_range (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end); +static void insert_text_cb (GeditDocument *doc, + GtkTextIter *pos, + const gchar *text, + gint length); + +static void delete_range_cb (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end); + +struct _GeditDocumentPrivate +{ + gchar *uri; + gint untitled_number; + gchar *short_name; + + GFileInfo *metadata_info; + + const GeditEncoding *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; + + GeditDocumentNewlineType newline_type; + + /* Temp data while loading */ + GeditDocumentLoader *loader; + gboolean create; /* Create file if uri points + * to a non existing file */ + const GeditEncoding *requested_encoding; + gint requested_line_pos; + + /* Saving stuff */ + GeditDocumentSaver *saver; + + /* Search highlighting support variables */ + GeditTextRegion *to_search_region; + GtkTextTag *found_tag; + + /* Mount operation factory */ + GeditMountOperationFactory 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(GeditDocument, gedit_document, GTK_TYPE_SOURCE_BUFFER) + +GQuark +gedit_document_error_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) + quark = g_quark_from_static_string ("gedit_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 +gedit_document_dispose (GObject *object) +{ + GeditDocument *doc = GEDIT_DOCUMENT (object); + + gedit_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 = gedit_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) + gedit_document_set_metadata (doc, GEDIT_METADATA_ATTRIBUTE_POSITION, + position, NULL); + else + gedit_document_set_metadata (doc, GEDIT_METADATA_ATTRIBUTE_POSITION, + position, GEDIT_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 (gedit_document_parent_class)->dispose (object); +} + +static void +gedit_document_finalize (GObject *object) +{ + GeditDocument *doc = GEDIT_DOCUMENT (object); + + gedit_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 */ + gedit_text_region_destroy (doc->priv->to_search_region, FALSE); + } + + G_OBJECT_CLASS (gedit_document_parent_class)->finalize (object); +} + +static void +gedit_document_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocument *doc = GEDIT_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, gedit_document_get_short_name_for_display (doc)); + break; + case PROP_CONTENT_TYPE: + g_value_take_string (value, gedit_document_get_content_type (doc)); + break; + case PROP_MIME_TYPE: + g_value_take_string (value, gedit_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, gedit_document_get_can_search_again (doc)); + break; + case PROP_ENABLE_SEARCH_HIGHLIGHTING: + g_value_set_boolean (value, gedit_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 +gedit_document_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocument *doc = GEDIT_DOCUMENT (object); + + switch (prop_id) + { + case PROP_ENABLE_SEARCH_HIGHLIGHTING: + gedit_document_set_enable_search_highlighting (doc, + g_value_get_boolean (value)); + break; + case PROP_NEWLINE_TYPE: + gedit_document_set_newline_type (doc, + g_value_get_enum (value)); + break; + case PROP_SHORTNAME: + gedit_document_set_short_name_for_display (doc, + g_value_get_string (value)); + break; + case PROP_CONTENT_TYPE: + gedit_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 (GeditDocument *doc) +{ + if (!doc->priv->stop_cursor_moved_emission) + { + g_signal_emit (doc, + document_signals[CURSOR_MOVED], + 0); + } +} + +static void +gedit_document_mark_set (GtkTextBuffer *buffer, + const GtkTextIter *iter, + GtkTextMark *mark) +{ + GeditDocument *doc = GEDIT_DOCUMENT (buffer); + + if (GTK_TEXT_BUFFER_CLASS (gedit_document_parent_class)->mark_set) + GTK_TEXT_BUFFER_CLASS (gedit_document_parent_class)->mark_set (buffer, + iter, + mark); + + if (mark == gtk_text_buffer_get_insert (buffer)) + { + emit_cursor_moved (doc); + } +} + +static void +gedit_document_changed (GtkTextBuffer *buffer) +{ + emit_cursor_moved (GEDIT_DOCUMENT (buffer)); + + GTK_TEXT_BUFFER_CLASS (gedit_document_parent_class)->changed (buffer); +} + +static void +gedit_document_class_init (GeditDocumentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkTextBufferClass *buf_class = GTK_TEXT_BUFFER_CLASS (klass); + + object_class->dispose = gedit_document_dispose; + object_class->finalize = gedit_document_finalize; + object_class->get_property = gedit_document_get_property; + object_class->set_property = gedit_document_set_property; + + buf_class->mark_set = gedit_document_mark_set; + buf_class->changed = gedit_document_changed; + + klass->load = gedit_document_load_real; + klass->save = gedit_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 GeditEncoding used for the document", + GEDIT_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)); + + /** + * GeditDocument: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", + GEDIT_TYPE_DOCUMENT_NEWLINE_TYPE, + GEDIT_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 (GeditDocumentClass, cursor_moved), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * GeditDocument::load: + * @document: the #GeditDocument. + * @uri: the uri where to load the document from. + * @encoding: the #GeditEncoding 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 (GeditDocumentClass, load), + NULL, NULL, + gedit_marshal_VOID__STRING_BOXED_INT_BOOLEAN, + G_TYPE_NONE, + 4, + G_TYPE_STRING, + /* we rely on the fact that the GeditEncoding pointer stays + * the same forever */ + GEDIT_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 (GeditDocumentClass, loading), + NULL, NULL, + gedit_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 (GeditDocumentClass, loaded), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + /** + * GeditDocument::save: + * @document: the #GeditDocument. + * @uri: the uri where the document is about to be saved. + * @encoding: the #GeditEncoding used to save the document. + * @flags: the #GeditDocumentSaveFlags 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 (GeditDocumentClass, save), + NULL, NULL, + gedit_marshal_VOID__STRING_BOXED_FLAGS, + G_TYPE_NONE, + 3, + G_TYPE_STRING, + /* we rely on the fact that the GeditEncoding pointer stays + * the same forever */ + GEDIT_TYPE_ENCODING | G_SIGNAL_TYPE_STATIC_SCOPE, + GEDIT_TYPE_DOCUMENT_SAVE_FLAGS); + + document_signals[SAVING] = + g_signal_new ("saving", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditDocumentClass, saving), + NULL, NULL, + gedit_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 (GeditDocumentClass, 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 (GeditDocumentClass, search_highlight_updated), + NULL, NULL, + gedit_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(GeditDocumentPrivate)); +} + +static void +set_language (GeditDocument *doc, + GtkSourceLanguage *lang, + gboolean set_by_user) +{ + GtkSourceLanguage *old_lang; + + gedit_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), + gedit_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)) + { + gedit_document_set_metadata (doc, GEDIT_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 (GeditDocument *doc, + const GeditEncoding *encoding, + gboolean set_by_user) +{ + g_return_if_fail (encoding != NULL); + + gedit_debug (DEBUG_DOCUMENT); + + if (doc->priv->encoding == encoding) + return; + + doc->priv->encoding = encoding; + + if (set_by_user) + { + const gchar *charset; + + charset = gedit_encoding_get_charset (encoding); + + gedit_document_set_metadata (doc, GEDIT_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 = gedit_get_style_scheme_manager (); + scheme_id = gedit_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 (GeditDocument *doc, + GParamSpec *pspec, + gpointer useless) +{ +#ifdef ENABLE_GVFS_METADATA + GFile *location; + + location = gedit_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 (GeditDocument *doc, + const gchar *content_type) +{ + gchar *data; + GtkSourceLanguage *language = NULL; + + data = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_LANGUAGE); + + if (data != NULL) + { + gedit_debug_message (DEBUG_DOCUMENT, "Language from metadata: %s", data); + + if (strcmp (data, "_NORMAL_") != 0) + { + language = gtk_source_language_manager_get_language ( + gedit_get_language_manager (), + data); + } + + g_free (data); + } + else + { + GFile *file; + gchar *basename = NULL; + + file = gedit_document_get_location (doc); + gedit_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 ( + gedit_get_language_manager (), + basename, + content_type); + + g_free (basename); + + if (file != NULL) + { + g_object_unref (file); + } + } + + return language; +} + +static void +on_content_type_changed (GeditDocument *doc, + GParamSpec *pspec, + gpointer useless) +{ + if (!doc->priv->language_set_by_user) + { + GtkSourceLanguage *language; + + language = guess_language (doc, doc->priv->content_type); + + gedit_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 +gedit_document_init (GeditDocument *doc) +{ + GtkSourceStyleScheme *style_scheme; + + gedit_debug (DEBUG_DOCUMENT); + + doc->priv = GEDIT_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 = gedit_encoding_get_utf8 (); + + doc->priv->newline_type = GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT; + + gtk_source_buffer_set_max_undo_levels (GTK_SOURCE_BUFFER (doc), + gedit_prefs_manager_get_undo_actions_limit ()); + + gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (doc), + gedit_prefs_manager_get_bracket_matching ()); + + gedit_document_set_enable_search_highlighting (doc, + gedit_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); +} + +GeditDocument * +gedit_document_new (void) +{ + gedit_debug (DEBUG_DOCUMENT); + + return GEDIT_DOCUMENT (g_object_new (GEDIT_TYPE_DOCUMENT, NULL)); +} + +static void +set_content_type_no_guess (GeditDocument *doc, + const gchar *content_type) +{ + gedit_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 (GeditDocument *doc, + const gchar *content_type) +{ + gedit_debug (DEBUG_DOCUMENT); + + if (content_type == NULL) + { + GFile *file; + gchar *guessed_type = NULL; + + /* If content type is null, we guess from the filename */ + file = gedit_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 +gedit_document_set_content_type (GeditDocument *doc, + const gchar *content_type) +{ + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + set_content_type (doc, content_type); +} + +static void +set_uri (GeditDocument *doc, + const gchar *uri) +{ + gedit_debug (DEBUG_DOCUMENT); + + g_return_if_fail ((uri == NULL) || gedit_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 * +gedit_document_get_location (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + return doc->priv->uri == NULL ? NULL : g_file_new_for_uri (doc->priv->uri); +} + +gchar * +gedit_document_get_uri (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + return g_strdup (doc->priv->uri); +} + +void +gedit_document_set_uri (GeditDocument *doc, + const gchar *uri) +{ + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + g_return_if_fail (uri != NULL); + + set_uri (doc, uri); + set_content_type (doc, NULL); +} + +/* Never returns NULL */ +gchar * +gedit_document_get_uri_for_display (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), g_strdup ("")); + + if (doc->priv->uri == NULL) + return g_strdup_printf (_("Unsaved Document %d"), + doc->priv->untitled_number); + else + return gedit_utils_uri_for_display (doc->priv->uri); +} + +/* Never returns NULL */ +gchar * +gedit_document_get_short_name_for_display (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_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 gedit_utils_basename_for_display (doc->priv->uri); +} + +void +gedit_document_set_short_name_for_display (GeditDocument *doc, + const gchar *short_name) +{ + g_return_if_fail (GEDIT_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 * +gedit_document_get_content_type (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + return g_strdup (doc->priv->content_type); +} + +/* Never returns NULL */ +gchar * +gedit_document_get_mime_type (GeditDocument *doc) +{ + gchar *mime_type = NULL; + + g_return_val_if_fail (GEDIT_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 (GeditDocument *doc, + gboolean readonly) +{ + gedit_debug (DEBUG_DOCUMENT); + + readonly = (readonly != FALSE); + + if (doc->priv->readonly == readonly) + return FALSE; + + doc->priv->readonly = readonly; + + return TRUE; +} + +/** + * gedit_document_set_readonly: + * @doc: a #GeditDocument + * @readonly: %TRUE to se the document as read-only + * + * If @readonly is %TRUE sets @doc as read-only. + */ +void +_gedit_document_set_readonly (GeditDocument *doc, + gboolean readonly) +{ + gedit_debug (DEBUG_DOCUMENT); + + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + if (set_readonly (doc, readonly)) + { + g_object_notify (G_OBJECT (doc), "read-only"); + } +} + +gboolean +gedit_document_get_readonly (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), TRUE); + + return doc->priv->readonly; +} + +gboolean +_gedit_document_check_externally_modified (GeditDocument *doc) +{ + GFile *gfile; + GFileInfo *info; + + g_return_val_if_fail (GEDIT_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); + + _gedit_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 (GeditDocument *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 (GeditDocumentLoader *loader, + const GError *error, + GeditDocument *doc) +{ + /* load was successful */ + if (error == NULL || + (error->domain == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK)) + { + GtkTextIter iter; + GFileInfo *info; + const gchar *content_type = NULL; + gboolean read_only = FALSE; + GTimeVal mtime = {0, 0}; + + info = gedit_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, + gedit_document_loader_get_encoding (loader), + (doc->priv->requested_encoding != NULL)); + + set_content_type (doc, content_type); + + gedit_document_set_newline_type (doc, + gedit_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 (gedit_prefs_manager_get_restore_cursor_position ()) + { + gchar *pos; + gint offset; + + pos = gedit_document_get_metadata (doc, GEDIT_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) && + (gedit_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 (GeditDocumentLoader *loader, + gboolean completed, + const GError *error, + GeditDocument *doc) +{ + if (completed) + { + document_loader_loaded (loader, error, doc); + } + else + { + goffset size = 0; + goffset read; + GFileInfo *info; + + info = gedit_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 = gedit_document_loader_get_bytes_read (loader); + + g_signal_emit (doc, + document_signals[LOADING], + 0, + read, + size); + } +} + +static void +gedit_document_load_real (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create) +{ + g_return_if_fail (doc->priv->loader == NULL); + + gedit_debug_message (DEBUG_DOCUMENT, "load_real: uri = %s", uri); + + /* create a loader. It will be destroyed when loading is completed */ + doc->priv->loader = gedit_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); + + gedit_document_loader_load (doc->priv->loader); +} + +/** + * gedit_document_load: + * @doc: the #GeditDocument. + * @uri: the uri where to load the document from. + * @encoding: the #GeditEncoding 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 +gedit_document_load (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create) +{ + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + g_return_if_fail (uri != NULL); + g_return_if_fail (gedit_utils_is_valid_uri (uri)); + + g_signal_emit (doc, document_signals[LOAD], 0, uri, encoding, line_pos, create); +} + +/** + * gedit_document_load_cancel: + * @doc: the #GeditDocument. + * + * Cancel load of a document. + */ +gboolean +gedit_document_load_cancel (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE); + + if (doc->priv->loader == NULL) + return FALSE; + + return gedit_document_loader_cancel (doc->priv->loader); +} + +static void +document_saver_saving (GeditDocumentSaver *saver, + gboolean completed, + const GError *error, + GeditDocument *doc) +{ + gedit_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 = gedit_document_saver_get_uri (saver); + set_uri (doc, uri); + + info = gedit_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); + + _gedit_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 = gedit_document_saver_get_file_size (saver); + written = gedit_document_saver_get_bytes_written (saver); + + gedit_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 +gedit_document_save_real (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentSaveFlags flags) +{ + g_return_if_fail (doc->priv->saver == NULL); + + /* create a saver, it will be destroyed once saving is complete */ + doc->priv->saver = gedit_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; + + gedit_document_saver_save (doc->priv->saver, + &doc->priv->mtime); +} + +/** + * gedit_document_save: + * @doc: the #GeditDocument. + * @flags: optionnal #GeditDocumentSaveFlags. + * + * Save the document to its previous location. This results in the "save" + * signal to be emitted. + */ +void +gedit_document_save (GeditDocument *doc, + GeditDocumentSaveFlags flags) +{ + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_document_save_as: + * @doc: the #GeditDocument. + * @uri: the uri where to save the document. + * @encoding: the #GeditEncoding to encode the document. + * @flags: optionnal #GeditDocumentSaveFlags. + * + * Save the document to a new location. This results in the "save" signal + * to be emitted. + */ +void +gedit_document_save_as (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentSaveFlags flags) +{ + g_return_if_fail (GEDIT_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 | GEDIT_DOCUMENT_SAVE_IGNORE_MTIME); +} + +gboolean +gedit_document_insert_file (GeditDocument *doc, + GtkTextIter *iter, + const gchar *uri, + const GeditEncoding *encoding) +{ + g_return_val_if_fail (GEDIT_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 +gedit_document_is_untouched (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), TRUE); + + return (doc->priv->uri == NULL) && + (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc))); +} + +gboolean +gedit_document_is_untitled (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), TRUE); + + return (doc->priv->uri == NULL); +} + +gboolean +gedit_document_is_local (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE); + + if (doc->priv->uri == NULL) + { + return FALSE; + } + + return gedit_utils_uri_has_file_scheme (doc->priv->uri); +} + +gboolean +gedit_document_get_deleted (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE); + + return doc->priv->uri && !gedit_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 +gedit_document_goto_line (GeditDocument *doc, + gint line) +{ + gboolean ret = TRUE; + guint line_count; + GtkTextIter iter; + + gedit_debug (DEBUG_DOCUMENT); + + g_return_val_if_fail (GEDIT_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 +gedit_document_goto_line_offset (GeditDocument *doc, + gint line, + gint line_offset) +{ + gboolean ret = TRUE; + guint offset_count; + GtkTextIter iter; + + g_return_val_if_fail (GEDIT_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 +gedit_document_set_search_text (GeditDocument *doc, + const gchar *text, + guint flags) +{ + gchar *converted_text; + gboolean notify = FALSE; + gboolean update_to_search_region = FALSE; + + g_return_if_fail (GEDIT_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)); + + gedit_debug_message (DEBUG_DOCUMENT, "text = %s", text); + + if (text != NULL) + { + if (*text != '\0') + { + converted_text = gedit_utils_unescape_search_text (text); + notify = !gedit_document_get_can_search_again (doc); + } + else + { + converted_text = g_strdup(""); + notify = gedit_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 (!GEDIT_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 * +gedit_document_get_search_text (GeditDocument *doc, + guint *flags) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + if (flags != NULL) + *flags = doc->priv->search_flags; + + return gedit_utils_escape_search_text (doc->priv->search_text); +} + +gboolean +gedit_document_get_can_search_again (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE); + + return ((doc->priv->search_text != NULL) && + (*doc->priv->search_text != '\0')); +} + +gboolean +gedit_document_search_forward (GeditDocument *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 (GEDIT_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) + { + gedit_debug_message (DEBUG_DOCUMENT, "doc->priv->search_text == NULL\n"); + return FALSE; + } + else + gedit_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 (!GEDIT_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 && GEDIT_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 +gedit_document_search_backward (GeditDocument *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 (GEDIT_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) + { + gedit_debug_message (DEBUG_DOCUMENT, "doc->priv->search_text == NULL\n"); + return FALSE; + } + else + gedit_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 (!GEDIT_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 && GEDIT_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 +gedit_document_replace_all (GeditDocument *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 (GEDIT_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 = gedit_utils_unescape_search_text (find); + + replace_text = gedit_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 (!GEDIT_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 = gedit_document_get_enable_search_highlighting (doc); + gedit_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 && GEDIT_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); + gedit_document_set_enable_search_highlighting (doc, search_highliting); + + g_free (search_text); + g_free (replace_text); + + return cont; +} + +void +gedit_document_set_language (GeditDocument *doc, + GtkSourceLanguage *lang) +{ + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + set_language (doc, lang, TRUE); +} + +GtkSourceLanguage * +gedit_document_get_language (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + return gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (doc)); +} + +const GeditEncoding * +gedit_document_get_encoding (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + return doc->priv->encoding; +} + +glong +_gedit_document_get_seconds_since_last_save_or_load (GeditDocument *doc) +{ + GTimeVal current_time; + + gedit_debug (DEBUG_DOCUMENT); + + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), -1); + + g_get_current_time (¤t_time); + + return (current_time.tv_sec - doc->priv->time_of_last_save_or_load.tv_sec); +} + +static void +get_search_match_colors (GeditDocument *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: + gedit_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 (GeditDocument *doc, + GParamSpec *pspec, + gpointer data) +{ + GdkColor fg; + GdkColor bg; + gboolean fg_set; + gboolean bg_set; + + gedit_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 (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end) +{ + GtkTextIter iter; + GtkTextIter m_start; + GtkTextIter m_end; + GtkSourceSearchFlags search_flags = 0; + gboolean found = TRUE; + + GtkTextBuffer *buffer; + + gedit_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 (!GEDIT_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 && GEDIT_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 (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end) +{ + gedit_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 */ + gedit_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 +_gedit_document_search_region (GeditDocument *doc, + const GtkTextIter *start, + const GtkTextIter *end) +{ + GeditTextRegion *region; + + gedit_debug (DEBUG_DOCUMENT); + + g_return_if_fail (GEDIT_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 = gedit_text_region_intersect (doc->priv->to_search_region, + start, + end); + if (region) + { + gint i; + GtkTextIter start_search; + GtkTextIter end_search; + + i = gedit_text_region_subregions (region); + gedit_text_region_nth_subregion (region, + 0, + &start_search, + NULL); + + gedit_text_region_nth_subregion (region, + i - 1, + NULL, + &end_search); + + gedit_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 */ + gedit_text_region_subtract (doc->priv->to_search_region, + start, + end); + } +} + +static void +insert_text_cb (GeditDocument *doc, + GtkTextIter *pos, + const gchar *text, + gint length) +{ + GtkTextIter start; + GtkTextIter end; + + gedit_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 (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end) +{ + GtkTextIter d_start; + GtkTextIter d_end; + + gedit_debug (DEBUG_DOCUMENT); + + d_start = *start; + d_end = *end; + + to_search_region_range (doc, &d_start, &d_end); +} + +void +gedit_document_set_enable_search_highlighting (GeditDocument *doc, + gboolean enable) +{ + g_return_if_fail (GEDIT_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); + } + + gedit_text_region_destroy (doc->priv->to_search_region, + TRUE); + doc->priv->to_search_region = NULL; + } + else + { + doc->priv->to_search_region = gedit_text_region_new (GTK_TEXT_BUFFER (doc)); + if (gedit_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 +gedit_document_get_enable_search_highlighting (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE); + + return (doc->priv->to_search_region != NULL); +} + +void +gedit_document_set_newline_type (GeditDocument *doc, + GeditDocumentNewlineType newline_type) +{ + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + if (doc->priv->newline_type != newline_type) + { + doc->priv->newline_type = newline_type; + + g_object_notify (G_OBJECT (doc), "newline-type"); + } +} + +GeditDocumentNewlineType +gedit_document_get_newline_type (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), 0); + + return doc->priv->newline_type; +} + +void +_gedit_document_set_mount_operation_factory (GeditDocument *doc, + GeditMountOperationFactory callback, + gpointer userdata) +{ + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + doc->priv->mount_operation_factory = callback; + doc->priv->mount_operation_userdata = userdata; +} + +GMountOperation * +_gedit_document_create_mount_operation (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_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 * +gedit_document_get_metadata (GeditDocument *doc, + const gchar *key) +{ + gchar *value = NULL; + + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + g_return_val_if_fail (key != NULL, NULL); + + if (!gedit_document_is_untitled (doc)) + { + value = gedit_metadata_manager_get (doc->priv->uri, key); + } + + return value; +} + +void +gedit_document_set_metadata (GeditDocument *doc, + const gchar *first_key, + ...) +{ + const gchar *key; + const gchar *value; + va_list var_args; + + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + g_return_if_fail (first_key != NULL); + + if (gedit_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 *); + + gedit_metadata_manager_set (doc->priv->uri, + key, + value); + } + + va_end (var_args); +} + +#else + +/** + * gedit_document_get_metadata: + * @doc: a #GeditDocument + * @key: name of the key + * + * Gets the metadata assigned to @key. + * + * Returns: the value assigned to @key. + */ +gchar * +gedit_document_get_metadata (GeditDocument *doc, + const gchar *key) +{ + gchar *value = NULL; + + g_return_val_if_fail (GEDIT_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); +} + +/** + * gedit_document_set_metadata: + * @doc: a #GeditDocument + * @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 +gedit_document_set_metadata (GeditDocument *doc, + const gchar *first_key, + ...) +{ + const gchar *key; + const gchar *value; + va_list var_args; + GFileInfo *info; + GFile *location; + + g_return_if_fail (GEDIT_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 = gedit_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/gedit/gedit-document.h b/gedit/gedit-document.h new file mode 100755 index 00000000..cf966b15 --- /dev/null +++ b/gedit/gedit-document.h @@ -0,0 +1,338 @@ +/* + * gedit-document.h + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_DOCUMENT_H__ +#define __GEDIT_DOCUMENT_H__ + +#include <gio/gio.h> +#include <gtk/gtk.h> +#include <gtksourceview/gtksourcebuffer.h> + +#include <gedit/gedit-encodings.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_DOCUMENT (gedit_document_get_type()) +#define GEDIT_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_DOCUMENT, GeditDocument)) +#define GEDIT_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_DOCUMENT, GeditDocumentClass)) +#define GEDIT_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_DOCUMENT)) +#define GEDIT_IS_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENT)) +#define GEDIT_DOCUMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_DOCUMENT, GeditDocumentClass)) + +#ifdef G_OS_WIN32 +#define GEDIT_METADATA_ATTRIBUTE_POSITION "position" +#define GEDIT_METADATA_ATTRIBUTE_ENCODING "encoding" +#define GEDIT_METADATA_ATTRIBUTE_LANGUAGE "language" +#else +#define GEDIT_METADATA_ATTRIBUTE_POSITION "metadata::gedit-position" +#define GEDIT_METADATA_ATTRIBUTE_ENCODING "metadata::gedit-encoding" +#define GEDIT_METADATA_ATTRIBUTE_LANGUAGE "metadata::gedit-language" +#endif + +typedef enum +{ + GEDIT_DOCUMENT_NEWLINE_TYPE_LF, + GEDIT_DOCUMENT_NEWLINE_TYPE_CR, + GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF +} GeditDocumentNewlineType; + +#ifdef G_OS_WIN32 +#define GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF +#else +#define GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT GEDIT_DOCUMENT_NEWLINE_TYPE_LF +#endif + +typedef enum +{ + GEDIT_SEARCH_DONT_SET_FLAGS = 1 << 0, + GEDIT_SEARCH_ENTIRE_WORD = 1 << 1, + GEDIT_SEARCH_CASE_SENSITIVE = 1 << 2 + +} GeditSearchFlags; + +/** + * GeditDocumentSaveFlags: + * @GEDIT_DOCUMENT_SAVE_IGNORE_MTIME: save file despite external modifications. + * @GEDIT_DOCUMENT_SAVE_IGNORE_BACKUP: write the file directly without attempting to backup. + * @GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP: preserve previous backup file, needed to support autosaving. + */ +typedef enum +{ + GEDIT_DOCUMENT_SAVE_IGNORE_MTIME = 1 << 0, + GEDIT_DOCUMENT_SAVE_IGNORE_BACKUP = 1 << 1, + GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP = 1 << 2 +} GeditDocumentSaveFlags; + +/* Private structure type */ +typedef struct _GeditDocumentPrivate GeditDocumentPrivate; + +/* + * Main object structure + */ +typedef struct _GeditDocument GeditDocument; + +struct _GeditDocument +{ + GtkSourceBuffer buffer; + + /*< private > */ + GeditDocumentPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditDocumentClass GeditDocumentClass; + +struct _GeditDocumentClass +{ + GtkSourceBufferClass parent_class; + + /* Signals */ // CHECK: ancora da rivedere + + void (* cursor_moved) (GeditDocument *document); + + /* Document load */ + void (* load) (GeditDocument *document, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create); + + void (* loading) (GeditDocument *document, + goffset size, + goffset total_size); + + void (* loaded) (GeditDocument *document, + const GError *error); + + /* Document save */ + void (* save) (GeditDocument *document, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentSaveFlags flags); + + void (* saving) (GeditDocument *document, + goffset size, + goffset total_size); + + void (* saved) (GeditDocument *document, + const GError *error); + + void (* search_highlight_updated) + (GeditDocument *document, + GtkTextIter *start, + GtkTextIter *end); +}; + + +#define GEDIT_DOCUMENT_ERROR gedit_document_error_quark () + +enum +{ + GEDIT_DOCUMENT_ERROR_EXTERNALLY_MODIFIED, + GEDIT_DOCUMENT_ERROR_CANT_CREATE_BACKUP, + GEDIT_DOCUMENT_ERROR_TOO_BIG, + GEDIT_DOCUMENT_ERROR_ENCODING_AUTO_DETECTION_FAILED, + GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK, + GEDIT_DOCUMENT_NUM_ERRORS +}; + +GQuark gedit_document_error_quark (void); + +GType gedit_document_get_type (void) G_GNUC_CONST; + +GeditDocument *gedit_document_new (void); + +GFile *gedit_document_get_location (GeditDocument *doc); + +gchar *gedit_document_get_uri (GeditDocument *doc); +void gedit_document_set_uri (GeditDocument *doc, + const gchar *uri); + +gchar *gedit_document_get_uri_for_display + (GeditDocument *doc); +gchar *gedit_document_get_short_name_for_display + (GeditDocument *doc); + +void gedit_document_set_short_name_for_display + (GeditDocument *doc, + const gchar *name); + +gchar *gedit_document_get_content_type + (GeditDocument *doc); + +void gedit_document_set_content_type + (GeditDocument *doc, + const gchar *content_type); + +gchar *gedit_document_get_mime_type (GeditDocument *doc); + +gboolean gedit_document_get_readonly (GeditDocument *doc); + +void gedit_document_load (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create); + +gboolean gedit_document_insert_file (GeditDocument *doc, + GtkTextIter *iter, + const gchar *uri, + const GeditEncoding *encoding); + +gboolean gedit_document_load_cancel (GeditDocument *doc); + +void gedit_document_save (GeditDocument *doc, + GeditDocumentSaveFlags flags); + +void gedit_document_save_as (GeditDocument *doc, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentSaveFlags flags); + +gboolean gedit_document_is_untouched (GeditDocument *doc); +gboolean gedit_document_is_untitled (GeditDocument *doc); + +gboolean gedit_document_is_local (GeditDocument *doc); + +gboolean gedit_document_get_deleted (GeditDocument *doc); + +gboolean gedit_document_goto_line (GeditDocument *doc, + gint line); + +gboolean gedit_document_goto_line_offset(GeditDocument *doc, + gint line, + gint line_offset); + +void gedit_document_set_search_text (GeditDocument *doc, + const gchar *text, + guint flags); + +gchar *gedit_document_get_search_text (GeditDocument *doc, + guint *flags); + +gboolean gedit_document_get_can_search_again + (GeditDocument *doc); + +gboolean gedit_document_search_forward (GeditDocument *doc, + const GtkTextIter *start, + const GtkTextIter *end, + GtkTextIter *match_start, + GtkTextIter *match_end); + +gboolean gedit_document_search_backward (GeditDocument *doc, + const GtkTextIter *start, + const GtkTextIter *end, + GtkTextIter *match_start, + GtkTextIter *match_end); + +gint gedit_document_replace_all (GeditDocument *doc, + const gchar *find, + const gchar *replace, + guint flags); + +void gedit_document_set_language (GeditDocument *doc, + GtkSourceLanguage *lang); +GtkSourceLanguage + *gedit_document_get_language (GeditDocument *doc); + +const GeditEncoding + *gedit_document_get_encoding (GeditDocument *doc); + +void gedit_document_set_enable_search_highlighting + (GeditDocument *doc, + gboolean enable); + +gboolean gedit_document_get_enable_search_highlighting + (GeditDocument *doc); + +void gedit_document_set_newline_type (GeditDocument *doc, + GeditDocumentNewlineType newline_type); + +GeditDocumentNewlineType + gedit_document_get_newline_type (GeditDocument *doc); + +gchar *gedit_document_get_metadata (GeditDocument *doc, + const gchar *key); + +void gedit_document_set_metadata (GeditDocument *doc, + const gchar *first_key, + ...); + +/* + * Non exported functions + */ +void _gedit_document_set_readonly (GeditDocument *doc, + gboolean readonly); + +glong _gedit_document_get_seconds_since_last_save_or_load + (GeditDocument *doc); + +/* Note: this is a sync stat: use only on local files */ +gboolean _gedit_document_check_externally_modified + (GeditDocument *doc); + +void _gedit_document_search_region (GeditDocument *doc, + const GtkTextIter *start, + const GtkTextIter *end); + +/* Search macros */ +#define GEDIT_SEARCH_IS_DONT_SET_FLAGS(sflags) ((sflags & GEDIT_SEARCH_DONT_SET_FLAGS) != 0) +#define GEDIT_SEARCH_SET_DONT_SET_FLAGS(sflags,state) ((state == TRUE) ? \ +(sflags |= GEDIT_SEARCH_DONT_SET_FLAGS) : (sflags &= ~GEDIT_SEARCH_DONT_SET_FLAGS)) + +#define GEDIT_SEARCH_IS_ENTIRE_WORD(sflags) ((sflags & GEDIT_SEARCH_ENTIRE_WORD) != 0) +#define GEDIT_SEARCH_SET_ENTIRE_WORD(sflags,state) ((state == TRUE) ? \ +(sflags |= GEDIT_SEARCH_ENTIRE_WORD) : (sflags &= ~GEDIT_SEARCH_ENTIRE_WORD)) + +#define GEDIT_SEARCH_IS_CASE_SENSITIVE(sflags) ((sflags & GEDIT_SEARCH_CASE_SENSITIVE) != 0) +#define GEDIT_SEARCH_SET_CASE_SENSITIVE(sflags,state) ((state == TRUE) ? \ +(sflags |= GEDIT_SEARCH_CASE_SENSITIVE) : (sflags &= ~GEDIT_SEARCH_CASE_SENSITIVE)) + +typedef GMountOperation *(*GeditMountOperationFactory)(GeditDocument *doc, + gpointer userdata); + +void _gedit_document_set_mount_operation_factory + (GeditDocument *doc, + GeditMountOperationFactory callback, + gpointer userdata); +GMountOperation + *_gedit_document_create_mount_operation + (GeditDocument *doc); + +G_END_DECLS + +#endif /* __GEDIT_DOCUMENT_H__ */ diff --git a/gedit/gedit-documents-panel.c b/gedit/gedit-documents-panel.c new file mode 100755 index 00000000..911654b2 --- /dev/null +++ b/gedit/gedit-documents-panel.c @@ -0,0 +1,828 @@ +/* + * gedit-documents-panel.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-documents-panel.h" +#include "gedit-utils.h" +#include "gedit-notebook.h" + +#include <glib/gi18n.h> + +#define GEDIT_DOCUMENTS_PANEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_DOCUMENTS_PANEL, \ + GeditDocumentsPanelPrivate)) + +struct _GeditDocumentsPanelPrivate +{ + GeditWindow *window; + + GtkWidget *treeview; + GtkTreeModel *model; + + guint adding_tab : 1; + guint is_reodering : 1; +}; + +G_DEFINE_TYPE(GeditDocumentsPanel, gedit_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 (GeditTab *tab) +{ + GeditDocument *doc; + gchar *name; + gchar *docname; + gchar *tab_name; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + doc = gedit_tab_get_document (tab); + + name = gedit_document_get_short_name_for_display (doc); + + /* Truncate the name so it doesn't get insanely wide. */ + docname = gedit_utils_str_middle_truncate (name, MAX_DOC_NAME_LENGTH); + + if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc))) + { + if (gedit_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 (gedit_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 (GeditDocumentsPanel *panel, GeditTab *tab, GtkTreeIter *iter) +{ + gint num; + GtkWidget *nb; + GtkTreePath *path; + + nb = _gedit_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 (GeditWindow *window, + GeditTab *tab, + GeditDocumentsPanel *panel) +{ + g_return_if_fail (tab != NULL); + + if (!_gedit_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 (GeditDocumentsPanel *panel) +{ + /* TODO: refresh the list only if the panel is visible */ + + GList *tabs; + GList *l; + GtkWidget *nb; + GtkListStore *list_store; + GeditTab *active_tab; + + /* g_debug ("refresh_list"); */ + + list_store = GTK_LIST_STORE (panel->priv->model); + + gtk_list_store_clear (list_store); + + active_tab = gedit_window_get_active_tab (panel->priv->window); + + nb = _gedit_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 (GEDIT_TAB (l->data)); + pixbuf = _gedit_tab_get_icon (GEDIT_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 (GeditTab *tab, + GParamSpec *pspec, + GeditDocumentsPanel *panel) +{ + GdkPixbuf *pixbuf; + gchar *name; + GtkTreeIter iter; + + get_iter_from_tab (panel, tab, &iter); + + name = tab_get_name (tab); + pixbuf = _gedit_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 (GeditWindow *window, + GeditTab *tab, + GeditDocumentsPanel *panel) +{ + g_signal_handlers_disconnect_by_func (tab, + G_CALLBACK (sync_name_and_icon), + panel); + + if (_gedit_window_is_removing_tabs (window)) + gtk_list_store_clear (GTK_LIST_STORE (panel->priv->model)); + else + refresh_list (panel); +} + +static void +window_tab_added (GeditWindow *window, + GeditTab *tab, + GeditDocumentsPanel *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 + { + GeditTab *active_tab; + + gtk_list_store_append (GTK_LIST_STORE (panel->priv->model), + &iter); + + active_tab = gedit_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 = _gedit_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 (GeditWindow *window, + GeditDocumentsPanel *panel) +{ + if (panel->priv->is_reodering) + return; + + refresh_list (panel); +} + +static void +set_window (GeditDocumentsPanel *panel, + GeditWindow *window) +{ + g_return_if_fail (panel->priv->window == NULL); + g_return_if_fail (GEDIT_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, + GeditDocumentsPanel *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 (gedit_window_get_active_tab (panel->priv->window) != tab) + { + gedit_window_set_active_tab (panel->priv->window, + GEDIT_TAB (tab)); + } + } +} + +static void +gedit_documents_panel_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocumentsPanel *panel = GEDIT_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 +gedit_documents_panel_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, + GEDIT_DOCUMENTS_PANEL_GET_PRIVATE (panel)->window); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_documents_panel_finalize (GObject *object) +{ + /* GeditDocumentsPanel *tab = GEDIT_DOCUMENTS_PANEL (object); */ + + /* TODO: disconnect signal with window */ + + G_OBJECT_CLASS (gedit_documents_panel_parent_class)->finalize (object); +} + +static void +gedit_documents_panel_dispose (GObject *object) +{ + GeditDocumentsPanel *panel = GEDIT_DOCUMENTS_PANEL (object); + + if (panel->priv->window != NULL) + { + g_object_unref (panel->priv->window); + panel->priv->window = NULL; + } + + G_OBJECT_CLASS (gedit_documents_panel_parent_class)->dispose (object); +} + +static void +gedit_documents_panel_class_init (GeditDocumentsPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_documents_panel_finalize; + object_class->dispose = gedit_documents_panel_dispose; + object_class->get_property = gedit_documents_panel_get_property; + object_class->set_property = gedit_documents_panel_set_property; + + g_object_class_install_property (object_class, + PROP_WINDOW, + g_param_spec_object ("window", + "Window", + "The GeditWindow this GeditDocumentsPanel is associated with", + GEDIT_TYPE_WINDOW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (object_class, sizeof (GeditDocumentsPanelPrivate)); +} + +static GtkTreePath * +get_current_path (GeditDocumentsPanel *panel) +{ + gint num; + GtkWidget *nb; + GtkTreePath *path; + + nb = _gedit_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, + GeditDocumentsPanel *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 (GeditDocumentsPanel *panel, + GdkEventButton *event) +{ + GtkWidget *menu; + + menu = gtk_ui_manager_get_widget (gedit_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, + GeditDocumentsPanel *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, + GeditDocumentsPanel *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 = _gedit_tab_get_tooltips (GEDIT_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, + GeditDocumentsPanel *panel) +{ + GeditTab *tab; + gint *indeces; + GtkWidget *nb; + gint old_position; + gint new_position; + + if (panel->priv->adding_tab) + return; + + tab = gedit_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 = _gedit_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); + + gedit_notebook_reorder_tab (GEDIT_NOTEBOOK (nb), + tab, + new_position); + + panel->priv->is_reodering = FALSE; +} + +static void +gedit_documents_panel_init (GeditDocumentsPanel *panel) +{ + GtkWidget *sw; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkTreeSelection *selection; + + panel->priv = GEDIT_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 * +gedit_documents_panel_new (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return GTK_WIDGET (g_object_new (GEDIT_TYPE_DOCUMENTS_PANEL, + "window", window, + NULL)); +} diff --git a/gedit/gedit-documents-panel.h b/gedit/gedit-documents-panel.h new file mode 100755 index 00000000..b6191bf8 --- /dev/null +++ b/gedit/gedit-documents-panel.h @@ -0,0 +1,85 @@ +/* + * gedit-documents-panel.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_DOCUMENTS_PANEL_H__ +#define __GEDIT_DOCUMENTS_PANEL_H__ + +#include <gtk/gtk.h> + +#include <gedit/gedit-window.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_DOCUMENTS_PANEL (gedit_documents_panel_get_type()) +#define GEDIT_DOCUMENTS_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_DOCUMENTS_PANEL, GeditDocumentsPanel)) +#define GEDIT_DOCUMENTS_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_DOCUMENTS_PANEL, GeditDocumentsPanelClass)) +#define GEDIT_IS_DOCUMENTS_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_DOCUMENTS_PANEL)) +#define GEDIT_IS_DOCUMENTS_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENTS_PANEL)) +#define GEDIT_DOCUMENTS_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_DOCUMENTS_PANEL, GeditDocumentsPanelClass)) + +/* Private structure type */ +typedef struct _GeditDocumentsPanelPrivate GeditDocumentsPanelPrivate; + +/* + * Main object structure + */ +typedef struct _GeditDocumentsPanel GeditDocumentsPanel; + +struct _GeditDocumentsPanel +{ + GtkVBox vbox; + + /*< private > */ + GeditDocumentsPanelPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditDocumentsPanelClass GeditDocumentsPanelClass; + +struct _GeditDocumentsPanelClass +{ + GtkVBoxClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_documents_panel_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_documents_panel_new (GeditWindow *window); + +G_END_DECLS + +#endif /* __GEDIT_DOCUMENTS_PANEL_H__ */ diff --git a/gedit/gedit-encodings-combo-box.c b/gedit/gedit-encodings-combo-box.c new file mode 100755 index 00000000..1626bb97 --- /dev/null +++ b/gedit/gedit-encodings-combo-box.c @@ -0,0 +1,468 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-encodings-combo-box.c + * This file is part of gedit + * + * 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 gedit Team, 2003-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id: gedit-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 <gedit/gedit-encodings-combo-box.h> +#include <gedit/gedit-prefs-manager.h> +#include <gedit/dialogs/gedit-encodings-dialog.h> + +#define ENCODING_KEY "Enconding" + +#define GEDIT_ENCODINGS_COMBO_BOX_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_ENCODINGS_COMBO_BOX, \ + GeditEncodingsComboBoxPrivate)) + +struct _GeditEncodingsComboBoxPrivate +{ + 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(GeditEncodingsComboBox, gedit_encodings_combo_box, GTK_TYPE_COMBO_BOX) + +static void update_menu (GeditEncodingsComboBox *combo_box); + +static void +gedit_encodings_combo_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditEncodingsComboBox *combo; + + combo = GEDIT_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 +gedit_encodings_combo_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditEncodingsComboBox *combo; + + combo = GEDIT_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 +gedit_encodings_combo_box_dispose (GObject *object) +{ + GeditEncodingsComboBox *combo = GEDIT_ENCODINGS_COMBO_BOX (object); + + if (combo->priv->store != NULL) + { + g_object_unref (combo->priv->store); + combo->priv->store = NULL; + } + + G_OBJECT_CLASS (gedit_encodings_combo_box_parent_class)->dispose (object); +} + +static void +gedit_encodings_combo_box_class_init (GeditEncodingsComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gedit_encodings_combo_box_set_property; + object_class->get_property = gedit_encodings_combo_box_get_property; + object_class->dispose = gedit_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 (GeditEncodingsComboBoxPrivate)); +} + +static void +dialog_response_cb (GtkDialog *dialog, + gint response_id, + GeditEncodingsComboBox *menu) +{ + if (response_id == GTK_RESPONSE_OK) + { + update_menu (menu); + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +add_or_remove (GeditEncodingsComboBox *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 = gedit_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 (GeditEncodingsComboBox *menu) +{ + GtkListStore *store; + GtkTreeIter iter; + GSList *encodings, *l; + gchar *str; + const GeditEncoding *utf8_encoding; + const GeditEncoding *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 = gedit_encoding_get_utf8 (); + current_encoding = gedit_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 = gedit_encoding_to_string (utf8_encoding); + else + str = g_strdup_printf (_("Current Locale (%s)"), + gedit_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)"), + gedit_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 = gedit_prefs_manager_get_shown_in_menu_encodings (); + + for (l = encodings; l != NULL; l = g_slist_next (l)) + { + const GeditEncoding *enc = (const GeditEncoding *)l->data; + + if ((enc != current_encoding) && + (enc != utf8_encoding) && + (enc != NULL)) + { + str = gedit_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 (gedit_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 +gedit_encodings_combo_box_init (GeditEncodingsComboBox *menu) +{ + GtkCellRenderer *text_renderer; + + menu->priv = GEDIT_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 * +gedit_encodings_combo_box_new (gboolean save_mode) +{ + return g_object_new (GEDIT_TYPE_ENCODINGS_COMBO_BOX, + "save_mode", save_mode, + NULL); +} + +const GeditEncoding * +gedit_encodings_combo_box_get_selected_encoding (GeditEncodingsComboBox *menu) +{ + GtkTreeIter iter; + + g_return_val_if_fail (GEDIT_IS_ENCODINGS_COMBO_BOX (menu), NULL); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (menu), &iter)) + { + const GeditEncoding *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 +gedit_encodings_combo_box_set_selected_encoding (GeditEncodingsComboBox *menu, + const GeditEncoding *encoding) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gboolean b; + g_return_if_fail (GEDIT_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 GeditEncoding *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/gedit/gedit-encodings-combo-box.h b/gedit/gedit-encodings-combo-box.h new file mode 100755 index 00000000..586f8007 --- /dev/null +++ b/gedit/gedit-encodings-combo-box.h @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-encodings-combo-box.h + * This file is part of gedit + * + * 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 gedit Team, 2003-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id: gedit-encodings-option-menu.h 4429 2005-12-12 17:28:04Z pborelli $ + */ + +#ifndef __GEDIT_ENCODINGS_COMBO_BOX_H__ +#define __GEDIT_ENCODINGS_COMBO_BOX_H__ + +#include <gtk/gtkoptionmenu.h> +#include <gedit/gedit-encodings.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_ENCODINGS_COMBO_BOX (gedit_encodings_combo_box_get_type ()) +#define GEDIT_ENCODINGS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_ENCODINGS_COMBO_BOX, GeditEncodingsComboBox)) +#define GEDIT_ENCODINGS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_ENCODINGS_COMBO_BOX, GeditEncodingsComboBoxClass)) +#define GEDIT_IS_ENCODINGS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_ENCODINGS_COMBO_BOX)) +#define GEDIT_IS_ENCODINGS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_ENCODINGS_COMBO_BOX)) +#define GEDIT_ENCODINGS_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_ENCODINGS_COMBO_BOX, GeditEncodingsComboBoxClass)) + + +typedef struct _GeditEncodingsComboBox GeditEncodingsComboBox; +typedef struct _GeditEncodingsComboBoxClass GeditEncodingsComboBoxClass; + +typedef struct _GeditEncodingsComboBoxPrivate GeditEncodingsComboBoxPrivate; + +struct _GeditEncodingsComboBox +{ + GtkComboBox parent; + + GeditEncodingsComboBoxPrivate *priv; +}; + +struct _GeditEncodingsComboBoxClass +{ + GtkComboBoxClass parent_class; +}; + +GType gedit_encodings_combo_box_get_type (void) G_GNUC_CONST; + +/* Constructor */ +GtkWidget *gedit_encodings_combo_box_new (gboolean save_mode); + +const GeditEncoding *gedit_encodings_combo_box_get_selected_encoding (GeditEncodingsComboBox *menu); +void gedit_encodings_combo_box_set_selected_encoding (GeditEncodingsComboBox *menu, + const GeditEncoding *encoding); + +G_END_DECLS + +#endif /* __GEDIT_ENCODINGS_COMBO_BOX_H__ */ + + diff --git a/gedit/gedit-encodings.c b/gedit/gedit-encodings.c new file mode 100755 index 00000000..cf97bf56 --- /dev/null +++ b/gedit/gedit-encodings.c @@ -0,0 +1,473 @@ +/* + * gedit-encodings.c + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-encodings.h" + + +struct _GeditEncoding +{ + 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 +{ + + GEDIT_ENCODING_ISO_8859_1, + GEDIT_ENCODING_ISO_8859_2, + GEDIT_ENCODING_ISO_8859_3, + GEDIT_ENCODING_ISO_8859_4, + GEDIT_ENCODING_ISO_8859_5, + GEDIT_ENCODING_ISO_8859_6, + GEDIT_ENCODING_ISO_8859_7, + GEDIT_ENCODING_ISO_8859_8, + GEDIT_ENCODING_ISO_8859_9, + GEDIT_ENCODING_ISO_8859_10, + GEDIT_ENCODING_ISO_8859_13, + GEDIT_ENCODING_ISO_8859_14, + GEDIT_ENCODING_ISO_8859_15, + GEDIT_ENCODING_ISO_8859_16, + + GEDIT_ENCODING_UTF_7, + GEDIT_ENCODING_UTF_16, + GEDIT_ENCODING_UTF_16_BE, + GEDIT_ENCODING_UTF_16_LE, + GEDIT_ENCODING_UTF_32, + GEDIT_ENCODING_UCS_2, + GEDIT_ENCODING_UCS_4, + + GEDIT_ENCODING_ARMSCII_8, + GEDIT_ENCODING_BIG5, + GEDIT_ENCODING_BIG5_HKSCS, + GEDIT_ENCODING_CP_866, + + GEDIT_ENCODING_EUC_JP, + GEDIT_ENCODING_EUC_JP_MS, + GEDIT_ENCODING_CP932, + GEDIT_ENCODING_EUC_KR, + GEDIT_ENCODING_EUC_TW, + + GEDIT_ENCODING_GB18030, + GEDIT_ENCODING_GB2312, + GEDIT_ENCODING_GBK, + GEDIT_ENCODING_GEOSTD8, + + GEDIT_ENCODING_IBM_850, + GEDIT_ENCODING_IBM_852, + GEDIT_ENCODING_IBM_855, + GEDIT_ENCODING_IBM_857, + GEDIT_ENCODING_IBM_862, + GEDIT_ENCODING_IBM_864, + + GEDIT_ENCODING_ISO_2022_JP, + GEDIT_ENCODING_ISO_2022_KR, + GEDIT_ENCODING_ISO_IR_111, + GEDIT_ENCODING_JOHAB, + GEDIT_ENCODING_KOI8_R, + GEDIT_ENCODING_KOI8__R, + GEDIT_ENCODING_KOI8_U, + + GEDIT_ENCODING_SHIFT_JIS, + GEDIT_ENCODING_TCVN, + GEDIT_ENCODING_TIS_620, + GEDIT_ENCODING_UHC, + GEDIT_ENCODING_VISCII, + + GEDIT_ENCODING_WINDOWS_1250, + GEDIT_ENCODING_WINDOWS_1251, + GEDIT_ENCODING_WINDOWS_1252, + GEDIT_ENCODING_WINDOWS_1253, + GEDIT_ENCODING_WINDOWS_1254, + GEDIT_ENCODING_WINDOWS_1255, + GEDIT_ENCODING_WINDOWS_1256, + GEDIT_ENCODING_WINDOWS_1257, + GEDIT_ENCODING_WINDOWS_1258, + + GEDIT_ENCODING_LAST, + + GEDIT_ENCODING_UTF_8, + GEDIT_ENCODING_UNKNOWN + +} GeditEncodingIndex; + +static const GeditEncoding utf8_encoding = { + GEDIT_ENCODING_UTF_8, + "UTF-8", + N_("Unicode") +}; + +/* initialized in gedit_encoding_lazy_init() */ +static GeditEncoding unknown_encoding = { + GEDIT_ENCODING_UNKNOWN, + NULL, + NULL +}; + +static const GeditEncoding encodings [] = { + + { GEDIT_ENCODING_ISO_8859_1, + "ISO-8859-1", N_("Western") }, + { GEDIT_ENCODING_ISO_8859_2, + "ISO-8859-2", N_("Central European") }, + { GEDIT_ENCODING_ISO_8859_3, + "ISO-8859-3", N_("South European") }, + { GEDIT_ENCODING_ISO_8859_4, + "ISO-8859-4", N_("Baltic") }, + { GEDIT_ENCODING_ISO_8859_5, + "ISO-8859-5", N_("Cyrillic") }, + { GEDIT_ENCODING_ISO_8859_6, + "ISO-8859-6", N_("Arabic") }, + { GEDIT_ENCODING_ISO_8859_7, + "ISO-8859-7", N_("Greek") }, + { GEDIT_ENCODING_ISO_8859_8, + "ISO-8859-8", N_("Hebrew Visual") }, + { GEDIT_ENCODING_ISO_8859_9, + "ISO-8859-9", N_("Turkish") }, + { GEDIT_ENCODING_ISO_8859_10, + "ISO-8859-10", N_("Nordic") }, + { GEDIT_ENCODING_ISO_8859_13, + "ISO-8859-13", N_("Baltic") }, + { GEDIT_ENCODING_ISO_8859_14, + "ISO-8859-14", N_("Celtic") }, + { GEDIT_ENCODING_ISO_8859_15, + "ISO-8859-15", N_("Western") }, + { GEDIT_ENCODING_ISO_8859_16, + "ISO-8859-16", N_("Romanian") }, + + { GEDIT_ENCODING_UTF_7, + "UTF-7", N_("Unicode") }, + { GEDIT_ENCODING_UTF_16, + "UTF-16", N_("Unicode") }, + { GEDIT_ENCODING_UTF_16_BE, + "UTF-16BE", N_("Unicode") }, + { GEDIT_ENCODING_UTF_16_LE, + "UTF-16LE", N_("Unicode") }, + { GEDIT_ENCODING_UTF_32, + "UTF-32", N_("Unicode") }, + { GEDIT_ENCODING_UCS_2, + "UCS-2", N_("Unicode") }, + { GEDIT_ENCODING_UCS_4, + "UCS-4", N_("Unicode") }, + + { GEDIT_ENCODING_ARMSCII_8, + "ARMSCII-8", N_("Armenian") }, + { GEDIT_ENCODING_BIG5, + "BIG5", N_("Chinese Traditional") }, + { GEDIT_ENCODING_BIG5_HKSCS, + "BIG5-HKSCS", N_("Chinese Traditional") }, + { GEDIT_ENCODING_CP_866, + "CP866", N_("Cyrillic/Russian") }, + + { GEDIT_ENCODING_EUC_JP, + "EUC-JP", N_("Japanese") }, + { GEDIT_ENCODING_EUC_JP_MS, + "EUC-JP-MS", N_("Japanese") }, + { GEDIT_ENCODING_CP932, + "CP932", N_("Japanese") }, + + { GEDIT_ENCODING_EUC_KR, + "EUC-KR", N_("Korean") }, + { GEDIT_ENCODING_EUC_TW, + "EUC-TW", N_("Chinese Traditional") }, + + { GEDIT_ENCODING_GB18030, + "GB18030", N_("Chinese Simplified") }, + { GEDIT_ENCODING_GB2312, + "GB2312", N_("Chinese Simplified") }, + { GEDIT_ENCODING_GBK, + "GBK", N_("Chinese Simplified") }, + { GEDIT_ENCODING_GEOSTD8, + "GEORGIAN-ACADEMY", N_("Georgian") }, /* FIXME GEOSTD8 ? */ + + { GEDIT_ENCODING_IBM_850, + "IBM850", N_("Western") }, + { GEDIT_ENCODING_IBM_852, + "IBM852", N_("Central European") }, + { GEDIT_ENCODING_IBM_855, + "IBM855", N_("Cyrillic") }, + { GEDIT_ENCODING_IBM_857, + "IBM857", N_("Turkish") }, + { GEDIT_ENCODING_IBM_862, + "IBM862", N_("Hebrew") }, + { GEDIT_ENCODING_IBM_864, + "IBM864", N_("Arabic") }, + + { GEDIT_ENCODING_ISO_2022_JP, + "ISO-2022-JP", N_("Japanese") }, + { GEDIT_ENCODING_ISO_2022_KR, + "ISO-2022-KR", N_("Korean") }, + { GEDIT_ENCODING_ISO_IR_111, + "ISO-IR-111", N_("Cyrillic") }, + { GEDIT_ENCODING_JOHAB, + "JOHAB", N_("Korean") }, + { GEDIT_ENCODING_KOI8_R, + "KOI8R", N_("Cyrillic") }, + { GEDIT_ENCODING_KOI8__R, + "KOI8-R", N_("Cyrillic") }, + { GEDIT_ENCODING_KOI8_U, + "KOI8U", N_("Cyrillic/Ukrainian") }, + + { GEDIT_ENCODING_SHIFT_JIS, + "SHIFT_JIS", N_("Japanese") }, + { GEDIT_ENCODING_TCVN, + "TCVN", N_("Vietnamese") }, + { GEDIT_ENCODING_TIS_620, + "TIS-620", N_("Thai") }, + { GEDIT_ENCODING_UHC, + "UHC", N_("Korean") }, + { GEDIT_ENCODING_VISCII, + "VISCII", N_("Vietnamese") }, + + { GEDIT_ENCODING_WINDOWS_1250, + "WINDOWS-1250", N_("Central European") }, + { GEDIT_ENCODING_WINDOWS_1251, + "WINDOWS-1251", N_("Cyrillic") }, + { GEDIT_ENCODING_WINDOWS_1252, + "WINDOWS-1252", N_("Western") }, + { GEDIT_ENCODING_WINDOWS_1253, + "WINDOWS-1253", N_("Greek") }, + { GEDIT_ENCODING_WINDOWS_1254, + "WINDOWS-1254", N_("Turkish") }, + { GEDIT_ENCODING_WINDOWS_1255, + "WINDOWS-1255", N_("Hebrew") }, + { GEDIT_ENCODING_WINDOWS_1256, + "WINDOWS-1256", N_("Arabic") }, + { GEDIT_ENCODING_WINDOWS_1257, + "WINDOWS-1257", N_("Baltic") }, + { GEDIT_ENCODING_WINDOWS_1258, + "WINDOWS-1258", N_("Vietnamese") } +}; + +static void +gedit_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 GeditEncoding * +gedit_encoding_get_from_charset (const gchar *charset) +{ + gint i; + + g_return_val_if_fail (charset != NULL, NULL); + + gedit_encoding_lazy_init (); + + if (charset == NULL) + return NULL; + + if (g_ascii_strcasecmp (charset, "UTF-8") == 0) + return gedit_encoding_get_utf8 (); + + i = 0; + while (i < GEDIT_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 GeditEncoding * +gedit_encoding_get_from_index (gint idx) +{ + g_return_val_if_fail (idx >= 0, NULL); + + if (idx >= GEDIT_ENCODING_LAST) + return NULL; + + gedit_encoding_lazy_init (); + + return &encodings[idx]; +} + +const GeditEncoding * +gedit_encoding_get_utf8 (void) +{ + gedit_encoding_lazy_init (); + + return &utf8_encoding; +} + +const GeditEncoding * +gedit_encoding_get_current (void) +{ + static gboolean initialized = FALSE; + static const GeditEncoding *locale_encoding = NULL; + + const gchar *locale_charset; + + gedit_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 = gedit_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 * +gedit_encoding_to_string (const GeditEncoding* enc) +{ + g_return_val_if_fail (enc != NULL, NULL); + + gedit_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 * +gedit_encoding_get_charset (const GeditEncoding* enc) +{ + g_return_val_if_fail (enc != NULL, NULL); + + gedit_encoding_lazy_init (); + + g_return_val_if_fail (enc->charset != NULL, NULL); + + return enc->charset; +} + +const gchar * +gedit_encoding_get_name (const GeditEncoding* enc) +{ + g_return_val_if_fail (enc != NULL, NULL); + + gedit_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 */ + +GeditEncoding * +gedit_encoding_copy (const GeditEncoding *enc) +{ + g_return_val_if_fail (enc != NULL, NULL); + + return (GeditEncoding *) enc; +} + +void +gedit_encoding_free (GeditEncoding *enc) +{ + g_return_if_fail (enc != NULL); +} + +/** + * gedit_encoding_get_type: + * + * Retrieves the GType object which is associated with the + * #GeditEncoding class. + * + * Return value: the GType associated with #GeditEncoding. + **/ +GType +gedit_encoding_get_type (void) +{ + static GType our_type = 0; + + if (!our_type) + our_type = g_boxed_type_register_static ( + "GeditEncoding", + (GBoxedCopyFunc) gedit_encoding_copy, + (GBoxedFreeFunc) gedit_encoding_free); + + return our_type; +} + diff --git a/gedit/gedit-encodings.h b/gedit/gedit-encodings.h new file mode 100755 index 00000000..7e0aa8d2 --- /dev/null +++ b/gedit/gedit-encodings.h @@ -0,0 +1,62 @@ +/* + * gedit-encodings.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_ENCODINGS_H__ +#define __GEDIT_ENCODINGS_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _GeditEncoding GeditEncoding; + +#define GEDIT_TYPE_ENCODING (gedit_encoding_get_type ()) + +GType gedit_encoding_get_type (void) G_GNUC_CONST; + +const GeditEncoding *gedit_encoding_get_from_charset (const gchar *charset); +const GeditEncoding *gedit_encoding_get_from_index (gint index); + +gchar *gedit_encoding_to_string (const GeditEncoding *enc); + +const gchar *gedit_encoding_get_name (const GeditEncoding *enc); +const gchar *gedit_encoding_get_charset (const GeditEncoding *enc); + +const GeditEncoding *gedit_encoding_get_utf8 (void); +const GeditEncoding *gedit_encoding_get_current (void); + +/* These should not be used, they are just to make python bindings happy */ +GeditEncoding *gedit_encoding_copy (const GeditEncoding *enc); +void gedit_encoding_free (GeditEncoding *enc); + +G_END_DECLS + +#endif /* __GEDIT_ENCODINGS_H__ */ diff --git a/gedit/gedit-enum-types.c.template b/gedit/gedit-enum-types.c.template new file mode 100755 index 00000000..7a67ac79 --- /dev/null +++ b/gedit/gedit-enum-types.c.template @@ -0,0 +1,39 @@ +/*** BEGIN file-header ***/ +#include "gedit-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/gedit/gedit-enum-types.h.template b/gedit/gedit-enum-types.h.template new file mode 100755 index 00000000..78f39ce8 --- /dev/null +++ b/gedit/gedit-enum-types.h.template @@ -0,0 +1,27 @@ +/*** BEGIN file-header ***/ +#ifndef __GEDIT_ENUM_TYPES_H__ +#define __GEDIT_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 GEDIT_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 /* __GEDIT_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + diff --git a/gedit/gedit-file-chooser-dialog.c b/gedit/gedit-file-chooser-dialog.c new file mode 100755 index 00000000..94858338 --- /dev/null +++ b/gedit/gedit-file-chooser-dialog.c @@ -0,0 +1,560 @@ +/* + * gedit-file-chooser-dialog.c + * This file is part of gedit + * + * 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 gedit Team, 2005-2007. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-file-chooser-dialog.h" +#include "gedit-encodings-combo-box.h" +#include "gedit-language-manager.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-debug.h" +#include "gedit-enum-types.h" + +#define GEDIT_FILE_CHOOSER_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_FILE_CHOOSER_DIALOG, GeditFileChooserDialogPrivate)) + +#define ALL_FILES _("All Files") +#define ALL_TEXT_FILES _("All Text Files") + +struct _GeditFileChooserDialogPrivate +{ + GtkWidget *option_menu; + GtkWidget *extra_widget; + + GtkWidget *newline_label; + GtkWidget *newline_combo; + GtkListStore *newline_store; +}; + +G_DEFINE_TYPE(GeditFileChooserDialog, gedit_file_chooser_dialog, GTK_TYPE_FILE_CHOOSER_DIALOG) + +static void +gedit_file_chooser_dialog_class_init (GeditFileChooserDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof(GeditFileChooserDialogPrivate)); +} + +static void +create_option_menu (GeditFileChooserDialog *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 = gedit_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 (GeditFileChooserDialog *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, + GeditDocumentNewlineType newline_type) +{ + gtk_list_store_append (store, iter); + gtk_list_store_set (store, iter, 0, label, 1, newline_type, -1); + + if (newline_type == GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT) + { + gtk_combo_box_set_active_iter (combo, iter); + } +} + +static void +create_newline_combo (GeditFileChooserDialog *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, GEDIT_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"), + GEDIT_DOCUMENT_NEWLINE_TYPE_LF); + + newline_combo_append (GTK_COMBO_BOX (combo), + store, + &iter, + _("Mac OS Classic"), + GEDIT_DOCUMENT_NEWLINE_TYPE_CR); + + newline_combo_append (GTK_COMBO_BOX (combo), + store, + &iter, + _("Windows"), + GEDIT_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 (GeditFileChooserDialog *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 (GeditFileChooserDialog *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 (GeditFileChooserDialog *dialog, + GParamSpec *pspec, + gpointer data) +{ + GtkFileFilter *filter; + + if (!gedit_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; + + gedit_debug_message (DEBUG_COMMANDS, "Active filter: %s (%d)", name, id); + + gedit_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 = gedit_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")) + { + gedit_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 gedit 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 +gedit_file_chooser_dialog_init (GeditFileChooserDialog *dialog) +{ + dialog->priv = GEDIT_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog); +} + +static GtkWidget * +gedit_file_chooser_dialog_new_valist (const gchar *title, + GtkWindow *parent, + GtkFileChooserAction action, + const GeditEncoding *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 (GEDIT_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 (GEDIT_FILE_CHOOSER_DIALOG (result)); + + g_signal_connect (result, + "notify::action", + G_CALLBACK (action_changed), + NULL); + + if (encoding != NULL) + gedit_encodings_combo_box_set_selected_encoding ( + GEDIT_ENCODINGS_COMBO_BOX (GEDIT_FILE_CHOOSER_DIALOG (result)->priv->option_menu), + encoding); + + active_filter = gedit_prefs_manager_get_active_file_filter (); + gedit_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; +} + +/** + * gedit_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 #GeditFileChooserDialog. This function is analogous to + * gtk_dialog_new_with_buttons(). + * + * Return value: a new #GeditFileChooserDialog + * + **/ +GtkWidget * +gedit_file_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + GtkFileChooserAction action, + const GeditEncoding *encoding, + const gchar *first_button_text, + ...) +{ + GtkWidget *result; + va_list varargs; + + va_start (varargs, first_button_text); + result = gedit_file_chooser_dialog_new_valist (title, parent, action, + encoding, first_button_text, + varargs); + va_end (varargs); + + return result; +} + +void +gedit_file_chooser_dialog_set_encoding (GeditFileChooserDialog *dialog, + const GeditEncoding *encoding) +{ + g_return_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog)); + g_return_if_fail (GEDIT_IS_ENCODINGS_COMBO_BOX (dialog->priv->option_menu)); + + gedit_encodings_combo_box_set_selected_encoding ( + GEDIT_ENCODINGS_COMBO_BOX (dialog->priv->option_menu), + encoding); +} + +const GeditEncoding * +gedit_file_chooser_dialog_get_encoding (GeditFileChooserDialog *dialog) +{ + g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog), NULL); + g_return_val_if_fail (GEDIT_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 gedit_encodings_combo_box_get_selected_encoding ( + GEDIT_ENCODINGS_COMBO_BOX (dialog->priv->option_menu)); +} + +void +gedit_file_chooser_dialog_set_newline_type (GeditFileChooserDialog *dialog, + GeditDocumentNewlineType newline_type) +{ + GtkTreeIter iter; + GtkTreeModel *model; + + g_return_if_fail (GEDIT_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 + { + GeditDocumentNewlineType 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)); +} + +GeditDocumentNewlineType +gedit_file_chooser_dialog_get_newline_type (GeditFileChooserDialog *dialog) +{ + GtkTreeIter iter; + GeditDocumentNewlineType newline_type; + + g_return_val_if_fail (GEDIT_IS_FILE_CHOOSER_DIALOG (dialog), GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT); + g_return_val_if_fail (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == GTK_FILE_CHOOSER_ACTION_SAVE, + GEDIT_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/gedit/gedit-file-chooser-dialog.h b/gedit/gedit-file-chooser-dialog.h new file mode 100755 index 00000000..090045c5 --- /dev/null +++ b/gedit/gedit-file-chooser-dialog.h @@ -0,0 +1,89 @@ +/* + * gedit-file-chooser-dialog.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_FILE_CHOOSER_DIALOG_H__ +#define __GEDIT_FILE_CHOOSER_DIALOG_H__ + +#include <gtk/gtk.h> + +#include <gedit/gedit-encodings.h> +#include <gedit/gedit-enum-types.h> +#include <gedit/gedit-document.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_CHOOSER_DIALOG (gedit_file_chooser_dialog_get_type ()) +#define GEDIT_FILE_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_CHOOSER_DIALOG, GeditFileChooserDialog)) +#define GEDIT_FILE_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_CHOOSER_DIALOG, GeditFileChooserDialogClass)) +#define GEDIT_IS_FILE_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_CHOOSER_DIALOG)) +#define GEDIT_IS_FILE_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_CHOOSER_DIALOG)) +#define GEDIT_FILE_CHOOSER_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_CHOOSER_DIALOG, GeditFileChooserDialogClass)) + +typedef struct _GeditFileChooserDialog GeditFileChooserDialog; +typedef struct _GeditFileChooserDialogClass GeditFileChooserDialogClass; + +typedef struct _GeditFileChooserDialogPrivate GeditFileChooserDialogPrivate; + +struct _GeditFileChooserDialogClass +{ + GtkFileChooserDialogClass parent_class; +}; + +struct _GeditFileChooserDialog +{ + GtkFileChooserDialog parent_instance; + + GeditFileChooserDialogPrivate *priv; +}; + +GType gedit_file_chooser_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_file_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + GtkFileChooserAction action, + const GeditEncoding *encoding, + const gchar *first_button_text, + ...); + +void gedit_file_chooser_dialog_set_encoding (GeditFileChooserDialog *dialog, + const GeditEncoding *encoding); + +const GeditEncoding + *gedit_file_chooser_dialog_get_encoding (GeditFileChooserDialog *dialog); + +void gedit_file_chooser_dialog_set_newline_type (GeditFileChooserDialog *dialog, + GeditDocumentNewlineType newline_type); + +GeditDocumentNewlineType + gedit_file_chooser_dialog_get_newline_type (GeditFileChooserDialog *dialog); + +G_END_DECLS + +#endif /* __GEDIT_FILE_CHOOSER_DIALOG_H__ */ diff --git a/gedit/gedit-gio-document-loader.c b/gedit/gedit-gio-document-loader.c new file mode 100755 index 00000000..73cfe113 --- /dev/null +++ b/gedit/gedit-gio-document-loader.c @@ -0,0 +1,708 @@ +/* + * gedit-gio-document-loader.c + * This file is part of gedit + * + * 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 gedit Team, 2005-2008. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-gio-document-loader.h" +#include "gedit-document-output-stream.h" +#include "gedit-smart-charset-converter.h" +#include "gedit-prefs-manager.h" +#include "gedit-debug.h" +#include "gedit-utils.h" + +#ifndef ENABLE_GVFS_METADATA +#include "gedit-metadata-manager.h" +#endif + +typedef struct +{ + GeditGioDocumentLoader *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 "," \ + GEDIT_METADATA_ATTRIBUTE_ENCODING + +#define GEDIT_GIO_DOCUMENT_LOADER_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_GIO_DOCUMENT_LOADER, \ + GeditGioDocumentLoaderPrivate)) + +static void gedit_gio_document_loader_load (GeditDocumentLoader *loader); +static gboolean gedit_gio_document_loader_cancel (GeditDocumentLoader *loader); +static goffset gedit_gio_document_loader_get_bytes_read (GeditDocumentLoader *loader); + +static void open_async_read (AsyncData *async); + +struct _GeditGioDocumentLoaderPrivate +{ + /* Info on the current file */ + GFile *gfile; + + goffset bytes_read; + + /* Handle for remote files */ + GCancellable *cancellable; + GInputStream *stream; + GOutputStream *output; + GeditSmartCharsetConverter *converter; + + gchar buffer[READ_CHUNK_SIZE]; + + GError *error; +}; + +G_DEFINE_TYPE(GeditGioDocumentLoader, gedit_gio_document_loader, GEDIT_TYPE_DOCUMENT_LOADER) + +static void +gedit_gio_document_loader_dispose (GObject *object) +{ + GeditGioDocumentLoaderPrivate *priv; + + priv = GEDIT_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 (gedit_gio_document_loader_parent_class)->dispose (object); +} + +static void +gedit_gio_document_loader_class_init (GeditGioDocumentLoaderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditDocumentLoaderClass *loader_class = GEDIT_DOCUMENT_LOADER_CLASS (klass); + + object_class->dispose = gedit_gio_document_loader_dispose; + + loader_class->load = gedit_gio_document_loader_load; + loader_class->cancel = gedit_gio_document_loader_cancel; + loader_class->get_bytes_read = gedit_gio_document_loader_get_bytes_read; + + g_type_class_add_private (object_class, sizeof(GeditGioDocumentLoaderPrivate)); +} + +static void +gedit_gio_document_loader_init (GeditGioDocumentLoader *gvloader) +{ + gvloader->priv = GEDIT_GIO_DOCUMENT_LOADER_GET_PRIVATE (gvloader); + + gvloader->priv->converter = NULL; + gvloader->priv->error = NULL; +} + +static AsyncData * +async_data_new (GeditGioDocumentLoader *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 GeditEncoding * +get_metadata_encoding (GeditDocumentLoader *loader) +{ + const GeditEncoding *enc = NULL; + +#ifndef ENABLE_GVFS_METADATA + gchar *charset; + const gchar *uri; + + uri = gedit_document_loader_get_uri (loader); + + charset = gedit_metadata_manager_get (uri, "encoding"); + + if (charset == NULL) + return NULL; + + enc = gedit_encoding_get_from_charset (charset); + + g_free (charset); +#else + GFileInfo *info; + + info = gedit_document_loader_get_info (loader); + + /* check if the encoding was set in the metadata */ + if (g_file_info_has_attribute (info, GEDIT_METADATA_ATTRIBUTE_ENCODING)) + { + const gchar *charset; + + charset = g_file_info_get_attribute_string (info, + GEDIT_METADATA_ATTRIBUTE_ENCODING); + + if (charset == NULL) + return NULL; + + enc = gedit_encoding_get_from_charset (charset); + } +#endif + + return enc; +} + +static void +remote_load_completed_or_failed (GeditGioDocumentLoader *loader, AsyncData *async) +{ + gedit_document_loader_loading (GEDIT_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; + + gedit_debug (DEBUG_LOADER); + + /* check cancelled state manually */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_data_free (async); + return; + } + + gedit_debug_message (DEBUG_SAVER, "Finished closing input stream"); + + if (!g_input_stream_close_finish (stream, res, &error)) + { + gedit_debug_message (DEBUG_SAVER, "Closing input stream error: %s", error->message); + + async_failed (async, error); + return; + } + + gedit_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) +{ + GeditDocumentLoader *loader; + + loader = GEDIT_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) +{ + GeditGioDocumentLoader *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); + + gedit_debug_message (DEBUG_SAVER, "Written: %" G_GSSIZE_FORMAT, bytes_written); + if (bytes_written == -1) + { + gedit_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 + */ + gedit_document_loader_loading (GEDIT_DOCUMENT_LOADER (gvloader), + FALSE, + NULL); + + read_file_chunk (async); +} + +static void +async_read_cb (GInputStream *stream, + GAsyncResult *res, + AsyncData *async) +{ + gedit_debug (DEBUG_LOADER); + GeditGioDocumentLoader *gvloader; + GError *error = NULL; + + gedit_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, + GEDIT_DOCUMENT_ERROR, + GEDIT_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) + { + GeditDocumentLoader *loader; + + loader = GEDIT_DOCUMENT_LOADER (gvloader); + + loader->auto_detected_encoding = + gedit_smart_charset_converter_get_guessed (gvloader->priv->converter); + + loader->auto_detected_newline_type = + gedit_document_output_stream_detect_newline_type (GEDIT_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 ((gedit_smart_charset_converter_get_num_fallbacks (gvloader->priv->converter) != 0) && + gvloader->priv->error == NULL) + { + g_set_error_literal (&gvloader->priv->error, + GEDIT_DOCUMENT_ERROR, + GEDIT_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) +{ + GeditGioDocumentLoader *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 (GeditGioDocumentLoader *gvloader) +{ + const GeditEncoding *metadata; + GSList *encodings = NULL; + + encodings = gedit_prefs_manager_get_auto_detected_encodings (); + + metadata = get_metadata_encoding (GEDIT_DOCUMENT_LOADER (gvloader)); + if (metadata != NULL) + { + encodings = g_slist_prepend (encodings, (gpointer)metadata); + } + + return encodings; +} + +static void +finish_query_info (AsyncData *async) +{ + GeditGioDocumentLoader *gvloader; + GeditDocumentLoader *loader; + GInputStream *conv_stream; + GFileInfo *info; + GSList *candidate_encodings; + + gvloader = async->loader; + loader = GEDIT_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 = gedit_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 = gedit_document_output_stream_new (loader->document); + + /* start reading */ + read_file_chunk (async); +} + +static void +query_info_cb (GFile *source, + GAsyncResult *res, + AsyncData *async) +{ + GeditGioDocumentLoader *gvloader; + GFileInfo *info; + GError *error = NULL; + + gedit_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; + } + + GEDIT_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; + + gedit_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) +{ + GeditDocument *doc; + GMountOperation *mount_operation; + + gedit_debug (DEBUG_LOADER); + + doc = gedit_document_loader_get_document (GEDIT_DOCUMENT_LOADER (async->loader)); + mount_operation = _gedit_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; + GeditGioDocumentLoader *gvloader; + + gedit_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); + gedit_document_loader_loading (GEDIT_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 +gedit_gio_document_loader_load (GeditDocumentLoader *loader) +{ + GeditGioDocumentLoader *gvloader = GEDIT_GIO_DOCUMENT_LOADER (loader); + AsyncData *async; + + gedit_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 */ + gedit_document_loader_loading (GEDIT_DOCUMENT_LOADER (gvloader), + FALSE, + NULL); + + gvloader->priv->cancellable = g_cancellable_new (); + async = async_data_new (gvloader); + + open_async_read (async); +} + +static goffset +gedit_gio_document_loader_get_bytes_read (GeditDocumentLoader *loader) +{ + return GEDIT_GIO_DOCUMENT_LOADER (loader)->priv->bytes_read; +} + +static gboolean +gedit_gio_document_loader_cancel (GeditDocumentLoader *loader) +{ + GeditGioDocumentLoader *gvloader = GEDIT_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/gedit/gedit-gio-document-loader.h b/gedit/gedit-gio-document-loader.h new file mode 100755 index 00000000..78c9e8ad --- /dev/null +++ b/gedit/gedit-gio-document-loader.h @@ -0,0 +1,79 @@ +/* + * gedit-gio-document-loader.h + * This file is part of gedit + * + * 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 gedit Team, 2005-2008. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_GIO_DOCUMENT_LOADER_H__ +#define __GEDIT_GIO_DOCUMENT_LOADER_H__ + +#include <gedit/gedit-document.h> +#include "gedit-document-loader.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_GIO_DOCUMENT_LOADER (gedit_gio_document_loader_get_type()) +#define GEDIT_GIO_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_GIO_DOCUMENT_LOADER, GeditGioDocumentLoader)) +#define GEDIT_GIO_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_GIO_DOCUMENT_LOADER, GeditGioDocumentLoaderClass)) +#define GEDIT_IS_GIO_DOCUMENT_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_GIO_DOCUMENT_LOADER)) +#define GEDIT_IS_GIO_DOCUMENT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_GIO_DOCUMENT_LOADER)) +#define GEDIT_GIO_DOCUMENT_LOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_GIO_DOCUMENT_LOADER, GeditGioDocumentLoaderClass)) + +/* Private structure type */ +typedef struct _GeditGioDocumentLoaderPrivate GeditGioDocumentLoaderPrivate; + +/* + * Main object structure + */ +typedef struct _GeditGioDocumentLoader GeditGioDocumentLoader; + +struct _GeditGioDocumentLoader +{ + GeditDocumentLoader loader; + + /*< private > */ + GeditGioDocumentLoaderPrivate *priv; +}; + +/* + * Class definition + */ +typedef GeditDocumentLoaderClass GeditGioDocumentLoaderClass; + +/* + * Public methods + */ +GType gedit_gio_document_loader_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GEDIT_GIO_DOCUMENT_LOADER_H__ */ diff --git a/gedit/gedit-gio-document-saver.c b/gedit/gedit-gio-document-saver.c new file mode 100755 index 00000000..f1f63b62 --- /dev/null +++ b/gedit/gedit-gio-document-saver.c @@ -0,0 +1,775 @@ +/* + * gedit-gio-document-saver.c + * This file is part of gedit + * + * 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 gedit Team, 2005-2006. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-gio-document-saver.h" +#include "gedit-document-input-stream.h" +#include "gedit-debug.h" + +#define WRITE_CHUNK_SIZE 8192 + +typedef struct +{ + GeditGioDocumentSaver *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 GEDIT_GIO_DOCUMENT_SAVER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_GIO_DOCUMENT_SAVER, \ + GeditGioDocumentSaverPrivate)) + +static void gedit_gio_document_saver_save (GeditDocumentSaver *saver, + GTimeVal *old_mtime); +static goffset gedit_gio_document_saver_get_file_size (GeditDocumentSaver *saver); +static goffset gedit_gio_document_saver_get_bytes_written (GeditDocumentSaver *saver); + + +static void check_modified_async (AsyncData *async); + +struct _GeditGioDocumentSaverPrivate +{ + GTimeVal old_mtime; + + goffset size; + goffset bytes_written; + + GFile *gfile; + GCancellable *cancellable; + GOutputStream *stream; + GInputStream *input; + + GError *error; +}; + +G_DEFINE_TYPE(GeditGioDocumentSaver, gedit_gio_document_saver, GEDIT_TYPE_DOCUMENT_SAVER) + +static void +gedit_gio_document_saver_dispose (GObject *object) +{ + GeditGioDocumentSaverPrivate *priv = GEDIT_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 (gedit_gio_document_saver_parent_class)->dispose (object); +} + +static AsyncData * +async_data_new (GeditGioDocumentSaver *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 +gedit_gio_document_saver_class_init (GeditGioDocumentSaverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditDocumentSaverClass *saver_class = GEDIT_DOCUMENT_SAVER_CLASS (klass); + + object_class->dispose = gedit_gio_document_saver_dispose; + + saver_class->save = gedit_gio_document_saver_save; + saver_class->get_file_size = gedit_gio_document_saver_get_file_size; + saver_class->get_bytes_written = gedit_gio_document_saver_get_bytes_written; + + g_type_class_add_private (object_class, sizeof(GeditGioDocumentSaverPrivate)); +} + +static void +gedit_gio_document_saver_init (GeditGioDocumentSaver *gvsaver) +{ + gvsaver->priv = GEDIT_GIO_DOCUMENT_SAVER_GET_PRIVATE (gvsaver); + + gvsaver->priv->cancellable = g_cancellable_new (); + gvsaver->priv->error = NULL; +} + +static void +remote_save_completed_or_failed (GeditGioDocumentSaver *gvsaver, + AsyncData *async) +{ + gedit_document_saver_saving (GEDIT_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 (gedit) + * https://bugzilla.mate.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.mate.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; + + gedit_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) +{ + + gedit_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) +{ + GeditGioDocumentSaver *saver; + GFileInfo *info; + GError *error = NULL; + + gedit_debug (DEBUG_SAVER); + + /* check cancelled state manually */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_data_free (async); + return; + } + + saver = async->saver; + + gedit_debug_message (DEBUG_SAVER, "Finished query info on file"); + info = g_file_query_info_finish (source, res, &error); + + if (info != NULL) + { + if (GEDIT_DOCUMENT_SAVER (saver)->info != NULL) + g_object_unref (GEDIT_DOCUMENT_SAVER (saver)->info); + + GEDIT_DOCUMENT_SAVER (saver)->info = info; + } + else + { + gedit_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; + + gedit_debug (DEBUG_SAVER); + + /* check cancelled state manually */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_data_free (async); + return; + } + + gedit_debug_message (DEBUG_SAVER, "Finished closing stream"); + + if (!g_output_stream_close_finish (stream, res, &error)) + { + gedit_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) + */ + gedit_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 */ + gedit_debug_message (DEBUG_SAVER, "Close input stream"); + if (!g_input_stream_close (async->saver->priv->input, + async->cancellable, &error)) + { + gedit_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 */ + gedit_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) +{ + GeditGioDocumentSaver *gvsaver; + gssize bytes_written; + GError *error = NULL; + + gedit_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); + + gedit_debug_message (DEBUG_SAVER, "Written: %" G_GSSIZE_FORMAT, bytes_written); + + if (bytes_written == -1) + { + gedit_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 + */ + gedit_document_saver_saving (GEDIT_DOCUMENT_SAVER (gvsaver), + FALSE, + NULL); + + read_file_chunk (async); +} + +static void +write_file_chunk (AsyncData *async) +{ + GeditGioDocumentSaver *gvsaver; + + gedit_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) +{ + GeditGioDocumentSaver *gvsaver; + GeditDocumentInputStream *dstream; + GError *error = NULL; + + gedit_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 = GEDIT_DOCUMENT_INPUT_STREAM (gvsaver->priv->input); + gvsaver->priv->bytes_written = gedit_document_input_stream_tell (dstream); + + write_file_chunk (async); +} + +static void +async_replace_ready_callback (GFile *source, + GAsyncResult *res, + AsyncData *async) +{ + GeditGioDocumentSaver *gvsaver; + GeditDocumentSaver *saver; + GCharsetConverter *converter; + GFileOutputStream *file_stream; + GError *error = NULL; + + gedit_debug (DEBUG_SAVER); + + /* Check cancelled state manually */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_data_free (async); + return; + } + + gvsaver = async->saver; + saver = GEDIT_DOCUMENT_SAVER (gvsaver); + file_stream = g_file_replace_finish (source, res, &error); + + /* handle any error that might occur */ + if (!file_stream) + { + gedit_debug_message (DEBUG_SAVER, "Opening file failed: %s", error->message); + async_failed (async, error); + return; + } + + /* FIXME: manage converter error? */ + gedit_debug_message (DEBUG_SAVER, "Encoding charset: %s", + gedit_encoding_get_charset (saver->encoding)); + + if (saver->encoding != gedit_encoding_get_utf8 ()) + { + converter = g_charset_converter_new (gedit_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 = gedit_document_input_stream_new (GTK_TEXT_BUFFER (saver->document), + saver->newline_type); + + gvsaver->priv->size = gedit_document_input_stream_get_total_size (GEDIT_DOCUMENT_INPUT_STREAM (gvsaver->priv->input)); + + read_file_chunk (async); +} + +static void +begin_write (AsyncData *async) +{ + GeditGioDocumentSaver *gvsaver; + GeditDocumentSaver *saver; + gboolean backup; + + gedit_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 = GEDIT_DOCUMENT_SAVER (gvsaver); + + /* Do not make backups for remote files so they do not clutter remote systems */ + backup = (saver->keep_backup && gedit_document_is_local (saver->document)); + + gedit_debug_message (DEBUG_SAVER, "File contents size: %" G_GINT64_FORMAT, gvsaver->priv->size); + gedit_debug_message (DEBUG_SAVER, "Calling replace_async"); + gedit_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; + + gedit_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) +{ + GeditDocument *doc; + GMountOperation *mount_operation; + + gedit_debug (DEBUG_LOADER); + + doc = gedit_document_saver_get_document (GEDIT_DOCUMENT_SAVER (async->saver)); + mount_operation = _gedit_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) +{ + GeditGioDocumentSaver *gvsaver; + GError *error = NULL; + GFileInfo *info; + + gedit_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) + { + gedit_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) && + (GEDIT_DOCUMENT_SAVER (gvsaver)->flags & GEDIT_DOCUMENT_SAVE_IGNORE_MTIME) == 0) + { + gedit_debug_message (DEBUG_SAVER, "File is externally modified"); + g_set_error (&gvsaver->priv->error, + GEDIT_DOCUMENT_ERROR, + GEDIT_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) +{ + gedit_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 (GeditGioDocumentSaver *gvsaver) +{ + AsyncData *async; + + gedit_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 +gedit_gio_document_saver_save (GeditDocumentSaver *saver, + GTimeVal *old_mtime) +{ + GeditGioDocumentSaver *gvsaver = GEDIT_GIO_DOCUMENT_SAVER (saver); + + gvsaver->priv->old_mtime = *old_mtime; + gvsaver->priv->gfile = g_file_new_for_uri (saver->uri); + + /* saving start */ + gedit_document_saver_saving (saver, FALSE, NULL); + + g_timeout_add_full (G_PRIORITY_HIGH, + 0, + (GSourceFunc) save_remote_file_real, + gvsaver, + NULL); +} + +static goffset +gedit_gio_document_saver_get_file_size (GeditDocumentSaver *saver) +{ + return GEDIT_GIO_DOCUMENT_SAVER (saver)->priv->size; +} + +static goffset +gedit_gio_document_saver_get_bytes_written (GeditDocumentSaver *saver) +{ + return GEDIT_GIO_DOCUMENT_SAVER (saver)->priv->bytes_written; +} diff --git a/gedit/gedit-gio-document-saver.h b/gedit/gedit-gio-document-saver.h new file mode 100755 index 00000000..18e05df1 --- /dev/null +++ b/gedit/gedit-gio-document-saver.h @@ -0,0 +1,76 @@ +/* + * gedit-gio-document-saver.h + * This file is part of gedit + * + * 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 gedit Team, 2005-2007. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef __GEDIT_GIO_DOCUMENT_SAVER_H__ +#define __GEDIT_GIO_DOCUMENT_SAVER_H__ + +#include <gedit/gedit-document-saver.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_GIO_DOCUMENT_SAVER (gedit_gio_document_saver_get_type()) +#define GEDIT_GIO_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_GIO_DOCUMENT_SAVER, GeditGioDocumentSaver)) +#define GEDIT_GIO_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_GIO_DOCUMENT_SAVER, GeditGioDocumentSaverClass)) +#define GEDIT_IS_GIO_DOCUMENT_SAVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_GIO_DOCUMENT_SAVER)) +#define GEDIT_IS_GIO_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_GIO_DOCUMENT_SAVER)) +#define GEDIT_GIO_DOCUMENT_SAVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_GIO_DOCUMENT_SAVER, GeditGioDocumentSaverClass)) + +/* Private structure type */ +typedef struct _GeditGioDocumentSaverPrivate GeditGioDocumentSaverPrivate; + +/* + * Main object structure + */ +typedef struct _GeditGioDocumentSaver GeditGioDocumentSaver; + +struct _GeditGioDocumentSaver +{ + GeditDocumentSaver saver; + + /*< private > */ + GeditGioDocumentSaverPrivate *priv; +}; + +/* + * Class definition + */ +typedef GeditDocumentSaverClass GeditGioDocumentSaverClass; + +/* + * Public methods + */ +GType gedit_gio_document_saver_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GEDIT_GIO_DOCUMENT_SAVER_H__ */ diff --git a/gedit/gedit-help.c b/gedit/gedit-help.c new file mode 100755 index 00000000..8ddf6a96 --- /dev/null +++ b/gedit/gedit-help.c @@ -0,0 +1,122 @@ +/* + * gedit-help.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-help.h" + +#include <glib/gi18n.h> +#include <string.h> +#include <gtk/gtk.h> + +#ifdef OS_OSX +#include "osx/gedit-osx.h" +#endif + +gboolean +gedit_help_display (GtkWindow *parent, + const gchar *name, /* "gedit" 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, "gedit.xml") == NULL || strcmp(name, "gedit") == 0) + { + return gedit_osx_show_help (link_id); + } + else + { + return FALSE; + } +#endif + + if (name == NULL) + name = "gedit"; + else if (strcmp (name, "gedit.xml") == 0) + { + g_warning ("%s: Using \"gedit.xml\" for the help name is deprecated, use \"gedit\" or simply NULL instead", G_STRFUNC); + + name = "gedit"; + } + +#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.mate.org/users/gedit/stable/%s", + link_id); + else + link = g_strdup ("http://library.mate.org/users/gedit/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/gedit/gedit-help.h b/gedit/gedit-help.h new file mode 100755 index 00000000..ae59afdc --- /dev/null +++ b/gedit/gedit-help.h @@ -0,0 +1,44 @@ +/* + * gedit-help.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_HELP_H__ +#define __GEDIT_HELP_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +gboolean gedit_help_display (GtkWindow *parent, + const gchar *name, /* "gedit" if NULL */ + const gchar *link_id); + +G_END_DECLS + +#endif /* __GEDIT_HELP_H__ */ diff --git a/gedit/gedit-history-entry.c b/gedit/gedit-history-entry.c new file mode 100755 index 00000000..999d4097 --- /dev/null +++ b/gedit/gedit-history-entry.c @@ -0,0 +1,632 @@ +/* + * gedit-history-entry.c + * This file is part of gedit + * + * 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 gedit Team, 2006. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-history-entry.h" + +enum { + PROP_0, + PROP_HISTORY_ID, + PROP_HISTORY_LENGTH +}; + +#define MIN_ITEM_LEN 3 + +#define GEDIT_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT 10 + +#define GEDIT_HISTORY_ENTRY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_HISTORY_ENTRY, \ + GeditHistoryEntryPrivate)) + +struct _GeditHistoryEntryPrivate +{ + gchar *history_id; + guint history_length; + + GtkEntryCompletion *completion; + + MateConfClient *mateconf_client; +}; + +G_DEFINE_TYPE(GeditHistoryEntry, gedit_history_entry, GTK_TYPE_COMBO_BOX_ENTRY) + +static void +gedit_history_entry_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *spec) +{ + GeditHistoryEntry *entry; + + g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (object)); + + entry = GEDIT_HISTORY_ENTRY (object); + + switch (prop_id) { + case PROP_HISTORY_ID: + entry->priv->history_id = g_value_dup_string (value); + break; + case PROP_HISTORY_LENGTH: + gedit_history_entry_set_history_length (entry, + g_value_get_uint (value)); + break; + default: + break; + } +} + +static void +gedit_history_entry_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *spec) +{ + GeditHistoryEntryPrivate *priv; + + g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (object)); + + priv = GEDIT_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 +gedit_history_entry_destroy (GtkObject *object) +{ + gedit_history_entry_set_enable_completion (GEDIT_HISTORY_ENTRY (object), + FALSE); + + GTK_OBJECT_CLASS (gedit_history_entry_parent_class)->destroy (object); +} + +static void +gedit_history_entry_finalize (GObject *object) +{ + GeditHistoryEntryPrivate *priv; + + priv = GEDIT_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 (gedit_history_entry_parent_class)->finalize (object); +} + +static void +gedit_history_entry_class_init (GeditHistoryEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass); + + object_class->set_property = gedit_history_entry_set_property; + object_class->get_property = gedit_history_entry_get_property; + object_class->finalize = gedit_history_entry_finalize; + gtkobject_class->destroy = gedit_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, + GEDIT_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(GeditHistoryEntryPrivate)); +} + +static GtkListStore * +get_history_store (GeditHistoryEntry *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 (GeditHistoryEntry *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 /gedit 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/", + "gedit", + "/history-", + tmp, + NULL); + g_free (tmp); + + return key; +} + +static GSList * +get_history_list (GeditHistoryEntry *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 +gedit_history_entry_save_history (GeditHistoryEntry *entry) +{ + GSList *mateconf_items; + gchar *key; + + g_return_if_fail (GEDIT_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 (GeditHistoryEntry *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); + + gedit_history_entry_save_history (entry); +} + +void +gedit_history_entry_prepend_text (GeditHistoryEntry *entry, + const gchar *text) +{ + g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry)); + g_return_if_fail (text != NULL); + + insert_history_item (entry, text, TRUE); +} + +void +gedit_history_entry_append_text (GeditHistoryEntry *entry, + const gchar *text) +{ + g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry)); + g_return_if_fail (text != NULL); + + insert_history_item (entry, text, FALSE); +} + +static void +gedit_history_entry_load_history (GeditHistoryEntry *entry) +{ + GSList *mateconf_items, *l; + GtkListStore *store; + GtkTreeIter iter; + gchar *key; + guint i; + + g_return_if_fail (GEDIT_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 +gedit_history_entry_clear (GeditHistoryEntry *entry) +{ + GtkListStore *store; + + g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry)); + + store = get_history_store (entry); + gtk_list_store_clear (store); + + gedit_history_entry_save_history (entry); +} + +static void +gedit_history_entry_init (GeditHistoryEntry *entry) +{ + GeditHistoryEntryPrivate *priv; + + priv = GEDIT_HISTORY_ENTRY_GET_PRIVATE (entry); + entry->priv = priv; + + priv->history_id = NULL; + priv->history_length = GEDIT_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT; + + priv->completion = NULL; + + priv->mateconf_client = mateconf_client_get_default (); +} + +void +gedit_history_entry_set_history_length (GeditHistoryEntry *entry, + guint history_length) +{ + g_return_if_fail (GEDIT_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 +gedit_history_entry_get_history_length (GeditHistoryEntry *entry) +{ + g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), 0); + + return entry->priv->history_length; +} + +gchar * +gedit_history_entry_get_history_id (GeditHistoryEntry *entry) +{ + g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), NULL); + + return g_strdup (entry->priv->history_id); +} + +void +gedit_history_entry_set_enable_completion (GeditHistoryEntry *entry, + gboolean enable) +{ + g_return_if_fail (GEDIT_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 (gedit_history_entry_get_entry(entry)), + entry->priv->completion); + } + else + { + if (entry->priv->completion == NULL) + return; + + gtk_entry_set_completion (GTK_ENTRY (gedit_history_entry_get_entry (entry)), + NULL); + + g_object_unref (entry->priv->completion); + + entry->priv->completion = NULL; + } +} + +gboolean +gedit_history_entry_get_enable_completion (GeditHistoryEntry *entry) +{ + g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), FALSE); + + return entry->priv->completion != NULL; +} + +GtkWidget * +gedit_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 gedit_history_entry_ + * functions. + */ + + store = gtk_list_store_new (1, G_TYPE_STRING); + + ret = g_object_new (GEDIT_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. + */ + gedit_history_entry_load_history (GEDIT_HISTORY_ENTRY (ret)); + + gedit_history_entry_set_enable_completion (GEDIT_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 GeditHistoryEntry 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 * +gedit_history_entry_get_entry (GeditHistoryEntry *entry) +{ + g_return_val_if_fail (GEDIT_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, + GeditHistoryEntryEscapeFunc 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 +gedit_history_entry_set_escape_func (GeditHistoryEntry *entry, + GeditHistoryEntryEscapeFunc escape_func) +{ + GList *cells; + + g_return_if_fail (GEDIT_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/gedit/gedit-history-entry.h b/gedit/gedit-history-entry.h new file mode 100755 index 00000000..59e810c2 --- /dev/null +++ b/gedit/gedit-history-entry.h @@ -0,0 +1,96 @@ +/* + * gedit-history-entry.h + * This file is part of gedit + * + * 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 gedit Team, 2006. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_HISTORY_ENTRY_H__ +#define __GEDIT_HISTORY_ENTRY_H__ + + +G_BEGIN_DECLS + +#define GEDIT_TYPE_HISTORY_ENTRY (gedit_history_entry_get_type ()) +#define GEDIT_HISTORY_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_HISTORY_ENTRY, GeditHistoryEntry)) +#define GEDIT_HISTORY_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_HISTORY_ENTRY, GeditHistoryEntryClass)) +#define GEDIT_IS_HISTORY_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_HISTORY_ENTRY)) +#define GEDIT_IS_HISTORY_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_HISTORY_ENTRY)) +#define GEDIT_HISTORY_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_HISTORY_ENTRY, GeditHistoryEntryClass)) + + +typedef struct _GeditHistoryEntry GeditHistoryEntry; +typedef struct _GeditHistoryEntryClass GeditHistoryEntryClass; +typedef struct _GeditHistoryEntryPrivate GeditHistoryEntryPrivate; + +struct _GeditHistoryEntryClass +{ + GtkComboBoxEntryClass parent_class; +}; + +struct _GeditHistoryEntry +{ + GtkComboBoxEntry parent_instance; + + GeditHistoryEntryPrivate *priv; +}; + +GType gedit_history_entry_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_history_entry_new (const gchar *history_id, + gboolean enable_completion); + +void gedit_history_entry_prepend_text (GeditHistoryEntry *entry, + const gchar *text); + +void gedit_history_entry_append_text (GeditHistoryEntry *entry, + const gchar *text); + +void gedit_history_entry_clear (GeditHistoryEntry *entry); + +void gedit_history_entry_set_history_length (GeditHistoryEntry *entry, + guint max_saved); + +guint gedit_history_entry_get_history_length (GeditHistoryEntry *gentry); + +gchar *gedit_history_entry_get_history_id (GeditHistoryEntry *entry); + +void gedit_history_entry_set_enable_completion + (GeditHistoryEntry *entry, + gboolean enable); + +gboolean gedit_history_entry_get_enable_completion + (GeditHistoryEntry *entry); + +GtkWidget *gedit_history_entry_get_entry (GeditHistoryEntry *entry); + +typedef gchar * (* GeditHistoryEntryEscapeFunc) (const gchar *str); +void gedit_history_entry_set_escape_func (GeditHistoryEntry *entry, + GeditHistoryEntryEscapeFunc escape_func); + +G_END_DECLS + +#endif /* __GEDIT_HISTORY_ENTRY_H__ */ diff --git a/gedit/gedit-io-error-message-area.c b/gedit/gedit-io-error-message-area.c new file mode 100755 index 00000000..e116a15b --- /dev/null +++ b/gedit/gedit-io-error-message-area.c @@ -0,0 +1,1288 @@ +/* + * gedit-io-error-message-area.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-utils.h" +#include "gedit-document.h" +#include "gedit-io-error-message-area.h" +#include "gedit-prefs-manager.h" +#include <gedit/gedit-encodings-combo-box.h> + +#if !GTK_CHECK_VERSION (2, 17, 1) +#include "gedit-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) + gedit_message_area_set_contents (GEDIT_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 = gedit_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) + gedit_message_area_add_stock_button_with_text (GEDIT_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 (_("gedit cannot handle %s locations."), + scheme_markup); + g_free (scheme_markup); + } + else + { + *message_details = g_strdup (_("gedit 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 (gedit_utils_decode_uri (uri, NULL, NULL, &hn, NULL, NULL)) + { + if (hn != NULL) + { + gchar *host_markup; + gchar *host_name; + + host_name = gedit_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_gedit_error (gint code, + gchar **error_message, + gchar **message_details, + const gchar *uri, + const gchar *uri_for_display) +{ + gboolean ret = TRUE; + + switch (code) + { + case GEDIT_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 == GEDIT_DOCUMENT_ERROR) + { + ret = parse_gedit_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 * +gedit_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 == GEDIT_DOCUMENT_ERROR) || + (error->domain == G_IO_ERROR), NULL); + + full_formatted_uri = gedit_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 = gedit_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 (_("gedit 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 = gedit_encodings_combo_box_new (TRUE); + g_object_set_data (G_OBJECT (message_area), + "gedit-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 = gedit_message_area_new (); + + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (message_area), + _("_Retry"), + GTK_STOCK_REDO, + GTK_RESPONSE_OK); + + if (edit_anyway) + { + gedit_message_area_add_button (GEDIT_MESSAGE_AREA (message_area), + _("Edit Any_way"), + GTK_RESPONSE_YES); + gedit_message_area_add_button (GEDIT_MESSAGE_AREA (message_area), + _("D_on't Edit"), + GTK_RESPONSE_NO); + } + else + { + gedit_message_area_add_button (GEDIT_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 * +gedit_io_loading_error_message_area_new (const gchar *uri, + const GeditEncoding *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 == GEDIT_DOCUMENT_ERROR) || + (error->domain == G_IO_ERROR), NULL); + + full_formatted_uri = gedit_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 = gedit_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 = gedit_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 == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_DOCUMENT_ERROR_ENCODING_AUTO_DETECTION_FAILED)) + { + message_details = g_strconcat (_("gedit 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 == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_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 * +gedit_conversion_error_while_saving_message_area_new ( + const gchar *uri, + const GeditEncoding *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 = gedit_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 = gedit_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 = gedit_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 GeditEncoding * +gedit_conversion_error_message_area_get_encoding (GtkWidget *message_area) +{ + gpointer menu; + +#if !GTK_CHECK_VERSION (2, 17, 1) + g_return_val_if_fail (GEDIT_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), + "gedit-message-area-encoding-menu"); + g_return_val_if_fail (menu, NULL); + + return gedit_encodings_combo_box_get_selected_encoding + (GEDIT_ENCODINGS_COMBO_BOX (menu)); +} + +GtkWidget * +gedit_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 = gedit_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 = gedit_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 = gedit_message_area_new (); + gedit_message_area_add_button (GEDIT_MESSAGE_AREA (message_area), + _("Edit Any_way"), + GTK_RESPONSE_YES); + gedit_message_area_add_button (GEDIT_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 gedit 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 = _("gedit 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 * +gedit_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 == GEDIT_DOCUMENT_ERROR, NULL); + g_return_val_if_fail (error->code == GEDIT_DOCUMENT_ERROR_EXTERNALLY_MODIFIED, NULL); + + full_formatted_uri = gedit_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 = gedit_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 = gedit_message_area_new (); + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (message_area), + _("S_ave Anyway"), + GTK_STOCK_SAVE, + GTK_RESPONSE_YES); + gedit_message_area_add_button (GEDIT_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 * +gedit_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 == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_DOCUMENT_ERROR_CANT_CREATE_BACKUP) || + (error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_CANT_CREATE_BACKUP)), NULL); + + full_formatted_uri = gedit_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 = gedit_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 = gedit_message_area_new (); + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (message_area), + _("S_ave Anyway"), + GTK_STOCK_SAVE, + GTK_RESPONSE_YES); + gedit_message_area_add_button (GEDIT_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 (gedit_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 = _("gedit 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 * +gedit_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 == GEDIT_DOCUMENT_ERROR) || + (error->domain == G_IO_ERROR), NULL); + + full_formatted_uri = gedit_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 = gedit_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 (_("gedit 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 (_("gedit 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 == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_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 * +gedit_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 = gedit_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 = gedit_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 = gedit_message_area_new (); + + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (message_area), + _("_Reload"), + GTK_STOCK_REFRESH, + GTK_RESPONSE_OK); + + gedit_message_area_add_button (GEDIT_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/gedit/gedit-io-error-message-area.h b/gedit/gedit-io-error-message-area.h new file mode 100755 index 00000000..69a4d90e --- /dev/null +++ b/gedit/gedit-io-error-message-area.h @@ -0,0 +1,68 @@ +/* + * gedit-io-error-message-area.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_IO_ERROR_MESSAGE_AREA_H__ +#define __GEDIT_IO_ERROR_MESSAGE_AREA_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +GtkWidget *gedit_io_loading_error_message_area_new (const gchar *uri, + const GeditEncoding *encoding, + const GError *error); + +GtkWidget *gedit_unrecoverable_reverting_error_message_area_new (const gchar *uri, + const GError *error); + +GtkWidget *gedit_conversion_error_while_saving_message_area_new (const gchar *uri, + const GeditEncoding *encoding, + const GError *error); + +const GeditEncoding + *gedit_conversion_error_message_area_get_encoding (GtkWidget *message_area); + +GtkWidget *gedit_file_already_open_warning_message_area_new (const gchar *uri); + +GtkWidget *gedit_externally_modified_saving_error_message_area_new (const gchar *uri, + const GError *error); + +GtkWidget *gedit_no_backup_saving_error_message_area_new (const gchar *uri, + const GError *error); + +GtkWidget *gedit_unrecoverable_saving_error_message_area_new (const gchar *uri, + const GError *error); + +GtkWidget *gedit_externally_modified_message_area_new (const gchar *uri, + gboolean document_modified); + +G_END_DECLS + +#endif /* __GEDIT_IO_ERROR_MESSAGE_AREA_H__ */ diff --git a/gedit/gedit-language-manager.c b/gedit/gedit-language-manager.c new file mode 100755 index 00000000..a402e4a5 --- /dev/null +++ b/gedit/gedit-language-manager.c @@ -0,0 +1,90 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-languages-manager.c + * This file is part of gedit + * + * 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 gedit Team, 2003-2006. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#include <string.h> + +#include "gedit-language-manager.h" +#include "gedit-prefs-manager.h" +#include "gedit-utils.h" +#include "gedit-debug.h" + +static GtkSourceLanguageManager *language_manager = NULL; + +GtkSourceLanguageManager * +gedit_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 * +gedit_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/gedit/gedit-language-manager.h b/gedit/gedit-language-manager.h new file mode 100755 index 00000000..02438d32 --- /dev/null +++ b/gedit/gedit-language-manager.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-languages-manager.h + * This file is part of gedit + * + * 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 gedit Team, 2003-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_LANGUAGES_MANAGER_H__ +#define __GEDIT_LANGUAGES_MANAGER_H__ + +#include <gtksourceview/gtksourcelanguagemanager.h> + +G_BEGIN_DECLS + +GtkSourceLanguageManager *gedit_get_language_manager (void); + +GSList *gedit_language_manager_list_languages_sorted + (GtkSourceLanguageManager *lm, + gboolean include_hidden); + +G_END_DECLS + +#endif /* __GEDIT_LANGUAGES_MANAGER_H__ */ diff --git a/gedit/gedit-marshal.list b/gedit/gedit-marshal.list new file mode 100755 index 00000000..d2882947 --- /dev/null +++ b/gedit/gedit-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/gedit/gedit-message-area.c b/gedit/gedit-message-area.c new file mode 100755 index 00000000..09240515 --- /dev/null +++ b/gedit/gedit-message-area.c @@ -0,0 +1,626 @@ +/* + * gedit-message-area.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-message-area.h" + +#define GEDIT_MESSAGE_AREA_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_MESSAGE_AREA, \ + GeditMessageAreaPrivate)) + +struct _GeditMessageAreaPrivate +{ + 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(GeditMessageArea, gedit_message_area, GTK_TYPE_HBOX) + + +static void +gedit_message_area_finalize (GObject *object) +{ + /* + GeditMessageArea *message_area = GEDIT_MESSAGE_AREA (object); + */ + + G_OBJECT_CLASS (gedit_message_area_parent_class)->finalize (object); +} + +static ResponseData * +get_response_data (GtkWidget *widget, + gboolean create) +{ + ResponseData *ad = g_object_get_data (G_OBJECT (widget), + "gedit-message-area-response-data"); + + if (ad == NULL && create) + { + ad = g_new (ResponseData, 1); + + g_object_set_data_full (G_OBJECT (widget), + "gedit-message-area-response-data", + ad, + g_free); + } + + return ad; +} + +static GtkWidget * +find_button (GeditMessageArea *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 +gedit_message_area_close (GeditMessageArea *message_area) +{ + if (!find_button (message_area, GTK_RESPONSE_CANCEL)) + return; + + /* emit response signal */ + gedit_message_area_response (GEDIT_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 +gedit_message_area_class_init (GeditMessageAreaClass *klass) +{ + GObjectClass *object_class; + GtkBindingSet *binding_set; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = gedit_message_area_finalize; + + klass->close = gedit_message_area_close; + + g_type_class_add_private (object_class, sizeof(GeditMessageAreaPrivate)); + + signals[RESPONSE] = g_signal_new ("response", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditMessageAreaClass, 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 (GeditMessageAreaClass, 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, + GeditMessageArea *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 +gedit_message_area_init (GeditMessageArea *message_area) +{ + message_area->priv = GEDIT_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 (GeditMessageArea *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, GeditMessageArea *message_area) +{ + gint response_id; + + response_id = get_response_for_widget (message_area, widget); + + gedit_message_area_response (message_area, response_id); +} + +void +gedit_message_area_add_action_widget (GeditMessageArea *message_area, + GtkWidget *child, + gint response_id) +{ + ResponseData *ad; + guint signal_id; + + g_return_if_fail (GEDIT_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 GeditMessageArea"); + + 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); +} + +/** + * gedit_message_area_set_contents: + * @message_area: a #GeditMessageArea + * @contents: widget you want to add to the contents area + * + * Adds the @contents widget to the contents area of #GeditMessageArea. + */ +void +gedit_message_area_set_contents (GeditMessageArea *message_area, + GtkWidget *contents) +{ + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_message_area_add_button: + * @message_area: a #GeditMessageArea + * @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* +gedit_message_area_add_button (GeditMessageArea *message_area, + const gchar *button_text, + gint response_id) +{ + GtkWidget *button; + + g_return_val_if_fail (GEDIT_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); + + gedit_message_area_add_action_widget (message_area, + button, + response_id); + + return button; +} + +static void +add_buttons_valist (GeditMessageArea *message_area, + const gchar *first_button_text, + va_list args) +{ + const gchar* text; + gint response_id; + + g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area)); + + if (first_button_text == NULL) + return; + + text = first_button_text; + response_id = va_arg (args, gint); + + while (text != NULL) + { + gedit_message_area_add_button (message_area, + text, + response_id); + + text = va_arg (args, gchar*); + if (text == NULL) + break; + + response_id = va_arg (args, int); + } +} + +/** + * gedit_message_area_add_buttons: + * @message_area: a #GeditMessageArea + * @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 gedit_message_area_add_button() repeatedly. + * The variable argument list should be NULL-terminated as with + * gedit_message_area_new_with_buttons(). Each button must have both text and response ID. + */ +void +gedit_message_area_add_buttons (GeditMessageArea *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); +} + +/** + * gedit_message_area_new: + * + * Creates a new #GeditMessageArea object. + * + * Returns: a new #GeditMessageArea object + */ +GtkWidget * +gedit_message_area_new (void) +{ + return g_object_new (GEDIT_TYPE_MESSAGE_AREA, NULL); +} + +/** + * gedit_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 #GeditMessageArea 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, GeditMessageArea will emit the "response" + * signal with the corresponding response ID. + * + * Returns: a new #GeditMessageArea + */ +GtkWidget * +gedit_message_area_new_with_buttons (const gchar *first_button_text, + ...) +{ + GeditMessageArea *message_area; + va_list args; + + message_area = GEDIT_MESSAGE_AREA (gedit_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); +} + +/** + * gedit_message_area_set_response_sensitive: + * @message_area: a #GeditMessageArea + * @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 +gedit_message_area_set_response_sensitive (GeditMessageArea *message_area, + gint response_id, + gboolean setting) +{ + GList *children; + GList *tmp_list; + + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_message_area_set_default_response: + * @message_area: a #GeditMessageArea + * @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 +gedit_message_area_set_default_response (GeditMessageArea *message_area, + gint response_id) +{ + GList *children; + GList *tmp_list; + + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_message_area_set_default_response: + * @message_area: a #GeditMessageArea + * @response_id: a response ID + * + * Emits the 'response' signal with the given @response_id. + */ +void +gedit_message_area_response (GeditMessageArea *message_area, + gint response_id) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area)); + + g_signal_emit (message_area, + signals[RESPONSE], + 0, + response_id); +} + +/** + * gedit_message_area_add_stock_button_with_text: + * @message_area: a #GeditMessageArea + * @text: the text to visualize in the button + * @stock_id: the stock ID of the button + * @response_id: a response ID + * + * Same as gedit_message_area_add_button() but with a specific text. + */ +GtkWidget * +gedit_message_area_add_stock_button_with_text (GeditMessageArea *message_area, + const gchar *text, + const gchar *stock_id, + gint response_id) +{ + GtkWidget *button; + + g_return_val_if_fail (GEDIT_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); + + gedit_message_area_add_action_widget (message_area, + button, + response_id); + + return button; +} + diff --git a/gedit/gedit-message-area.h b/gedit/gedit-message-area.h new file mode 100755 index 00000000..01dfff0c --- /dev/null +++ b/gedit/gedit-message-area.h @@ -0,0 +1,129 @@ +/* + * gedit-message-area.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_MESSAGE_AREA_H__ +#define __GEDIT_MESSAGE_AREA_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_MESSAGE_AREA (gedit_message_area_get_type()) +#define GEDIT_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_MESSAGE_AREA, GeditMessageArea)) +#define GEDIT_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_MESSAGE_AREA, GeditMessageAreaClass)) +#define GEDIT_IS_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_MESSAGE_AREA)) +#define GEDIT_IS_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_MESSAGE_AREA)) +#define GEDIT_MESSAGE_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_MESSAGE_AREA, GeditMessageAreaClass)) + +/* Private structure type */ +typedef struct _GeditMessageAreaPrivate GeditMessageAreaPrivate; + +/* + * Main object structure + */ +typedef struct _GeditMessageArea GeditMessageArea; + +struct _GeditMessageArea +{ + GtkHBox parent; + + /*< private > */ + GeditMessageAreaPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditMessageAreaClass GeditMessageAreaClass; + +struct _GeditMessageAreaClass +{ + GtkHBoxClass parent_class; + + /* Signals */ + void (* response) (GeditMessageArea *message_area, gint response_id); + + /* Keybinding signals */ + void (* close) (GeditMessageArea *message_area); + + /* Padding for future expansion */ + void (*_gedit_reserved1) (void); + void (*_gedit_reserved2) (void); +}; + +/* + * Public methods + */ +GType gedit_message_area_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_message_area_new (void); + +GtkWidget *gedit_message_area_new_with_buttons (const gchar *first_button_text, + ...); + +void gedit_message_area_set_contents (GeditMessageArea *message_area, + GtkWidget *contents); + +void gedit_message_area_add_action_widget (GeditMessageArea *message_area, + GtkWidget *child, + gint response_id); + +GtkWidget *gedit_message_area_add_button (GeditMessageArea *message_area, + const gchar *button_text, + gint response_id); + +GtkWidget *gedit_message_area_add_stock_button_with_text + (GeditMessageArea *message_area, + const gchar *text, + const gchar *stock_id, + gint response_id); + +void gedit_message_area_add_buttons (GeditMessageArea *message_area, + const gchar *first_button_text, + ...); + +void gedit_message_area_set_response_sensitive + (GeditMessageArea *message_area, + gint response_id, + gboolean setting); +void gedit_message_area_set_default_response + (GeditMessageArea *message_area, + gint response_id); + +/* Emit response signal */ +void gedit_message_area_response (GeditMessageArea *message_area, + gint response_id); + +G_END_DECLS + +#endif /* __GEDIT_MESSAGE_AREA_H__ */ diff --git a/gedit/gedit-message-bus.c b/gedit/gedit-message-bus.c new file mode 100755 index 00000000..bb6017a3 --- /dev/null +++ b/gedit/gedit-message-bus.c @@ -0,0 +1,1158 @@ +#include "gedit-message-bus.h" + +#include <string.h> +#include <stdarg.h> +#include <gobject/gvaluecollector.h> + +/** + * GeditMessageCallback: + * @bus: the #GeditMessageBus on which the message was sent + * @message: the #GeditMessage 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 gedit_message_bus_connect()). + * + */ + +/** + * SECTION:gedit-message-bus + * @short_description: internal message communication bus + * @include: gedit/gedit-message-bus.h + * + * gedit 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 + * gedit functionality to external applications by providing DBus bindings for + * the internal gedit message bus. + * + * There are two different communication busses available. The default bus + * (see gedit_message_bus_get_default()) is an application wide communication + * bus. In addition, each #GeditWindow has a separate, private bus + * (see gedit_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> + * GeditMessageBus *bus = gedit_message_bus_get_default (); + * + * // Register 'method' at '/plugins/example' with one required + * // string argument 'arg1' + * GeditMessageType *message_type = gedit_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 (GeditMessageBus *bus, + * GeditMessage *message, + * gpointer userdata) + * { + * gchar *arg1 = NULL; + * + * gedit_message_get (message, "arg1", &arg1, NULL); + * g_message ("Evoked /plugins/example.method with: %s", arg1); + * g_free (arg1); + * } + * + * GeditMessageBus *bus = gedit_message_bus_get_default (); + * + * guint id = gedit_message_bus_connect (bus, + * "/plugins/example", "method", + * example_method_cb, + * NULL, + * NULL); + * + * </programlisting> + * </example> + * <example> + * <title>Sending a message</title> + * <programlisting> + * GeditMessageBus *bus = gedit_message_bus_get_default (); + * + * gedit_message_bus_send (bus, + * "/plugins/example", "method", + * "arg1", "Hello World", + * NULL); + * </programlisting> + * </example> + * + * Since: 2.25.3 + * + */ + +#define GEDIT_MESSAGE_BUS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBusPrivate)) + +typedef struct +{ + gchar *object_path; + gchar *method; + + GList *listeners; +} Message; + +typedef struct +{ + guint id; + gboolean blocked; + + GDestroyNotify destroy_data; + GeditMessageCallback callback; + gpointer userdata; +} Listener; + +typedef struct +{ + Message *message; + GList *listener; +} IdMap; + +struct _GeditMessageBusPrivate +{ + GHashTable *messages; + GHashTable *idmap; + + GList *message_queue; + guint idle_id; + + guint next_id; + + GHashTable *types; /* mapping from identifier to GeditMessageType */ +}; + +/* signals */ +enum +{ + DISPATCH, + REGISTERED, + UNREGISTERED, + LAST_SIGNAL +}; + +static guint message_bus_signals[LAST_SIGNAL]; + +static void gedit_message_bus_dispatch_real (GeditMessageBus *bus, + GeditMessage *message); + +G_DEFINE_TYPE(GeditMessageBus, gedit_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 +gedit_message_bus_finalize (GObject *object) +{ + GeditMessageBus *bus = GEDIT_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 (gedit_message_bus_parent_class)->finalize (object); +} + +static void +gedit_message_bus_class_init (GeditMessageBusClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_message_bus_finalize; + + klass->dispatch = gedit_message_bus_dispatch_real; + + /** + * GeditMessageBus::dispatch: + * @bus: a #GeditMessageBus + * @message: the #GeditMessage 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 (GeditMessageBusClass, dispatch), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_TYPE_MESSAGE); + + /** + * GeditMessageBus::registered: + * @bus: a #GeditMessageBus + * @message_type: the registered #GeditMessageType + * + * 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 (GeditMessageBusClass, registered), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + GEDIT_TYPE_MESSAGE_TYPE); + + /** + * GeditMessageBus::unregistered: + * @bus: a #GeditMessageBus + * @message_type: the unregistered #GeditMessageType + * + * 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 (GeditMessageBusClass, unregistered), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + GEDIT_TYPE_MESSAGE_TYPE); + + g_type_class_add_private (object_class, sizeof(GeditMessageBusPrivate)); +} + +static Message * +message_new (GeditMessageBus *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, + gedit_message_type_identifier (object_path, method), + message); + return message; +} + +static Message * +lookup_message (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + gboolean create) +{ + gchar *identifier; + Message *message; + + identifier = gedit_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 (GeditMessageBus *bus, + Message *message, + GeditMessageCallback 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 (GeditMessageBus *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 (GeditMessageBus *bus, + Message *message, + GList *listener) +{ + Listener *lst; + + lst = (Listener *)listener->data; + lst->blocked = TRUE; +} + +static void +unblock_listener (GeditMessageBus *bus, + Message *message, + GList *listener) +{ + Listener *lst; + + lst = (Listener *)listener->data; + lst->blocked = FALSE; +} + +static void +dispatch_message_real (GeditMessageBus *bus, + Message *msg, + GeditMessage *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 +gedit_message_bus_dispatch_real (GeditMessageBus *bus, + GeditMessage *message) +{ + const gchar *object_path; + const gchar *method; + Message *msg; + + object_path = gedit_message_get_object_path (message); + method = gedit_message_get_method (message); + + msg = lookup_message (bus, object_path, method, FALSE); + + if (msg) + dispatch_message_real (bus, msg, message); +} + +static void +dispatch_message (GeditMessageBus *bus, + GeditMessage *message) +{ + g_signal_emit (bus, message_bus_signals[DISPATCH], 0, message); +} + +static gboolean +idle_dispatch (GeditMessageBus *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) + { + GeditMessage *msg = GEDIT_MESSAGE (item->data); + + dispatch_message (bus, msg); + } + + message_queue_free (list); + return FALSE; +} + +typedef void (*MatchCallback) (GeditMessageBus *, Message *, GList *); + +static void +process_by_id (GeditMessageBus *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 (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback 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 +gedit_message_bus_init (GeditMessageBus *self) +{ + self->priv = GEDIT_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)gedit_message_type_unref); +} + +/** + * gedit_message_bus_get_default: + * + * Get the default application #GeditMessageBus. + * + * Return value: the default #GeditMessageBus + * + */ +GeditMessageBus * +gedit_message_bus_get_default (void) +{ + static GeditMessageBus *default_bus = NULL; + + if (G_UNLIKELY (default_bus == NULL)) + { + default_bus = g_object_new (GEDIT_TYPE_MESSAGE_BUS, NULL); + g_object_add_weak_pointer (G_OBJECT (default_bus), + (gpointer) &default_bus); + } + + return default_bus; +} + +/** + * gedit_message_bus_new: + * + * Create a new message bus. Use gedit_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 #GeditWindow. + * + * Return value: a new #GeditMessageBus + * + */ +GeditMessageBus * +gedit_message_bus_new (void) +{ + return GEDIT_MESSAGE_BUS (g_object_new (GEDIT_TYPE_MESSAGE_BUS, NULL)); +} + +/** + * gedit_message_bus_lookup: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * + * Get the registered #GeditMessageType for @method at @object_path. The + * returned #GeditMessageType is owned by the bus and should not be unreffed. + * + * Return value: the registered #GeditMessageType or %NULL if no message type + * is registered for @method at @object_path + * + */ +GeditMessageType * +gedit_message_bus_lookup (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method) +{ + gchar *identifier; + GeditMessageType *message_type; + + g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), NULL); + g_return_val_if_fail (object_path != NULL, NULL); + g_return_val_if_fail (method != NULL, NULL); + + identifier = gedit_message_type_identifier (object_path, method); + message_type = GEDIT_MESSAGE_TYPE (g_hash_table_lookup (bus->priv->types, identifier)); + + g_free (identifier); + return message_type; +} + +/** + * gedit_message_bus_register: + * @bus: a #GeditMessageBus + * @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 #GeditMessageBus::registered signal. + * + * Return value: the registered #GeditMessageType. The returned reference is + * owned by the bus. If you want to keep it alive after + * unregistering, use gedit_message_type_ref(). + * + */ +GeditMessageType * +gedit_message_bus_register (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + guint num_optional, + ...) +{ + gchar *identifier; + gpointer data; + va_list var_args; + GeditMessageType *message_type; + + g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), NULL); + g_return_val_if_fail (gedit_message_type_is_valid_object_path (object_path), NULL); + + if (gedit_message_bus_is_registered (bus, object_path, method)) + { + g_warning ("Message type for '%s.%s' is already registered", object_path, method); + return NULL; + } + + identifier = gedit_message_type_identifier (object_path, method); + data = g_hash_table_lookup (bus->priv->types, identifier); + + va_start (var_args, num_optional); + message_type = gedit_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 +gedit_message_bus_unregister_real (GeditMessageBus *bus, + GeditMessageType *message_type, + gboolean remove_from_store) +{ + gchar *identifier; + + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + identifier = gedit_message_type_identifier (gedit_message_type_get_object_path (message_type), + gedit_message_type_get_method (message_type)); + + /* Keep message type alive for signal emission */ + gedit_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); + + gedit_message_type_unref (message_type); + g_free (identifier); +} + +/** + * gedit_message_bus_unregister: + * @bus: a #GeditMessageBus + * @message_type: the #GeditMessageType 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 #GeditMessageBus::unregistered signal. + * + */ +void +gedit_message_bus_unregister (GeditMessageBus *bus, + GeditMessageType *message_type) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + gedit_message_bus_unregister_real (bus, message_type, TRUE); +} + +typedef struct +{ + GeditMessageBus *bus; + const gchar *object_path; +} UnregisterInfo; + +static gboolean +unregister_each (const gchar *identifier, + GeditMessageType *message_type, + UnregisterInfo *info) +{ + if (strcmp (gedit_message_type_get_object_path (message_type), + info->object_path) == 0) + { + gedit_message_bus_unregister_real (info->bus, message_type, FALSE); + return TRUE; + } + + return FALSE; +} + +/** + * gedit_message_bus_unregister_all: + * @bus: a #GeditMessageBus + * @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 #GeditMessageBus::unregistered signal for all + * unregistered message types. + * + */ +void +gedit_message_bus_unregister_all (GeditMessageBus *bus, + const gchar *object_path) +{ + UnregisterInfo info = {bus, object_path}; + + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (object_path != NULL); + + g_hash_table_foreach_remove (bus->priv->types, + (GHRFunc)unregister_each, + &info); +} + +/** + * gedit_message_bus_is_registered: + * @bus: a #GeditMessageBus + * @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 +gedit_message_bus_is_registered (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method) +{ + gchar *identifier; + gboolean ret; + + g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), FALSE); + g_return_val_if_fail (object_path != NULL, FALSE); + g_return_val_if_fail (method != NULL, FALSE); + + identifier = gedit_message_type_identifier (object_path, method); + ret = g_hash_table_lookup (bus->priv->types, identifier) != NULL; + + g_free(identifier); + return ret; +} + +typedef struct +{ + GeditMessageBusForeach func; + gpointer userdata; +} ForeachInfo; + +static void +foreach_type (const gchar *key, + GeditMessageType *message_type, + ForeachInfo *info) +{ + gedit_message_type_ref (message_type); + info->func (message_type, info->userdata); + gedit_message_type_unref (message_type); +} + +/** + * gedit_message_bus_foreach: + * @bus: the #GeditMessagebus + * @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 +gedit_message_bus_foreach (GeditMessageBus *bus, + GeditMessageBusForeach func, + gpointer userdata) +{ + ForeachInfo info = {func, userdata}; + + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (func != NULL); + + g_hash_table_foreach (bus->priv->types, (GHFunc)foreach_type, &info); +} + +/** + * gedit_message_bus_connect: + * @bus: a #GeditMessageBus + * @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 +gedit_message_bus_connect (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata, + GDestroyNotify destroy_data) +{ + Message *message; + + g_return_val_if_fail (GEDIT_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); +} + +/** + * gedit_message_bus_disconnect: + * @bus: a #GeditMessageBus + * @id: the callback id as returned by gedit_message_bus_connect() + * + * Disconnects a previously connected message callback. + * + */ +void +gedit_message_bus_disconnect (GeditMessageBus *bus, + guint id) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_id (bus, id, remove_listener); +} + +/** + * gedit_message_bus_disconnect_by_func: + * @bus: a #GeditMessageBus + * @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 + * gedit_message_bus_disconnect(). + * + */ +void +gedit_message_bus_disconnect_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_match (bus, object_path, method, callback, userdata, remove_listener); +} + +/** + * gedit_message_bus_block: + * @bus: a #GeditMessageBus + * @id: the callback id + * + * Blocks evoking the callback specified by @id. Unblock the callback by + * using gedit_message_bus_unblock(). + * + */ +void +gedit_message_bus_block (GeditMessageBus *bus, + guint id) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_id (bus, id, block_listener); +} + +/** + * gedit_message_bus_block_by_func: + * @bus: a #GeditMessageBus + * @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 gedit_message_unblock_by_func(). + * + */ +void +gedit_message_bus_block_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_match (bus, object_path, method, callback, userdata, block_listener); +} + +/** + * gedit_message_bus_unblock: + * @bus: a #GeditMessageBus + * @id: the callback id + * + * Unblocks the callback specified by @id. + * + */ +void +gedit_message_bus_unblock (GeditMessageBus *bus, + guint id) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_id (bus, id, unblock_listener); +} + +/** + * gedit_message_bus_unblock_by_func: + * @bus: a #GeditMessageBus + * @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 +gedit_message_bus_unblock_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_match (bus, object_path, method, callback, userdata, unblock_listener); +} + +static gboolean +validate_message (GeditMessage *message) +{ + if (!gedit_message_validate (message)) + { + g_warning ("Message '%s.%s' is invalid", gedit_message_get_object_path (message), + gedit_message_get_method (message)); + return FALSE; + } + + return TRUE; +} + +static void +send_message_real (GeditMessageBus *bus, + GeditMessage *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); +} + +/** + * gedit_message_bus_send_message: + * @bus: a #GeditMessageBus + * @message: the message to send + * + * This sends the provided @message asynchronously over the bus. To send + * a message synchronously, use gedit_message_bus_send_message_sync(). The + * convenience function gedit_message_bus_send() can be used to easily send + * a message without constructing the message object explicitly first. + * + */ +void +gedit_message_bus_send_message (GeditMessageBus *bus, + GeditMessage *message) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (GEDIT_IS_MESSAGE (message)); + + send_message_real (bus, message); +} + +static void +send_message_sync_real (GeditMessageBus *bus, + GeditMessage *message) +{ + if (!validate_message (message)) + { + return; + } + + dispatch_message (bus, message); +} + +/** + * gedit_message_bus_send_message_sync: + * @bus: a #GeditMessageBus + * @message: the message to send + * + * This sends the provided @message synchronously over the bus. To send + * a message asynchronously, use gedit_message_bus_send_message(). The + * convenience function gedit_message_bus_send_sync() can be used to easily send + * a message without constructing the message object explicitly first. + * + */ +void +gedit_message_bus_send_message_sync (GeditMessageBus *bus, + GeditMessage *message) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (GEDIT_IS_MESSAGE (message)); + + send_message_sync_real (bus, message); +} + +static GeditMessage * +create_message (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + va_list var_args) +{ + GeditMessageType *message_type; + + message_type = gedit_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 gedit_message_type_instantiate_valist (message_type, + var_args); +} + +/** + * gedit_message_bus_send: + * @bus: a #GeditMessageBus + * @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 gedit_message_bus_send_sync(). + * + */ +void +gedit_message_bus_send (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + ...) +{ + va_list var_args; + GeditMessage *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); +} + +/** + * gedit_message_bus_send_sync: + * @bus: a #GeditMessageBus + * @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 gedit_message_bus_send(). + * + * Return value: the constructed #GeditMessage. The caller owns a reference + * to the #GeditMessage and should call g_object_unref() when + * it is no longer needed + */ +GeditMessage * +gedit_message_bus_send_sync (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + ...) +{ + va_list var_args; + GeditMessage *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/gedit/gedit-message-bus.h b/gedit/gedit-message-bus.h new file mode 100755 index 00000000..b60eabd0 --- /dev/null +++ b/gedit/gedit-message-bus.h @@ -0,0 +1,129 @@ +#ifndef __GEDIT_MESSAGE_BUS_H__ +#define __GEDIT_MESSAGE_BUS_H__ + +#include <glib-object.h> +#include <gedit/gedit-message.h> +#include <gedit/gedit-message-type.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_MESSAGE_BUS (gedit_message_bus_get_type ()) +#define GEDIT_MESSAGE_BUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBus)) +#define GEDIT_MESSAGE_BUS_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBus const)) +#define GEDIT_MESSAGE_BUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBusClass)) +#define GEDIT_IS_MESSAGE_BUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_MESSAGE_BUS)) +#define GEDIT_IS_MESSAGE_BUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_MESSAGE_BUS)) +#define GEDIT_MESSAGE_BUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_MESSAGE_BUS, GeditMessageBusClass)) + +typedef struct _GeditMessageBus GeditMessageBus; +typedef struct _GeditMessageBusClass GeditMessageBusClass; +typedef struct _GeditMessageBusPrivate GeditMessageBusPrivate; + +struct _GeditMessageBus { + GObject parent; + + GeditMessageBusPrivate *priv; +}; + +struct _GeditMessageBusClass { + GObjectClass parent_class; + + void (*dispatch) (GeditMessageBus *bus, + GeditMessage *message); + void (*registered) (GeditMessageBus *bus, + GeditMessageType *message_type); + void (*unregistered) (GeditMessageBus *bus, + GeditMessageType *message_type); +}; + +typedef void (* GeditMessageCallback) (GeditMessageBus *bus, + GeditMessage *message, + gpointer userdata); + +typedef void (* GeditMessageBusForeach) (GeditMessageType *message_type, + gpointer userdata); + +GType gedit_message_bus_get_type (void) G_GNUC_CONST; + +GeditMessageBus *gedit_message_bus_get_default (void); +GeditMessageBus *gedit_message_bus_new (void); + +/* registering messages */ +GeditMessageType *gedit_message_bus_lookup (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method); +GeditMessageType *gedit_message_bus_register (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + guint num_optional, + ...) G_GNUC_NULL_TERMINATED; + +void gedit_message_bus_unregister (GeditMessageBus *bus, + GeditMessageType *message_type); + +void gedit_message_bus_unregister_all (GeditMessageBus *bus, + const gchar *object_path); + +gboolean gedit_message_bus_is_registered (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method); + +void gedit_message_bus_foreach (GeditMessageBus *bus, + GeditMessageBusForeach func, + gpointer userdata); + + +/* connecting to message events */ +guint gedit_message_bus_connect (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata, + GDestroyNotify destroy_data); + +void gedit_message_bus_disconnect (GeditMessageBus *bus, + guint id); + +void gedit_message_bus_disconnect_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata); + +/* blocking message event callbacks */ +void gedit_message_bus_block (GeditMessageBus *bus, + guint id); +void gedit_message_bus_block_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata); + +void gedit_message_bus_unblock (GeditMessageBus *bus, + guint id); +void gedit_message_bus_unblock_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer userdata); + +/* sending messages */ +void gedit_message_bus_send_message (GeditMessageBus *bus, + GeditMessage *message); +void gedit_message_bus_send_message_sync (GeditMessageBus *bus, + GeditMessage *message); + +void gedit_message_bus_send (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + ...) G_GNUC_NULL_TERMINATED; +GeditMessage *gedit_message_bus_send_sync (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + ...) G_GNUC_NULL_TERMINATED; + +G_END_DECLS + +#endif /* __GEDIT_MESSAGE_BUS_H__ */ + +// ex:ts=8:noet: diff --git a/gedit/gedit-message-type.c b/gedit/gedit-message-type.c new file mode 100755 index 00000000..61e782b3 --- /dev/null +++ b/gedit/gedit-message-type.c @@ -0,0 +1,526 @@ +#include "gedit-message-type.h" + +/** + * SECTION:gedit-message-type + * @short_description: message type description + * @include: gedit/gedit-message-type.h + * + * A message type is a prototype description for a #GeditMessage used to + * transmit messages on a #GeditMessageBus. 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 #GeditMessage from a #GeditMessageType, use + * gedit_message_type_instantiate(). + * + * Registering a new message type on a #GeditMessageBus with + * gedit_message_bus_register() internally creates a new #GeditMessageType. When + * then using gedit_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 + * GeditMessageType *message_type = gedit_message_type_new ("/plugins/example", + * "method", + * 0, + * "arg1", G_TYPE_STRING, + * NULL); + * + * // Instantiating an actual message from the type + * GeditMessage *message = gedit_message_type_instantiate (message_type, + * "arg1", "Hello World", + * NULL); + * </programlisting> + * </example> + * + * Since: 2.25.3 + * + */ +typedef struct +{ + GType type; + gboolean required; +} ArgumentInfo; + +struct _GeditMessageType +{ + gint ref_count; + + gchar *object_path; + gchar *method; + + guint num_arguments; + guint num_required; + + GHashTable *arguments; // mapping of key -> ArgumentInfo +}; + +/** + * gedit_message_type_ref: + * @message_type: the #GeditMessageType + * + * Increases the reference count on @message_type. + * + * Return value: @message_type + * + */ +GeditMessageType * +gedit_message_type_ref (GeditMessageType *message_type) +{ + g_return_val_if_fail (message_type != NULL, NULL); + g_atomic_int_inc (&message_type->ref_count); + + return message_type; +} + +/** + * gedit_message_type_unref: + * @message_type: the #GeditMessageType + * + * Decreases the reference count on @message_type. When the reference count + * drops to 0, @message_type is destroyed. + * + */ +void +gedit_message_type_unref (GeditMessageType *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); +} + +/** + * gedit_message_type_get_type: + * + * Retrieves the GType object which is associated with the + * #GeditMessageType class. + * + * Return value: the GType associated with #GeditMessageType. + **/ +GType +gedit_message_type_get_type (void) +{ + static GType our_type = 0; + + if (!our_type) + our_type = g_boxed_type_register_static ( + "GeditMessageType", + (GBoxedCopyFunc) gedit_message_type_ref, + (GBoxedFreeFunc) gedit_message_type_unref); + + return our_type; +} + +/** + * gedit_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 * +gedit_message_type_identifier (const gchar *object_path, + const gchar *method) +{ + return g_strconcat (object_path, ".", method, NULL); +} + +/** + * gedit_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 +gedit_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; +} + +/** + * gedit_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 +gedit_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; +} + +/** + * gedit_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 #GeditMessageType 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 #GeditMessageType + * + */ +GeditMessageType * +gedit_message_type_new_valist (const gchar *object_path, + const gchar *method, + guint num_optional, + va_list var_args) +{ + GeditMessageType *message_type; + + g_return_val_if_fail (object_path != NULL, NULL); + g_return_val_if_fail (method != NULL, NULL); + g_return_val_if_fail (gedit_message_type_is_valid_object_path (object_path), NULL); + + message_type = g_new0(GeditMessageType, 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); + + gedit_message_type_set_valist (message_type, num_optional, var_args); + return message_type; +} + +/** + * gedit_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 #GeditMessageType 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 #GeditMessageType + * + */ +GeditMessageType * +gedit_message_type_new (const gchar *object_path, + const gchar *method, + guint num_optional, + ...) +{ + GeditMessageType *message_type; + va_list var_args; + + va_start(var_args, num_optional); + message_type = gedit_message_type_new_valist (object_path, method, num_optional, var_args); + va_end(var_args); + + return message_type; +} + +/** + * gedit_message_type_set: + * @message_type: the #GeditMessageType + * @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 +gedit_message_type_set (GeditMessageType *message_type, + guint num_optional, + ...) +{ + va_list va_args; + + va_start (va_args, num_optional); + gedit_message_type_set_valist (message_type, num_optional, va_args); + va_end (va_args); +} + +/** + * gedit_message_type_set_valist: + * @message_type: the #GeditMessageType + * @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 +gedit_message_type_set_valist (GeditMessageType *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 (!gedit_message_type_is_supported (gtype)) + { + g_error ("Message type '%s' is not supported", g_type_name (gtype)); + + gedit_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); +} + +/** + * gedit_message_type_instantiate_valist: + * @message_type: the #GeditMessageType + * @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 + * + */ +GeditMessage * +gedit_message_type_instantiate_valist (GeditMessageType *message_type, + va_list va_args) +{ + GeditMessage *message; + + g_return_val_if_fail (message_type != NULL, NULL); + + message = GEDIT_MESSAGE (g_object_new (GEDIT_TYPE_MESSAGE, "type", message_type, NULL)); + gedit_message_set_valist (message, va_args); + + return message; +} + +/** + * gedit_message_type_instantiate: + * @message_type: the #GeditMessageType + * @...: 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 + * + */ +GeditMessage * +gedit_message_type_instantiate (GeditMessageType *message_type, + ...) +{ + GeditMessage *message; + va_list va_args; + + va_start (va_args, message_type); + message = gedit_message_type_instantiate_valist (message_type, va_args); + va_end (va_args); + + return message; +} + +/** + * gedit_message_type_get_object_path: + * @message_type: the #GeditMessageType + * + * Get the message type object path. + * + * Return value: the message type object path + * + */ +const gchar * +gedit_message_type_get_object_path (GeditMessageType *message_type) +{ + return message_type->object_path; +} + +/** + * gedit_message_type_get_method: + * @message_type: the #GeditMessageType + * + * Get the message type method. + * + * Return value: the message type method + * + */ +const gchar * +gedit_message_type_get_method (GeditMessageType *message_type) +{ + return message_type->method; +} + +/** + * gedit_message_type_lookup: + * @message_type: the #GeditMessageType + * @key: the argument key + * + * Get the argument key #GType. + * + * Return value: the #GType of @key + * + */ +GType +gedit_message_type_lookup (GeditMessageType *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 +{ + GeditMessageTypeForeach 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); +} + +/** + * gedit_message_type_foreach: + * @message_type: the #GeditMessageType + * @func: the callback function + * @user_data: user data supplied to the callback function + * + * Calls @func for each argument in the message type. + * + */ +void +gedit_message_type_foreach (GeditMessageType *message_type, + GeditMessageTypeForeach 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/gedit/gedit-message-type.h b/gedit/gedit-message-type.h new file mode 100755 index 00000000..d3140cdc --- /dev/null +++ b/gedit/gedit-message-type.h @@ -0,0 +1,67 @@ +#ifndef __GEDIT_MESSAGE_TYPE_H__ +#define __GEDIT_MESSAGE_TYPE_H__ + +#include <glib-object.h> +#include <stdarg.h> + +#include "gedit-message.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_MESSAGE_TYPE (gedit_message_type_get_type ()) +#define GEDIT_MESSAGE_TYPE(x) ((GeditMessageType *)(x)) + +typedef void (*GeditMessageTypeForeach) (const gchar *key, + GType type, + gboolean required, + gpointer user_data); + +typedef struct _GeditMessageType GeditMessageType; + +GType gedit_message_type_get_type (void) G_GNUC_CONST; + +gboolean gedit_message_type_is_supported (GType type); +gchar *gedit_message_type_identifier (const gchar *object_path, + const gchar *method); +gboolean gedit_message_type_is_valid_object_path (const gchar *object_path); + +GeditMessageType *gedit_message_type_new (const gchar *object_path, + const gchar *method, + guint num_optional, + ...) G_GNUC_NULL_TERMINATED; +GeditMessageType *gedit_message_type_new_valist (const gchar *object_path, + const gchar *method, + guint num_optional, + va_list va_args); + +void gedit_message_type_set (GeditMessageType *message_type, + guint num_optional, + ...) G_GNUC_NULL_TERMINATED; +void gedit_message_type_set_valist (GeditMessageType *message_type, + guint num_optional, + va_list va_args); + +GeditMessageType *gedit_message_type_ref (GeditMessageType *message_type); +void gedit_message_type_unref (GeditMessageType *message_type); + + +GeditMessage *gedit_message_type_instantiate_valist (GeditMessageType *message_type, + va_list va_args); +GeditMessage *gedit_message_type_instantiate (GeditMessageType *message_type, + ...) G_GNUC_NULL_TERMINATED; + +const gchar *gedit_message_type_get_object_path (GeditMessageType *message_type); +const gchar *gedit_message_type_get_method (GeditMessageType *message_type); + +GType gedit_message_type_lookup (GeditMessageType *message_type, + const gchar *key); + +void gedit_message_type_foreach (GeditMessageType *message_type, + GeditMessageTypeForeach func, + gpointer user_data); + +G_END_DECLS + +#endif /* __GEDIT_MESSAGE_TYPE_H__ */ + +// ex:ts=8:noet: diff --git a/gedit/gedit-message.c b/gedit/gedit-message.c new file mode 100755 index 00000000..f1e12daa --- /dev/null +++ b/gedit/gedit-message.c @@ -0,0 +1,593 @@ +#include "gedit-message.h" +#include "gedit-message-type.h" + +#include <string.h> +#include <gobject/gvaluecollector.h> + +/** + * SECTION:gedit-message + * @short_description: message bus message object + * @include: gedit/gedit-message.h + * + * Communication on a #GeditMessageBus is done through messages. Messages are + * sent over the bus and received by connecting callbacks on the message bus. + * A #GeditMessage is an instantiation of a #GeditMessageType, 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 GEDIT_MESSAGE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_MESSAGE, GeditMessagePrivate)) + +enum { + PROP_0, + + PROP_OBJECT_PATH, + PROP_METHOD, + PROP_TYPE +}; + +struct _GeditMessagePrivate +{ + GeditMessageType *type; + gboolean valid; + + GHashTable *values; +}; + +G_DEFINE_TYPE (GeditMessage, gedit_message, G_TYPE_OBJECT) + +static void +gedit_message_finalize (GObject *object) +{ + GeditMessage *message = GEDIT_MESSAGE (object); + + gedit_message_type_unref (message->priv->type); + g_hash_table_destroy (message->priv->values); + + G_OBJECT_CLASS (gedit_message_parent_class)->finalize (object); +} + +static void +gedit_message_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditMessage *msg = GEDIT_MESSAGE (object); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, gedit_message_type_get_object_path (msg->priv->type)); + break; + case PROP_METHOD: + g_value_set_string (value, gedit_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 +gedit_message_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditMessage *msg = GEDIT_MESSAGE (object); + + switch (prop_id) + { + case PROP_TYPE: + msg->priv->type = GEDIT_MESSAGE_TYPE (g_value_dup_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GValue * +add_value (GeditMessage *message, + const gchar *key) +{ + GValue *value; + GType type = gedit_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 +gedit_message_class_init (GeditMessageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_message_finalize; + object_class->get_property = gedit_message_get_property; + object_class->set_property = gedit_message_set_property; + + /** + * GeditMessage:object_path: + * + * The messages object path (e.g. /gedit/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)); + + /** + * GeditMessage: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)); + + /** + * GeditMEssage:type: + * + * The message type. + * + */ + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_boxed ("type", + "TYPE", + "The message type", + GEDIT_TYPE_MESSAGE_TYPE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (object_class, sizeof(GeditMessagePrivate)); +} + +static void +destroy_value (GValue *value) +{ + g_value_unset (value); + g_free (value); +} + +static void +gedit_message_init (GeditMessage *self) +{ + self->priv = GEDIT_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 (GeditMessage *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; +} + +/** + * gedit_message_get_method: + * @message: the #GeditMessage + * + * Get the message method. + * + * Return value: the message method + * + */ +const gchar * +gedit_message_get_method (GeditMessage *message) +{ + g_return_val_if_fail (GEDIT_IS_MESSAGE (message), NULL); + + return gedit_message_type_get_method (message->priv->type); +} + +/** + * gedit_message_get_object_path: + * @message: the #GeditMessage + * + * Get the message object path. + * + * Return value: the message object path + * + */ +const gchar * +gedit_message_get_object_path (GeditMessage *message) +{ + g_return_val_if_fail (GEDIT_IS_MESSAGE (message), NULL); + + return gedit_message_type_get_object_path (message->priv->type); +} + +/** + * gedit_message_set: + * @message: the #GeditMessage + * @...: 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 +gedit_message_set (GeditMessage *message, + ...) +{ + va_list ap; + + g_return_if_fail (GEDIT_IS_MESSAGE (message)); + + va_start (ap, message); + gedit_message_set_valist (message, ap); + va_end (ap); +} + +/** + * gedit_message_set_valist: + * @message: the #GeditMessage + * @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 +gedit_message_set_valist (GeditMessage *message, + va_list var_args) +{ + const gchar *key; + + g_return_if_fail (GEDIT_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); + } +} + +/** + * gedit_message_set_value: + * @message: the #GeditMessage + * @key: the argument key + * @value: the argument value + * + * Set value of message argument @key to @value. + * + */ +void +gedit_message_set_value (GeditMessage *message, + const gchar *key, + GValue *value) +{ + GValue *container; + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_message_set_valuesv: + * @message: the #GeditMessage + * @keys: keys to set values for + * @values: values to set + * @n_values: number of arguments to set values for + * + * Set message argument values. + * + */ +void +gedit_message_set_valuesv (GeditMessage *message, + const gchar **keys, + GValue *values, + gint n_values) +{ + gint i; + + g_return_if_fail (GEDIT_IS_MESSAGE (message)); + + for (i = 0; i < n_values; i++) + { + gedit_message_set_value (message, keys[i], &values[i]); + } +} + +/** + * gedit_message_get: + * @message: the #GeditMessage + * @...: 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 +gedit_message_get (GeditMessage *message, + ...) +{ + va_list ap; + + g_return_if_fail (GEDIT_IS_MESSAGE (message)); + + va_start (ap, message); + gedit_message_get_valist (message, ap); + va_end (ap); +} + +/** + * gedit_message_get_valist: + * @message: the #GeditMessage + * @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 +gedit_message_get_valist (GeditMessage *message, + va_list var_args) +{ + const gchar *key; + + g_return_if_fail (GEDIT_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 (©, G_VALUE_TYPE (container)); + g_value_copy (container, ©); + + G_VALUE_LCOPY (©, 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 (©); + } +} + +/** + * gedit_message_get_value: + * @message: the #GeditMessage + * @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 +gedit_message_get_value (GeditMessage *message, + const gchar *key, + GValue *value) +{ + GValue *container; + + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_message_get_key_type: + * @message: the #GeditMessage + * @key: the argument key + * + * Get the type of a message argument. + * + * Return value: the type of @key + * + */ +GType +gedit_message_get_key_type (GeditMessage *message, + const gchar *key) +{ + g_return_val_if_fail (GEDIT_IS_MESSAGE (message), G_TYPE_INVALID); + g_return_val_if_fail (message->priv->type != NULL, G_TYPE_INVALID); + + return gedit_message_type_lookup (message->priv->type, key); +} + +/** + * gedit_message_has_key: + * @message: the #GeditMessage + * @key: the argument key + * + * Check whether the message has a specific key. + * + * Return value: %TRUE if @message has argument @key + * + */ +gboolean +gedit_message_has_key (GeditMessage *message, + const gchar *key) +{ + g_return_val_if_fail (GEDIT_IS_MESSAGE (message), FALSE); + + return value_lookup (message, key, FALSE) != NULL; +} + +typedef struct +{ + GeditMessage *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; +} + +/** + * gedit_message_validate: + * @message: the #GeditMessage + * + * Validates the message arguments according to the message type. + * + * Return value: %TRUE if the message is valid + * + */ +gboolean +gedit_message_validate (GeditMessage *message) +{ + ValidateInfo info = {message, TRUE}; + + g_return_val_if_fail (GEDIT_IS_MESSAGE (message), FALSE); + g_return_val_if_fail (message->priv->type != NULL, FALSE); + + if (!message->priv->valid) + { + gedit_message_type_foreach (message->priv->type, + (GeditMessageTypeForeach)validate_key, + &info); + + message->priv->valid = info.valid; + } + + return message->priv->valid; +} + +// ex:ts=8:noet: diff --git a/gedit/gedit-message.h b/gedit/gedit-message.h new file mode 100755 index 00000000..9d17ac9e --- /dev/null +++ b/gedit/gedit-message.h @@ -0,0 +1,71 @@ +#ifndef __GEDIT_MESSAGE_H__ +#define __GEDIT_MESSAGE_H__ + +#include <glib-object.h> +#include <stdarg.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_MESSAGE (gedit_message_get_type ()) +#define GEDIT_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE, GeditMessage)) +#define GEDIT_MESSAGE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_MESSAGE, GeditMessage const)) +#define GEDIT_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_MESSAGE, GeditMessageClass)) +#define GEDIT_IS_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_MESSAGE)) +#define GEDIT_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_MESSAGE)) +#define GEDIT_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_MESSAGE, GeditMessageClass)) + +typedef struct _GeditMessage GeditMessage; +typedef struct _GeditMessageClass GeditMessageClass; +typedef struct _GeditMessagePrivate GeditMessagePrivate; + +struct _GeditMessage { + GObject parent; + + GeditMessagePrivate *priv; +}; + +struct _GeditMessageClass { + GObjectClass parent_class; +}; + +GType gedit_message_get_type (void) G_GNUC_CONST; + +struct _GeditMessageType gedit_message_get_message_type (GeditMessage *message); + +void gedit_message_get (GeditMessage *message, + ...) G_GNUC_NULL_TERMINATED; +void gedit_message_get_valist (GeditMessage *message, + va_list var_args); +void gedit_message_get_value (GeditMessage *message, + const gchar *key, + GValue *value); + +void gedit_message_set (GeditMessage *message, + ...) G_GNUC_NULL_TERMINATED; +void gedit_message_set_valist (GeditMessage *message, + va_list var_args); +void gedit_message_set_value (GeditMessage *message, + const gchar *key, + GValue *value); +void gedit_message_set_valuesv (GeditMessage *message, + const gchar **keys, + GValue *values, + gint n_values); + +const gchar *gedit_message_get_object_path (GeditMessage *message); +const gchar *gedit_message_get_method (GeditMessage *message); + +gboolean gedit_message_has_key (GeditMessage *message, + const gchar *key); + +GType gedit_message_get_key_type (GeditMessage *message, + const gchar *key); + +gboolean gedit_message_validate (GeditMessage *message); + + +G_END_DECLS + +#endif /* __GEDIT_MESSAGE_H__ */ + +// ex:ts=8:noet: diff --git a/gedit/gedit-metadata-manager.c b/gedit/gedit-metadata-manager.c new file mode 100755 index 00000000..bdb00fff --- /dev/null +++ b/gedit/gedit-metadata-manager.c @@ -0,0 +1,563 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-metadata-manager.c + * This file is part of gedit + * + * 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 gedit Team, 2003-2007. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-metadata-manager.h" +#include "gedit-debug.h" +#include "gedit-dirs.h" + +/* +#define GEDIT_METADATA_VERBOSE_DEBUG 1 +*/ + +#define METADATA_FILE "gedit-metadata.xml" + +#define MAX_ITEMS 50 + +typedef struct _GeditMetadataManager GeditMetadataManager; + +typedef struct _Item Item; + +struct _Item +{ + time_t atime; /* time of last access */ + + GHashTable *values; +}; + +struct _GeditMetadataManager +{ + gboolean values_loaded; /* It is true if the file + has been read */ + + guint timeout_id; + + GHashTable *items; +}; + +static gboolean gedit_metadata_manager_save (gpointer data); + + +static GeditMetadataManager *gedit_metadata_manager = NULL; + +static void +item_free (gpointer data) +{ + Item *item; + + g_return_if_fail (data != NULL); + +#ifdef GEDIT_METADATA_VERBOSE_DEBUG + gedit_debug (DEBUG_METADATA); +#endif + + item = (Item *)data; + + if (item->values != NULL) + g_hash_table_destroy (item->values); + + g_free (item); +} + +static void +gedit_metadata_manager_arm_timeout (void) +{ + if (gedit_metadata_manager->timeout_id == 0) + { + gedit_metadata_manager->timeout_id = + g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE, + 2, + (GSourceFunc)gedit_metadata_manager_save, + NULL, + NULL); + } +} + +static gboolean +gedit_metadata_manager_init (void) +{ + gedit_debug (DEBUG_METADATA); + + if (gedit_metadata_manager != NULL) + return TRUE; + + gedit_metadata_manager = g_new0 (GeditMetadataManager, 1); + + gedit_metadata_manager->values_loaded = FALSE; + + gedit_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 gedit */ +void +gedit_metadata_manager_shutdown (void) +{ + gedit_debug (DEBUG_METADATA); + + if (gedit_metadata_manager == NULL) + return; + + if (gedit_metadata_manager->timeout_id) + { + g_source_remove (gedit_metadata_manager->timeout_id); + gedit_metadata_manager->timeout_id = 0; + gedit_metadata_manager_save (NULL); + } + + if (gedit_metadata_manager->items != NULL) + g_hash_table_destroy (gedit_metadata_manager->items); + + g_free (gedit_metadata_manager); + gedit_metadata_manager = NULL; +} + +static void +parseItem (xmlDocPtr doc, xmlNodePtr cur) +{ + Item *item; + + xmlChar *uri; + xmlChar *atime; + +#ifdef GEDIT_METADATA_VERBOSE_DEBUG + gedit_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 (gedit_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 = gedit_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; + + gedit_debug (DEBUG_METADATA); + + g_return_val_if_fail (gedit_metadata_manager != NULL, FALSE); + g_return_val_if_fail (gedit_metadata_manager->values_loaded == FALSE, FALSE); + + gedit_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 * +gedit_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); + + gedit_debug_message (DEBUG_METADATA, "URI: %s --- key: %s", uri, key ); + + gedit_metadata_manager_init (); + + if (!gedit_metadata_manager->values_loaded) + { + gboolean res; + + res = load_values (); + + if (!res) + return NULL; + } + + item = (Item *)g_hash_table_lookup (gedit_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 +gedit_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); + + gedit_debug_message (DEBUG_METADATA, "URI: %s --- key: %s --- value: %s", uri, key, value); + + gedit_metadata_manager_init (); + + if (!gedit_metadata_manager->values_loaded) + { + gboolean res; + + res = load_values (); + + if (!res) + return; + } + + item = (Item *)g_hash_table_lookup (gedit_metadata_manager->items, + uri); + + if (item == NULL) + { + item = g_new0 (Item, 1); + + g_hash_table_insert (gedit_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); + + gedit_metadata_manager_arm_timeout (); +} + +static void +save_values (const gchar *key, const gchar *value, xmlNodePtr parent) +{ + xmlNodePtr xml_node; + +#ifdef GEDIT_METADATA_VERBOSE_DEBUG + gedit_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 GEDIT_METADATA_VERBOSE_DEBUG + gedit_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 GEDIT_METADATA_VERBOSE_DEBUG + gedit_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 GEDIT_METADATA_VERBOSE_DEBUG + gedit_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 GEDIT_METADATA_VERBOSE_DEBUG + gedit_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 (gedit_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 (gedit_metadata_manager->items) > MAX_ITEMS) + { + gpointer key_to_remove = NULL; + + g_hash_table_foreach (gedit_metadata_manager->items, + (GHFunc)get_oldest, + &key_to_remove); + + g_return_if_fail (key_to_remove != NULL); + + g_hash_table_remove (gedit_metadata_manager->items, + key_to_remove); + } +} + +static gboolean +gedit_metadata_manager_save (gpointer data) +{ + xmlDocPtr doc; + xmlNodePtr root; + gchar *file_name; + + gedit_debug (DEBUG_METADATA); + + gedit_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 (gedit_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 = gedit_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); + + gedit_debug_message (DEBUG_METADATA, "DONE"); + + return FALSE; +} + diff --git a/gedit/gedit-metadata-manager.h b/gedit/gedit-metadata-manager.h new file mode 100755 index 00000000..6ee324f6 --- /dev/null +++ b/gedit/gedit-metadata-manager.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-metadata-manager.h + * This file is part of gedit + * + * 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 gedit Team, 2003. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef __GEDIT_METADATA_MANAGER_H__ +#define __GEDIT_METADATA_MANAGER_H__ + +#include <glib.h> + +G_BEGIN_DECLS + + +/* This function must be called before exiting gedit */ +void gedit_metadata_manager_shutdown (void); + + +gchar *gedit_metadata_manager_get (const gchar *uri, + const gchar *key); +void gedit_metadata_manager_set (const gchar *uri, + const gchar *key, + const gchar *value); + +G_END_DECLS + +#endif /* __GEDIT_METADATA_MANAGER_H__ */ diff --git a/gedit/gedit-notebook.c b/gedit/gedit-notebook.c new file mode 100755 index 00000000..2578815a --- /dev/null +++ b/gedit/gedit-notebook.c @@ -0,0 +1,1099 @@ +/* + * gedit-notebook.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-notebook.h" +#include "gedit-tab.h" +#include "gedit-tab-label.h" +#include "gedit-marshal.h" +#include "gedit-window.h" + +#ifdef BUILD_SPINNER +#include "gedit-spinner.h" +#endif + +#define AFTER_ALL_TABS -1 +#define NOT_IN_APP_WINDOWS -2 + +#define GEDIT_NOTEBOOK_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_NOTEBOOK, GeditNotebookPrivate)) + +struct _GeditNotebookPrivate +{ + 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(GeditNotebook, gedit_notebook, GTK_TYPE_NOTEBOOK) + +static void gedit_notebook_finalize (GObject *object); + +static gboolean gedit_notebook_change_current_page (GtkNotebook *notebook, + gint offset); + +static void move_current_tab_to_another_notebook (GeditNotebook *src, + GeditNotebook *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 +gedit_notebook_destroy (GtkObject *object) +{ + GeditNotebook *notebook = GEDIT_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)) + { + gedit_notebook_remove_tab (notebook, + GEDIT_TAB (l->data)); + } + + g_list_free (children); + notebook->priv->destroy_has_run = TRUE; + } + + GTK_OBJECT_CLASS (gedit_notebook_parent_class)->destroy (object); +} + +static void +gedit_notebook_class_init (GeditNotebookClass *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 = gedit_notebook_finalize; + gtkobject_class->destroy = gedit_notebook_destroy; + + notebook_class->change_current_page = gedit_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 (GeditNotebookClass, tab_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[TAB_REMOVED] = + g_signal_new ("tab_removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditNotebookClass, tab_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[TAB_DETACHED] = + g_signal_new ("tab_detached", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditNotebookClass, tab_detached), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[TABS_REORDERED] = + g_signal_new ("tabs_reordered", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditNotebookClass, 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 (GeditNotebookClass, tab_close_request), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + + g_type_class_add_private (object_class, sizeof(GeditNotebookPrivate)); +} + +static GeditNotebook * +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 GeditWindow */ + if ((toplevel != NULL) && + GEDIT_IS_WINDOW (toplevel)) + { + return GEDIT_NOTEBOOK (_gedit_window_get_notebook + (GEDIT_WINDOW (toplevel))); + } + + /* We are outside all windows containing a notebook */ + return NULL; +} + +static gboolean +is_in_notebook_window (GeditNotebook *notebook, + gint abs_x, + gint abs_y) +{ + GeditNotebook *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 (GeditNotebook *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, + GeditNotebook **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; + } +} + +/** + * gedit_notebook_move_tab: + * @src: a #GeditNotebook + * @dest: a #GeditNotebook + * @tab: a #GeditTab + * @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 +gedit_notebook_move_tab (GeditNotebook *src, + GeditNotebook *dest, + GeditTab *tab, + gint dest_position) +{ + g_return_if_fail (GEDIT_IS_NOTEBOOK (src)); + g_return_if_fail (GEDIT_IS_NOTEBOOK (dest)); + g_return_if_fail (src != dest); + g_return_if_fail (GEDIT_IS_TAB (tab)); + + /* make sure the tab isn't destroyed while we move it */ + g_object_ref (tab); + gedit_notebook_remove_tab (src, tab); + gedit_notebook_add_tab (dest, tab, dest_position, TRUE); + g_object_unref (tab); +} + +/** + * gedit_notebook_reorder_tab: + * @src: a #GeditNotebook + * @tab: a #GeditTab + * @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 +gedit_notebook_reorder_tab (GeditNotebook *src, + GeditTab *tab, + gint dest_position) +{ + gint old_position; + + g_return_if_fail (GEDIT_IS_NOTEBOOK (src)); + g_return_if_fail (GEDIT_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 (GeditNotebook *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 (GeditNotebook *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 (GeditNotebook *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); + + gedit_notebook_reorder_tab (GEDIT_NOTEBOOK (notebook), + GEDIT_TAB (cur_tab), + dest_position); + } +} + +static gboolean +motion_notify_cb (GeditNotebook *notebook, + GdkEventMotion *event, + gpointer data) +{ + GeditNotebook *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 (GeditNotebook *src, + GeditNotebook *dest, + GdkEventMotion *event, + gint dest_position) +{ + GeditTab *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 (GEDIT_IS_NOTEBOOK (dest)); + g_return_if_fail (dest != src); + + cur_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (src)); + tab = GEDIT_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)); + + gedit_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 (GeditNotebook *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 (GeditNotebook *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; +} + +/** + * gedit_notebook_new: + * + * Creates a new #GeditNotebook object. + * + * Returns: a new #GeditNotebook + */ +GtkWidget * +gedit_notebook_new (void) +{ + return GTK_WIDGET (g_object_new (GEDIT_TYPE_NOTEBOOK, NULL)); +} + +static void +gedit_notebook_switch_page_cb (GtkNotebook *notebook, + GtkNotebookPage *page, + guint page_num, + gpointer data) +{ + GeditNotebook *nb = GEDIT_NOTEBOOK (notebook); + GtkWidget *child; + GeditView *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 = gedit_tab_get_view (GEDIT_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 (GeditNotebook *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 +gedit_notebook_init (GeditNotebook *notebook) +{ + notebook->priv = GEDIT_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 (gedit_notebook_switch_page_cb), + NULL); +} + +static void +gedit_notebook_finalize (GObject *object) +{ + GeditNotebook *notebook = GEDIT_NOTEBOOK (object); + + g_list_free (notebook->priv->focused_pages); + + G_OBJECT_CLASS (gedit_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 +gedit_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 (GeditTabLabel *tab_label, GeditNotebook *notebook) +{ + GeditTab *tab; + + tab = gedit_tab_label_get_tab (tab_label); + g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, tab); +} + +static GtkWidget * +create_tab_label (GeditNotebook *nb, + GeditTab *tab) +{ + GtkWidget *tab_label; + + tab_label = gedit_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 (GeditNotebook *nb, + GeditTab *tab) +{ + GtkWidget *tab_label; + + tab_label = gedit_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 (GeditTab *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; +} + +/** + * gedit_notebook_set_always_show_tabs: + * @nb: a #GeditNotebook + * @show_tabs: %TRUE to always show the tabs + * + * Sets the visibility of the tabs in the @nb. + */ +void +gedit_notebook_set_always_show_tabs (GeditNotebook *nb, + gboolean show_tabs) +{ + g_return_if_fail (GEDIT_IS_NOTEBOOK (nb)); + + nb->priv->always_show_tabs = (show_tabs != FALSE); + + update_tabs_visibility (nb, FALSE); +} + +/** + * gedit_notebook_add_tab: + * @nb: a #GeditNotebook + * @tab: a #GeditTab + * @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 +gedit_notebook_add_tab (GeditNotebook *nb, + GeditTab *tab, + gint position, + gboolean jump_to) +{ + GtkWidget *tab_label; + + g_return_if_fail (GEDIT_IS_NOTEBOOK (nb)); + g_return_if_fail (GEDIT_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) + { + GeditView *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 = gedit_tab_get_view (tab); + + gtk_widget_grab_focus (GTK_WIDGET (view)); + } +} + +static void +smart_tab_switching_on_closure (GeditNotebook *nb, + GeditTab *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 (GeditTab *tab, + GeditNotebook *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); +} + +/** + * gedit_notebook_remove_tab: + * @nb: a #GeditNotebook + * @tab: a #GeditTab + * + * Removes @tab from @nb. + */ +void +gedit_notebook_remove_tab (GeditNotebook *nb, + GeditTab *tab) +{ + gint position, curr; + + g_return_if_fail (GEDIT_IS_NOTEBOOK (nb)); + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_notebook_remove_all_tabs: + * @nb: a #GeditNotebook + * + * Removes all #GeditTab from @nb. + */ +void +gedit_notebook_remove_all_tabs (GeditNotebook *nb) +{ + g_return_if_fail (GEDIT_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 (GeditTab *tab, + GeditNotebook *nb) +{ + GtkWidget *tab_label; + + tab_label = get_tab_label (tab); + + gedit_tab_label_set_close_button_sensitive (GEDIT_TAB_LABEL (tab_label), + nb->priv->close_buttons_sensitive); +} + +/** + * gedit_notebook_set_close_buttons_sensitive: + * @nb: a #GeditNotebook + * @sensitive: %TRUE to make the buttons sensitive + * + * Sets whether the close buttons in the tabs of @nb are sensitive. + */ +void +gedit_notebook_set_close_buttons_sensitive (GeditNotebook *nb, + gboolean sensitive) +{ + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_notebook_get_close_buttons_sensitive: + * @nb: a #GeditNotebook + * + * Whether the close buttons are sensitive. + * + * Returns: %TRUE if the close buttons are sensitive + */ +gboolean +gedit_notebook_get_close_buttons_sensitive (GeditNotebook *nb) +{ + g_return_val_if_fail (GEDIT_IS_NOTEBOOK (nb), TRUE); + + return nb->priv->close_buttons_sensitive; +} + +/** + * gedit_notebook_set_tab_drag_and_drop_enabled: + * @nb: a #GeditNotebook + * @enable: %TRUE to enable the drag and drop + * + * Sets whether drag and drop of tabs in the @nb is enabled. + */ +void +gedit_notebook_set_tab_drag_and_drop_enabled (GeditNotebook *nb, + gboolean enable) +{ + g_return_if_fail (GEDIT_IS_NOTEBOOK (nb)); + + enable = (enable != FALSE); + + if (enable == nb->priv->tab_drag_and_drop_enabled) + return; + + nb->priv->tab_drag_and_drop_enabled = enable; +} + +/** + * gedit_notebook_get_tab_drag_and_drop_enabled: + * @nb: a #GeditNotebook + * + * Whether the drag and drop is enabled in the @nb. + * + * Returns: %TRUE if the drag and drop is enabled. + */ +gboolean +gedit_notebook_get_tab_drag_and_drop_enabled (GeditNotebook *nb) +{ + g_return_val_if_fail (GEDIT_IS_NOTEBOOK (nb), TRUE); + + return nb->priv->tab_drag_and_drop_enabled; +} + diff --git a/gedit/gedit-notebook.h b/gedit/gedit-notebook.h new file mode 100755 index 00000000..ada7c274 --- /dev/null +++ b/gedit/gedit-notebook.h @@ -0,0 +1,143 @@ +/* + * gedit-notebook.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 GEDIT_NOTEBOOK_H +#define GEDIT_NOTEBOOK_H + +#include <gedit/gedit-tab.h> + +#include <glib.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_NOTEBOOK (gedit_notebook_get_type ()) +#define GEDIT_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_NOTEBOOK, GeditNotebook)) +#define GEDIT_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_NOTEBOOK, GeditNotebookClass)) +#define GEDIT_IS_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_NOTEBOOK)) +#define GEDIT_IS_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_NOTEBOOK)) +#define GEDIT_NOTEBOOK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_NOTEBOOK, GeditNotebookClass)) + +/* Private structure type */ +typedef struct _GeditNotebookPrivate GeditNotebookPrivate; + +/* + * Main object structure + */ +typedef struct _GeditNotebook GeditNotebook; + +struct _GeditNotebook +{ + GtkNotebook notebook; + + /*< private >*/ + GeditNotebookPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditNotebookClass GeditNotebookClass; + +struct _GeditNotebookClass +{ + GtkNotebookClass parent_class; + + /* Signals */ + void (* tab_added) (GeditNotebook *notebook, + GeditTab *tab); + void (* tab_removed) (GeditNotebook *notebook, + GeditTab *tab); + void (* tab_detached) (GeditNotebook *notebook, + GeditTab *tab); + void (* tabs_reordered) (GeditNotebook *notebook); + void (* tab_close_request) + (GeditNotebook *notebook, + GeditTab *tab); +}; + +/* + * Public methods + */ +GType gedit_notebook_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_notebook_new (void); + +void gedit_notebook_add_tab (GeditNotebook *nb, + GeditTab *tab, + gint position, + gboolean jump_to); + +void gedit_notebook_remove_tab (GeditNotebook *nb, + GeditTab *tab); + +void gedit_notebook_remove_all_tabs (GeditNotebook *nb); + +void gedit_notebook_reorder_tab (GeditNotebook *src, + GeditTab *tab, + gint dest_position); + +void gedit_notebook_move_tab (GeditNotebook *src, + GeditNotebook *dest, + GeditTab *tab, + gint dest_position); + +/* FIXME: do we really need this function ? */ +void gedit_notebook_set_always_show_tabs + (GeditNotebook *nb, + gboolean show_tabs); + +void gedit_notebook_set_close_buttons_sensitive + (GeditNotebook *nb, + gboolean sensitive); + +gboolean gedit_notebook_get_close_buttons_sensitive + (GeditNotebook *nb); + +void gedit_notebook_set_tab_drag_and_drop_enabled + (GeditNotebook *nb, + gboolean enable); + +gboolean gedit_notebook_get_tab_drag_and_drop_enabled + (GeditNotebook *nb); + +G_END_DECLS + +#endif /* GEDIT_NOTEBOOK_H */ diff --git a/gedit/gedit-object-module.c b/gedit/gedit-object-module.c new file mode 100755 index 00000000..3c6f09c1 --- /dev/null +++ b/gedit/gedit-object-module.c @@ -0,0 +1,343 @@ +/* + * gedit-object-module.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id: gedit-module.c 6314 2008-06-05 12:57:53Z pborelli $ + */ + +#include "config.h" + +#include "gedit-object-module.h" +#include "gedit-debug.h" + +typedef GType (*GeditObjectModuleRegisterFunc) (GTypeModule *); + +enum { + PROP_0, + PROP_MODULE_NAME, + PROP_PATH, + PROP_TYPE_REGISTRATION, + PROP_RESIDENT +}; + +struct _GeditObjectModulePrivate +{ + GModule *library; + + GType type; + gchar *path; + gchar *module_name; + gchar *type_registration; + + gboolean resident; +}; + +G_DEFINE_TYPE (GeditObjectModule, gedit_object_module, G_TYPE_TYPE_MODULE); + +static gboolean +gedit_object_module_load (GTypeModule *gmodule) +{ + GeditObjectModule *module = GEDIT_OBJECT_MODULE (gmodule); + GeditObjectModuleRegisterFunc register_func; + gchar *path; + + gedit_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); + gedit_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 *) ®ister_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 +gedit_object_module_unload (GTypeModule *gmodule) +{ + GeditObjectModule *module = GEDIT_OBJECT_MODULE (gmodule); + + gedit_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 +gedit_object_module_init (GeditObjectModule *module) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditObjectModule %p initialising", module); + + module->priv = G_TYPE_INSTANCE_GET_PRIVATE (module, + GEDIT_TYPE_OBJECT_MODULE, + GeditObjectModulePrivate); +} + +static void +gedit_object_module_finalize (GObject *object) +{ + GeditObjectModule *module = GEDIT_OBJECT_MODULE (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditObjectModule %p finalising", module); + + g_free (module->priv->path); + g_free (module->priv->module_name); + g_free (module->priv->type_registration); + + G_OBJECT_CLASS (gedit_object_module_parent_class)->finalize (object); +} + +static void +gedit_object_module_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditObjectModule *module = GEDIT_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 +gedit_object_module_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditObjectModule *module = GEDIT_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 +gedit_object_module_class_init (GeditObjectModuleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (klass); + + object_class->set_property = gedit_object_module_set_property; + object_class->get_property = gedit_object_module_get_property; + object_class->finalize = gedit_object_module_finalize; + + module_class->load = gedit_object_module_load; + module_class->unload = gedit_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 (GeditObjectModulePrivate)); +} + +GeditObjectModule * +gedit_object_module_new (const gchar *module_name, + const gchar *path, + const gchar *type_registration, + gboolean resident) +{ + return (GeditObjectModule *)g_object_new (GEDIT_TYPE_OBJECT_MODULE, + "module-name", + module_name, + "path", + path, + "type-registration", + type_registration, + "resident", + resident, + NULL); +} + +GObject * +gedit_object_module_new_object (GeditObjectModule *module, + const gchar *first_property_name, + ...) +{ + va_list var_args; + GObject *result; + + g_return_val_if_fail (module->priv->type != 0, NULL); + + gedit_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 * +gedit_object_module_get_path (GeditObjectModule *module) +{ + g_return_val_if_fail (GEDIT_IS_OBJECT_MODULE (module), NULL); + + return module->priv->path; +} + +const gchar * +gedit_object_module_get_module_name (GeditObjectModule *module) +{ + g_return_val_if_fail (GEDIT_IS_OBJECT_MODULE (module), NULL); + + return module->priv->module_name; +} + +const gchar * +gedit_object_module_get_type_registration (GeditObjectModule *module) +{ + g_return_val_if_fail (GEDIT_IS_OBJECT_MODULE (module), NULL); + + return module->priv->type_registration; +} + +GType +gedit_object_module_get_object_type (GeditObjectModule *module) +{ + g_return_val_if_fail (GEDIT_IS_OBJECT_MODULE (module), 0); + + return module->priv->type; +} diff --git a/gedit/gedit-object-module.h b/gedit/gedit-object-module.h new file mode 100755 index 00000000..06210794 --- /dev/null +++ b/gedit/gedit-object-module.h @@ -0,0 +1,94 @@ +/* + * gedit-object-module.h + * This file is part of gedit + * + * 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 gedit-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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id: gedit-module.h 6263 2008-05-05 10:52:10Z sfre $ + */ + +#ifndef __GEDIT_OBJECT_MODULE_H__ +#define __GEDIT_OBJECT_MODULE_H__ + +#include <glib-object.h> +#include <gmodule.h> +#include <stdarg.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_OBJECT_MODULE (gedit_object_module_get_type ()) +#define GEDIT_OBJECT_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_OBJECT_MODULE, GeditObjectModule)) +#define GEDIT_OBJECT_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_OBJECT_MODULE, GeditObjectModuleClass)) +#define GEDIT_IS_OBJECT_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_OBJECT_MODULE)) +#define GEDIT_IS_OBJECT_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_OBJECT_MODULE)) +#define GEDIT_OBJECT_MODULE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_OBJECT_MODULE, GeditObjectModuleClass)) + +typedef struct _GeditObjectModule GeditObjectModule; +typedef struct _GeditObjectModulePrivate GeditObjectModulePrivate; + +struct _GeditObjectModule +{ + GTypeModule parent; + + GeditObjectModulePrivate *priv; +}; + +typedef struct _GeditObjectModuleClass GeditObjectModuleClass; + +struct _GeditObjectModuleClass +{ + GTypeModuleClass parent_class; + + /* Virtual class methods */ + void (* garbage_collect) (); +}; + +GType gedit_object_module_get_type (void) G_GNUC_CONST; + +GeditObjectModule *gedit_object_module_new (const gchar *module_name, + const gchar *path, + const gchar *type_registration, + gboolean resident); + +GObject *gedit_object_module_new_object (GeditObjectModule *module, + const gchar *first_property_name, + ...); + +GType gedit_object_module_get_object_type (GeditObjectModule *module); +const gchar *gedit_object_module_get_path (GeditObjectModule *module); +const gchar *gedit_object_module_get_module_name (GeditObjectModule *module); +const gchar *gedit_object_module_get_type_registration (GeditObjectModule *module); + +G_END_DECLS + +#endif diff --git a/gedit/gedit-panel.c b/gedit/gedit-panel.c new file mode 100755 index 00000000..fb1f9672 --- /dev/null +++ b/gedit/gedit-panel.c @@ -0,0 +1,950 @@ +/* + * gedit-panel.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#include "gedit-panel.h" + +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> + +#include "gedit-close-button.h" +#include "gedit-window.h" +#include "gedit-debug.h" + +#define PANEL_ITEM_KEY "GeditPanelItemKey" + +#define GEDIT_PANEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_PANEL, GeditPanelPrivate)) + +struct _GeditPanelPrivate +{ + GtkOrientation orientation; + + /* Title bar (vertical panel only) */ + GtkWidget *title_image; + GtkWidget *title_label; + + /* Notebook */ + GtkWidget *notebook; +}; + +typedef struct _GeditPanelItem GeditPanelItem; + +struct _GeditPanelItem +{ + 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 *gedit_panel_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); + + +G_DEFINE_TYPE(GeditPanel, gedit_panel, GTK_TYPE_VBOX) + +static void +gedit_panel_finalize (GObject *obj) +{ + if (G_OBJECT_CLASS (gedit_panel_parent_class)->finalize) + (*G_OBJECT_CLASS (gedit_panel_parent_class)->finalize) (obj); +} + +static void +gedit_panel_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditPanel *panel = GEDIT_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 +gedit_panel_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditPanel *panel = GEDIT_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 +gedit_panel_close (GeditPanel *panel) +{ + gtk_widget_hide (GTK_WIDGET (panel)); +} + +static void +gedit_panel_focus_document (GeditPanel *panel) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (panel)); +#if !GTK_CHECK_VERSION (2, 18, 0) + if (GTK_WIDGET_TOPLEVEL (toplevel) && GEDIT_IS_WINDOW (toplevel)) +#else + if (gtk_widget_is_toplevel (toplevel) && GEDIT_IS_WINDOW (toplevel)) +#endif + { + GeditView *view; + + view = gedit_window_get_active_view (GEDIT_WINDOW (toplevel)); + if (view != NULL) + gtk_widget_grab_focus (GTK_WIDGET (view)); + } +} + +static void +gedit_panel_grab_focus (GtkWidget *w) +{ + gint n; + GtkWidget *tab; + GeditPanel *panel = GEDIT_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 +gedit_panel_class_init (GeditPanelClass *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 (GeditPanelPrivate)); + + object_class->constructor = gedit_panel_constructor; + object_class->finalize = gedit_panel_finalize; + object_class->get_property = gedit_panel_get_property; + object_class->set_property = gedit_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 = gedit_panel_grab_focus; + + klass->close = gedit_panel_close; + klass->focus_document = gedit_panel_focus_document; + + signals[ITEM_ADDED] = + g_signal_new ("item_added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditPanelClass, 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 (GeditPanelClass, 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 (GeditPanelClass, 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 (GeditPanelClass, 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.mate.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 (GeditPanel *panel, + GeditPanelItem *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, + GeditPanel *panel) +{ + GtkWidget *item; + GeditPanelItem *data; + + item = gtk_notebook_get_nth_page (notebook, page_num); + g_return_if_fail (item != NULL); + + data = (GeditPanelItem *)g_object_get_data (G_OBJECT (item), + PANEL_ITEM_KEY); + g_return_if_fail (data != NULL); + + sync_title (panel, data); +} + +static void +panel_show (GeditPanel *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 +gedit_panel_init (GeditPanel *panel) +{ + panel->priv = GEDIT_PANEL_GET_PRIVATE (panel); +} + +static void +close_button_clicked_cb (GtkWidget *widget, + GtkWidget *panel) +{ + gtk_widget_hide (panel); +} + +static GtkWidget * +create_close_button (GeditPanel *panel) +{ + GtkWidget *button; + + button = gedit_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 (GeditPanel *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 (GeditPanel *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 (GeditPanel *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 * +gedit_panel_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + + /* Invoke parent constructor. */ + GeditPanelClass *klass = GEDIT_PANEL_CLASS (g_type_class_peek (GEDIT_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) */ + GeditPanel *panel = GEDIT_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; +} + +/** + * gedit_panel_new: + * @orientation: a #GtkOrientation + * + * Creates a new #GeditPanel with the given @orientation. You shouldn't create + * a new panel use gedit_window_get_side_panel() or gedit_window_get_bottom_panel() + * instead. + * + * Returns: a new #GeditPanel object. + */ +GtkWidget * +gedit_panel_new (GtkOrientation orientation) +{ + return GTK_WIDGET (g_object_new (GEDIT_TYPE_PANEL, "orientation", orientation, NULL)); +} + +static GtkWidget * +build_tab_label (GeditPanel *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; +} + +/** + * gedit_panel_add_item: + * @panel: a #GeditPanel + * @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 +gedit_panel_add_item (GeditPanel *panel, + GtkWidget *item, + const gchar *name, + GtkWidget *image) +{ + GeditPanelItem *data; + GtkWidget *tab_label; + GtkWidget *menu_label; + gint w, h; + + g_return_if_fail (GEDIT_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 (GeditPanelItem, 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); +} + +/** + * gedit_panel_add_item_with_stock_icon: + * @panel: a #GeditPanel + * @item: the #GtkWidget to add to the @panel + * @name: the name to be shown in the @panel + * @stock_id: a stock id + * + * Same as gedit_panel_add_item() but using an image from stock. + */ +void +gedit_panel_add_item_with_stock_icon (GeditPanel *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); + } + + gedit_panel_add_item (panel, item, name, icon); +} + +/** + * gedit_panel_remove_item: + * @panel: a #GeditPanel + * @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 +gedit_panel_remove_item (GeditPanel *panel, + GtkWidget *item) +{ + GeditPanelItem *data; + gint page_num; + + g_return_val_if_fail (GEDIT_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 = (GeditPanelItem *)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; +} + +/** + * gedit_panel_activate_item: + * @panel: a #GeditPanel + * @item: the item to be activated + * + * Switches to the page that contains @item. + * + * Returns: TRUE if it was activated + */ +gboolean +gedit_panel_activate_item (GeditPanel *panel, + GtkWidget *item) +{ + gint page_num; + + g_return_val_if_fail (GEDIT_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; +} + +/** + * gedit_panel_item_is_active: + * @panel: a #GeditPanel + * @item: a widget contained in #GeditPanel + * + * Wheter @item is the one current active in @panel + * + * Returns: TRUE if the widget is active + */ +gboolean +gedit_panel_item_is_active (GeditPanel *panel, + GtkWidget *item) +{ + gint cur_page; + gint page_num; + + g_return_val_if_fail (GEDIT_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); +} + +/** + * gedit_panel_get_orientation: + * @panel: a #GeditPanel + * + * Gets the orientation of the @panel. + * + * Returns: the #GtkOrientation of #GeditPanel + */ +GtkOrientation +gedit_panel_get_orientation (GeditPanel *panel) +{ + g_return_val_if_fail (GEDIT_IS_PANEL (panel), GTK_ORIENTATION_VERTICAL); + + return panel->priv->orientation; +} + +/** + * gedit_panel_get_n_items: + * @panel: a #GeditPanel + * + * Gets the number of items in a @panel. + * + * Returns: the number of items contained in #GeditPanel + */ +gint +gedit_panel_get_n_items (GeditPanel *panel) +{ + g_return_val_if_fail (GEDIT_IS_PANEL (panel), -1); + + return gtk_notebook_get_n_pages (GTK_NOTEBOOK (panel->priv->notebook)); +} + +gint +_gedit_panel_get_active_item_id (GeditPanel *panel) +{ + gint cur_page; + GtkWidget *item; + GeditPanelItem *data; + + g_return_val_if_fail (GEDIT_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 = (GeditPanelItem *)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 +_gedit_panel_set_active_item_by_id (GeditPanel *panel, + gint id) +{ + gint n, i; + + g_return_if_fail (GEDIT_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; + GeditPanelItem *data; + + item = gtk_notebook_get_nth_page ( + GTK_NOTEBOOK (panel->priv->notebook), i); + + data = (GeditPanelItem *)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/gedit/gedit-panel.h b/gedit/gedit-panel.h new file mode 100755 index 00000000..87b0536c --- /dev/null +++ b/gedit/gedit-panel.h @@ -0,0 +1,130 @@ +/* + * gedit-panel.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PANEL_H__ +#define __GEDIT_PANEL_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_PANEL (gedit_panel_get_type()) +#define GEDIT_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PANEL, GeditPanel)) +#define GEDIT_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_PANEL, GeditPanelClass)) +#define GEDIT_IS_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_PANEL)) +#define GEDIT_IS_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PANEL)) +#define GEDIT_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_PANEL, GeditPanelClass)) + +/* Private structure type */ +typedef struct _GeditPanelPrivate GeditPanelPrivate; + +/* + * Main object structure + */ +typedef struct _GeditPanel GeditPanel; + +struct _GeditPanel +{ + GtkVBox vbox; + + /*< private > */ + GeditPanelPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditPanelClass GeditPanelClass; + +struct _GeditPanelClass +{ + GtkVBoxClass parent_class; + + void (* item_added) (GeditPanel *panel, + GtkWidget *item); + void (* item_removed) (GeditPanel *panel, + GtkWidget *item); + + /* Keybinding signals */ + void (* close) (GeditPanel *panel); + void (* focus_document) (GeditPanel *panel); + + /* Padding for future expansion */ + void (*_gedit_reserved1) (void); + void (*_gedit_reserved2) (void); + void (*_gedit_reserved3) (void); + void (*_gedit_reserved4) (void); +}; + +/* + * Public methods + */ +GType gedit_panel_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_panel_new (GtkOrientation orientation); + +void gedit_panel_add_item (GeditPanel *panel, + GtkWidget *item, + const gchar *name, + GtkWidget *image); + +void gedit_panel_add_item_with_stock_icon (GeditPanel *panel, + GtkWidget *item, + const gchar *name, + const gchar *stock_id); + +gboolean gedit_panel_remove_item (GeditPanel *panel, + GtkWidget *item); + +gboolean gedit_panel_activate_item (GeditPanel *panel, + GtkWidget *item); + +gboolean gedit_panel_item_is_active (GeditPanel *panel, + GtkWidget *item); + +GtkOrientation gedit_panel_get_orientation (GeditPanel *panel); + +gint gedit_panel_get_n_items (GeditPanel *panel); + + +/* + * Non exported functions + */ +gint _gedit_panel_get_active_item_id (GeditPanel *panel); + +void _gedit_panel_set_active_item_by_id (GeditPanel *panel, + gint id); + +G_END_DECLS + +#endif /* __GEDIT_PANEL_H__ */ diff --git a/gedit/gedit-plugin-info-priv.h b/gedit/gedit-plugin-info-priv.h new file mode 100755 index 00000000..247b51c5 --- /dev/null +++ b/gedit/gedit-plugin-info-priv.h @@ -0,0 +1,68 @@ +/* + * gedit-plugin-info-priv.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2007. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PLUGIN_INFO_PRIV_H__ +#define __GEDIT_PLUGIN_INFO_PRIV_H__ + +#include "gedit-plugin-info.h" +#include "gedit-plugin.h" + +struct _GeditPluginInfo +{ + gint refcount; + + GeditPlugin *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; +}; + +GeditPluginInfo *_gedit_plugin_info_new (const gchar *file); +void _gedit_plugin_info_ref (GeditPluginInfo *info); +void _gedit_plugin_info_unref (GeditPluginInfo *info); + + +#endif /* __GEDIT_PLUGIN_INFO_PRIV_H__ */ diff --git a/gedit/gedit-plugin-info.c b/gedit/gedit-plugin-info.c new file mode 100755 index 00000000..31411c42 --- /dev/null +++ b/gedit/gedit-plugin-info.c @@ -0,0 +1,394 @@ +/* + * gedit-plugin-info.c + * This file is part of gedit + * + * 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 gedit Team, 2002-2007. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-plugin-info.h" +#include "gedit-plugin-info-priv.h" +#include "gedit-debug.h" +#include "gedit-plugin.h" + +void +_gedit_plugin_info_ref (GeditPluginInfo *info) +{ + g_atomic_int_inc (&info->refcount); +} + +static GeditPluginInfo * +gedit_plugin_info_copy (GeditPluginInfo *info) +{ + _gedit_plugin_info_ref (info); + return info; +} + +void +_gedit_plugin_info_unref (GeditPluginInfo *info) +{ + if (!g_atomic_int_dec_and_test (&info->refcount)) + return; + + if (info->plugin != NULL) + { + gedit_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); +} + +/** + * gedit_plugin_info_get_type: + * + * Retrieves the #GType object which is associated with the #GeditPluginInfo + * class. + * + * Return value: the GType associated with #GeditPluginInfo. + **/ +GType +gedit_plugin_info_get_type (void) +{ + static GType the_type = 0; + + if (G_UNLIKELY (!the_type)) + the_type = g_boxed_type_register_static ( + "GeditPluginInfo", + (GBoxedCopyFunc) gedit_plugin_info_copy, + (GBoxedFreeFunc) _gedit_plugin_info_unref); + + return the_type; +} + +/** + * gedit_plugin_info_new: + * @filename: the filename where to read the plugin information + * + * Creates a new #GeditPluginInfo from a file on the disk. + * + * Return value: a newly created #GeditPluginInfo. + */ +GeditPluginInfo * +_gedit_plugin_info_new (const gchar *file) +{ + GeditPluginInfo *info; + GKeyFile *plugin_file = NULL; + gchar *str; + + g_return_val_if_fail (file != NULL, NULL); + + gedit_debug_message (DEBUG_PLUGINS, "Loading plugin: %s", file); + + info = g_new0 (GeditPluginInfo, 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, + "Gedit Plugin", + "IAge", + NULL)) + { + gedit_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, + "Gedit Plugin", + "IAge", + NULL) != 2) + { + gedit_debug_message (DEBUG_PLUGINS, + "Wrong IAge in file: %s", file); + goto error; + } + + /* Get module name */ + str = g_key_file_get_string (plugin_file, + "Gedit 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, + "Gedit Plugin", + "Depends", + NULL, + NULL); + if (info->dependencies == NULL) + { + gedit_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, + "Gedit 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, + "Gedit 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, + "Gedit Plugin", + "Description", + NULL, NULL); + if (str) + info->desc = str; + else + gedit_debug_message (DEBUG_PLUGINS, "Could not find 'Description' in %s", file); + + /* Get Icon */ + str = g_key_file_get_locale_string (plugin_file, + "Gedit Plugin", + "Icon", + NULL, NULL); + if (str) + info->icon_name = str; + else + gedit_debug_message (DEBUG_PLUGINS, "Could not find 'Icon' in %s, using 'gedit-plugin'", file); + + + /* Get Authors */ + info->authors = g_key_file_get_string_list (plugin_file, + "Gedit Plugin", + "Authors", + NULL, + NULL); + if (info->authors == NULL) + gedit_debug_message (DEBUG_PLUGINS, "Could not find 'Authors' in %s", file); + + + /* Get Copyright */ + str = g_key_file_get_string (plugin_file, + "Gedit Plugin", + "Copyright", + NULL); + if (str) + info->copyright = str; + else + gedit_debug_message (DEBUG_PLUGINS, "Could not find 'Copyright' in %s", file); + + /* Get Website */ + str = g_key_file_get_string (plugin_file, + "Gedit Plugin", + "Website", + NULL); + if (str) + info->website = str; + else + gedit_debug_message (DEBUG_PLUGINS, "Could not find 'Website' in %s", file); + + /* Get Version */ + str = g_key_file_get_string (plugin_file, + "Gedit Plugin", + "Version", + NULL); + if (str) + info->version = str; + else + gedit_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 +gedit_plugin_info_is_active (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, FALSE); + + return info->available && info->plugin != NULL; +} + +gboolean +gedit_plugin_info_is_available (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, FALSE); + + return info->available != FALSE; +} + +gboolean +gedit_plugin_info_is_configurable (GeditPluginInfo *info) +{ + gedit_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 gedit_plugin_is_configurable (info->plugin); +} + +const gchar * +gedit_plugin_info_get_module_name (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->module_name; +} + +const gchar * +gedit_plugin_info_get_name (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->name; +} + +const gchar * +gedit_plugin_info_get_description (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->desc; +} + +const gchar * +gedit_plugin_info_get_icon_name (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + /* use the gedit-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 "gedit-plugin"; +} + +const gchar ** +gedit_plugin_info_get_authors (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, (const gchar **)NULL); + + return (const gchar **) info->authors; +} + +const gchar * +gedit_plugin_info_get_website (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->website; +} + +const gchar * +gedit_plugin_info_get_copyright (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->copyright; +} + +const gchar * +gedit_plugin_info_get_version (GeditPluginInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->version; +} diff --git a/gedit/gedit-plugin-info.h b/gedit/gedit-plugin-info.h new file mode 100755 index 00000000..ac9e2fa7 --- /dev/null +++ b/gedit/gedit-plugin-info.h @@ -0,0 +1,63 @@ +/* + * gedit-plugin-info.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2007. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PLUGIN_INFO_H__ +#define __GEDIT_PLUGIN_INFO_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_PLUGIN_INFO (gedit_plugin_info_get_type ()) +#define GEDIT_PLUGIN_INFO(obj) ((GeditPluginInfo *) (obj)) + +typedef struct _GeditPluginInfo GeditPluginInfo; + +GType gedit_plugin_info_get_type (void) G_GNUC_CONST; + +gboolean gedit_plugin_info_is_active (GeditPluginInfo *info); +gboolean gedit_plugin_info_is_available (GeditPluginInfo *info); +gboolean gedit_plugin_info_is_configurable (GeditPluginInfo *info); + +const gchar *gedit_plugin_info_get_module_name (GeditPluginInfo *info); + +const gchar *gedit_plugin_info_get_name (GeditPluginInfo *info); +const gchar *gedit_plugin_info_get_description (GeditPluginInfo *info); +const gchar *gedit_plugin_info_get_icon_name (GeditPluginInfo *info); +const gchar **gedit_plugin_info_get_authors (GeditPluginInfo *info); +const gchar *gedit_plugin_info_get_website (GeditPluginInfo *info); +const gchar *gedit_plugin_info_get_copyright (GeditPluginInfo *info); +const gchar *gedit_plugin_info_get_version (GeditPluginInfo *info); + +G_END_DECLS + +#endif /* __GEDIT_PLUGIN_INFO_H__ */ + diff --git a/gedit/gedit-plugin-loader.c b/gedit/gedit-plugin-loader.c new file mode 100755 index 00000000..2dccde49 --- /dev/null +++ b/gedit/gedit-plugin-loader.c @@ -0,0 +1,131 @@ +/* + * gedit-plugin-loader.c + * This file is part of gedit + * + * 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 "gedit-plugin-loader.h" + +static void +gedit_plugin_loader_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (G_UNLIKELY (!initialized)) + { + /* create interface signals here. */ + initialized = TRUE; + } +} + +GType +gedit_plugin_loader_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + { + static const GTypeInfo info = + { + sizeof (GeditPluginLoaderInterface), + gedit_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, "GeditPluginLoader", &info, 0); + } + + return type; +} + +const gchar * +gedit_plugin_loader_type_get_id (GType type) +{ + GTypeClass *klass; + GeditPluginLoaderInterface *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, GEDIT_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 (); +} + +GeditPlugin * +gedit_plugin_loader_load (GeditPluginLoader *loader, + GeditPluginInfo *info, + const gchar *path) +{ + GeditPluginLoaderInterface *iface; + + g_return_val_if_fail (GEDIT_IS_PLUGIN_LOADER (loader), NULL); + + iface = GEDIT_PLUGIN_LOADER_GET_INTERFACE (loader); + g_return_val_if_fail (iface->load != NULL, NULL); + + return iface->load (loader, info, path); +} + +void +gedit_plugin_loader_unload (GeditPluginLoader *loader, + GeditPluginInfo *info) +{ + GeditPluginLoaderInterface *iface; + + g_return_if_fail (GEDIT_IS_PLUGIN_LOADER (loader)); + + iface = GEDIT_PLUGIN_LOADER_GET_INTERFACE (loader); + g_return_if_fail (iface->unload != NULL); + + iface->unload (loader, info); +} + +void +gedit_plugin_loader_garbage_collect (GeditPluginLoader *loader) +{ + GeditPluginLoaderInterface *iface; + + g_return_if_fail (GEDIT_IS_PLUGIN_LOADER (loader)); + + iface = GEDIT_PLUGIN_LOADER_GET_INTERFACE (loader); + + if (iface->garbage_collect != NULL) + iface->garbage_collect (loader); +} diff --git a/gedit/gedit-plugin-loader.h b/gedit/gedit-plugin-loader.h new file mode 100755 index 00000000..a064e5ec --- /dev/null +++ b/gedit/gedit-plugin-loader.h @@ -0,0 +1,106 @@ +/* + * gedit-plugin-loader.h + * This file is part of gedit + * + * 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 __GEDIT_PLUGIN_LOADER_H__ +#define __GEDIT_PLUGIN_LOADER_H__ + +#include <glib-object.h> +#include <gedit/gedit-plugin.h> +#include <gedit/gedit-plugin-info.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_PLUGIN_LOADER (gedit_plugin_loader_get_type ()) +#define GEDIT_PLUGIN_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_PLUGIN_LOADER, GeditPluginLoader)) +#define GEDIT_IS_PLUGIN_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_PLUGIN_LOADER)) +#define GEDIT_PLUGIN_LOADER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GEDIT_TYPE_PLUGIN_LOADER, GeditPluginLoaderInterface)) + +typedef struct _GeditPluginLoader GeditPluginLoader; /* dummy object */ +typedef struct _GeditPluginLoaderInterface GeditPluginLoaderInterface; + +struct _GeditPluginLoaderInterface { + GTypeInterface parent; + + const gchar *(*get_id) (void); + + GeditPlugin *(*load) (GeditPluginLoader *loader, + GeditPluginInfo *info, + const gchar *path); + + void (*unload) (GeditPluginLoader *loader, + GeditPluginInfo *info); + + void (*garbage_collect) (GeditPluginLoader *loader); +}; + +GType gedit_plugin_loader_get_type (void); + +const gchar *gedit_plugin_loader_type_get_id (GType type); +GeditPlugin *gedit_plugin_loader_load (GeditPluginLoader *loader, + GeditPluginInfo *info, + const gchar *path); +void gedit_plugin_loader_unload (GeditPluginLoader *loader, + GeditPluginInfo *info); +void gedit_plugin_loader_garbage_collect (GeditPluginLoader *loader); + +/** + * GEDIT_PLUGIN_LOADER_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init): + * + * Utility macro used to register interfaces for gobject types in plugin loaders. + */ +#define GEDIT_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); + +/** + * GEDIT_PLUGIN_LOADER_REGISTER_TYPE(PluginLoaderName, plugin_loader_name, PARENT_TYPE, loader_interface_init): + * + * Utility macro used to register plugin loaders. + */ +#define GEDIT_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, \ + GEDIT_PLUGIN_LOADER_IMPLEMENT_INTERFACE(GEDIT_TYPE_PLUGIN_LOADER, loader_iface_init)); \ + \ + \ +G_MODULE_EXPORT GType \ +register_gedit_plugin_loader (GTypeModule *type_module) \ +{ \ + plugin_loader_name##_register_type (type_module); \ + \ + return plugin_loader_name##_get_type(); \ +} + +G_END_DECLS + +#endif /* __GEDIT_PLUGIN_LOADER_H__ */ diff --git a/gedit/gedit-plugin-manager.c b/gedit/gedit-plugin-manager.c new file mode 100755 index 00000000..b72abad9 --- /dev/null +++ b/gedit/gedit-plugin-manager.c @@ -0,0 +1,889 @@ +/* + * gedit-plugin-manager.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2006. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-plugin-manager.h" +#include "gedit-utils.h" +#include "gedit-plugins-engine.h" +#include "gedit-plugin.h" +#include "gedit-debug.h" + +enum +{ + ACTIVE_COLUMN, + AVAILABLE_COLUMN, + INFO_COLUMN, + N_COLUMNS +}; + +#define PLUGIN_MANAGER_NAME_TITLE _("Plugin") +#define PLUGIN_MANAGER_ACTIVE_TITLE _("Enabled") + +#define GEDIT_PLUGIN_MANAGER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_PLUGIN_MANAGER, GeditPluginManagerPrivate)) + +struct _GeditPluginManagerPrivate +{ + GtkWidget *tree; + + GtkWidget *about_button; + GtkWidget *configure_button; + + GeditPluginsEngine *engine; + + GtkWidget *about; + + GtkWidget *popup_menu; +}; + +G_DEFINE_TYPE(GeditPluginManager, gedit_plugin_manager, GTK_TYPE_VBOX) + +static GeditPluginInfo *plugin_manager_get_selected_plugin (GeditPluginManager *pm); +static void plugin_manager_toggle_active (GeditPluginManager *pm, GtkTreeIter *iter, GtkTreeModel *model); +static void gedit_plugin_manager_finalize (GObject *object); + +static void +gedit_plugin_manager_class_init (GeditPluginManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_plugin_manager_finalize; + + g_type_class_add_private (object_class, sizeof (GeditPluginManagerPrivate)); +} + +static void +about_button_cb (GtkWidget *button, + GeditPluginManager *pm) +{ + GeditPluginInfo *info; + + gedit_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", gedit_plugin_info_get_name (info), + "copyright", gedit_plugin_info_get_copyright (info), + "authors", gedit_plugin_info_get_authors (info), + "comments", gedit_plugin_info_get_description (info), + "website", gedit_plugin_info_get_website (info), + "logo-icon-name", gedit_plugin_info_get_icon_name (info), + "version", gedit_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, + GeditPluginManager *pm) +{ + GeditPluginInfo *info; + GtkWindow *toplevel; + + gedit_debug (DEBUG_PLUGINS); + + info = plugin_manager_get_selected_plugin (pm); + + g_return_if_fail (info != NULL); + + gedit_debug_message (DEBUG_PLUGINS, "Configuring: %s\n", + gedit_plugin_info_get_name (info)); + + toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET(pm))); + + gedit_plugins_engine_configure_plugin (pm->priv->engine, + info, toplevel); + + gedit_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) +{ + GeditPluginInfo *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", + gedit_plugin_info_get_name (info), + gedit_plugin_info_get_description (info)); + g_object_set (G_OBJECT (cell), + "markup", text, + "sensitive", gedit_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) +{ + GeditPluginInfo *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", gedit_plugin_info_get_icon_name (info), + "sensitive", gedit_plugin_info_is_available (info), + NULL); +} + + +static void +active_toggled_cb (GtkCellRendererToggle *cell, + gchar *path_str, + GeditPluginManager *pm) +{ + GtkTreeIter iter; + GtkTreePath *path; + GtkTreeModel *model; + + gedit_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) +{ + GeditPluginManager *pm = data; + GeditPluginInfo *info; + + gedit_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) && + gedit_plugin_info_is_configurable (info)); +} + +static void +row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer data) +{ + GeditPluginManager *pm = data; + GtkTreeIter iter; + GtkTreeModel *model; + + gedit_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 (GeditPluginManager *pm) +{ + const GList *plugins; + GtkListStore *model; + GtkTreeIter iter; + + gedit_debug (DEBUG_PLUGINS); + + plugins = gedit_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) + { + GeditPluginInfo *info; + info = (GeditPluginInfo *)plugins->data; + + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, + ACTIVE_COLUMN, gedit_plugin_info_is_active (info), + AVAILABLE_COLUMN, gedit_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; + GeditPluginInfo* 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), + gedit_plugin_info_is_configurable (info)); + } +} + +static gboolean +plugin_manager_set_active (GeditPluginManager *pm, + GtkTreeIter *iter, + GtkTreeModel *model, + gboolean active) +{ + GeditPluginInfo *info; + gboolean res = TRUE; + + gedit_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 (!gedit_plugins_engine_activate_plugin (pm->priv->engine, info)) { + gedit_debug_message (DEBUG_PLUGINS, "Could not activate %s.\n", + gedit_plugin_info_get_name (info)); + + res = FALSE; + } + } + else + { + /* deactivate the plugin */ + if (!gedit_plugins_engine_deactivate_plugin (pm->priv->engine, info)) { + gedit_debug_message (DEBUG_PLUGINS, "Could not deactivate %s.\n", + gedit_plugin_info_get_name (info)); + + res = FALSE; + } + } + + return res; +} + +static void +plugin_manager_toggle_active (GeditPluginManager *pm, + GtkTreeIter *iter, + GtkTreeModel *model) +{ + gboolean active; + + gedit_debug (DEBUG_PLUGINS); + + gtk_tree_model_get (model, iter, ACTIVE_COLUMN, &active, -1); + + active ^= 1; + + plugin_manager_set_active (pm, iter, model, active); +} + +static GeditPluginInfo * +plugin_manager_get_selected_plugin (GeditPluginManager *pm) +{ + GeditPluginInfo *info = NULL; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreeSelection *selection; + + gedit_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 (GeditPluginManager *pm, + gboolean active) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + gedit_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) +{ + GeditPluginInfo *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 (gedit_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, + GeditPluginManager *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, + GeditPluginManager *pm) +{ + plugin_manager_set_active_all (pm, TRUE); +} + +static void +disable_all_menu_cb (GtkMenu *menu, + GeditPluginManager *pm) +{ + plugin_manager_set_active_all (pm, FALSE); +} + +static GtkWidget * +create_tree_popup_menu (GeditPluginManager *pm) +{ + GtkWidget *menu; + GtkWidget *item; + GtkWidget *image; + GeditPluginInfo *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, gedit_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, gedit_plugin_info_is_available (info)); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), + gedit_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 (GeditPluginManager *pm, + GtkMenu *menu) +{ + pm->priv->popup_menu = NULL; +} + +static void +show_tree_popup_menu (GtkTreeView *tree, + GeditPluginManager *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, + gedit_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, + GeditPluginManager *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.mate.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, + GeditPluginManager *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) +{ + GeditPluginInfo *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 (gedit_plugin_info_get_name (info1), + gedit_plugin_info_get_name (info2)); +} + +static void +plugin_manager_construct_tree (GeditPluginManager *pm) +{ + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkListStore *model; + + gedit_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 (GeditPluginsEngine *engine, + GeditPluginInfo *info, + GeditPluginManager *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! */ + GeditPluginInfo *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 + { + GeditPluginInfo *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 ("GeditPluginManager: plugin '%s' not found in the tree model", + gedit_plugin_info_get_name (info)); + return; + } + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, ACTIVE_COLUMN, gedit_plugin_info_is_active (info), -1); +} + +static void +gedit_plugin_manager_init (GeditPluginManager *pm) +{ + GtkWidget *label; + GtkWidget *viewport; + GtkWidget *hbuttonbox; + + gedit_debug (DEBUG_PLUGINS); + + pm->priv = GEDIT_PLUGIN_MANAGER_GET_PRIVATE (pm); + + /* + * Always we create the manager, firstly we rescan the plugins directory + */ + gedit_plugins_engine_rescan_plugins (gedit_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 = gedit_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 = gedit_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 = gedit_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 (gedit_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 +gedit_plugin_manager_finalize (GObject *object) +{ + GeditPluginManager *pm = GEDIT_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 (gedit_plugin_manager_parent_class)->finalize (object); + +} + +GtkWidget *gedit_plugin_manager_new (void) +{ + return g_object_new (GEDIT_TYPE_PLUGIN_MANAGER,0); +} diff --git a/gedit/gedit-plugin-manager.h b/gedit/gedit-plugin-manager.h new file mode 100755 index 00000000..53392ba5 --- /dev/null +++ b/gedit/gedit-plugin-manager.h @@ -0,0 +1,83 @@ +/* + * gedit-plugin-manager.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PLUGIN_MANAGER_H__ +#define __GEDIT_PLUGIN_MANAGER_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_PLUGIN_MANAGER (gedit_plugin_manager_get_type()) +#define GEDIT_PLUGIN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PLUGIN_MANAGER, GeditPluginManager)) +#define GEDIT_PLUGIN_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_PLUGIN_MANAGER, GeditPluginManagerClass)) +#define GEDIT_IS_PLUGIN_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_PLUGIN_MANAGER)) +#define GEDIT_IS_PLUGIN_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PLUGIN_MANAGER)) +#define GEDIT_PLUGIN_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_PLUGIN_MANAGER, GeditPluginManagerClass)) + +/* Private structure type */ +typedef struct _GeditPluginManagerPrivate GeditPluginManagerPrivate; + +/* + * Main object structure + */ +typedef struct _GeditPluginManager GeditPluginManager; + +struct _GeditPluginManager +{ + GtkVBox vbox; + + /*< private > */ + GeditPluginManagerPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditPluginManagerClass GeditPluginManagerClass; + +struct _GeditPluginManagerClass +{ + GtkVBoxClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_plugin_manager_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_plugin_manager_new (void); + +G_END_DECLS + +#endif /* __GEDIT_PLUGIN_MANAGER_H__ */ diff --git a/gedit/gedit-plugin.c b/gedit/gedit-plugin.c new file mode 100755 index 00000000..2b00e3fd --- /dev/null +++ b/gedit/gedit-plugin.c @@ -0,0 +1,334 @@ +/* + * gedit-plugin.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-plugin.h" +#include "gedit-dirs.h" + +/* properties */ +enum { + PROP_0, + PROP_INSTALL_DIR, + PROP_DATA_DIR_NAME, + PROP_DATA_DIR +}; + +typedef struct _GeditPluginPrivate GeditPluginPrivate; + +struct _GeditPluginPrivate +{ + gchar *install_dir; + gchar *data_dir_name; +}; + +#define GEDIT_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_PLUGIN, GeditPluginPrivate)) + +G_DEFINE_TYPE(GeditPlugin, gedit_plugin, G_TYPE_OBJECT) + +static void +dummy (GeditPlugin *plugin, GeditWindow *window) +{ + /* Empty */ +} + +static GtkWidget * +create_configure_dialog (GeditPlugin *plugin) +{ + return NULL; +} + +static gboolean +is_configurable (GeditPlugin *plugin) +{ + return (GEDIT_PLUGIN_GET_CLASS (plugin)->create_configure_dialog != + create_configure_dialog); +} + +static void +gedit_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_INSTALL_DIR: + g_value_take_string (value, gedit_plugin_get_install_dir (GEDIT_PLUGIN (object))); + break; + case PROP_DATA_DIR: + g_value_take_string (value, gedit_plugin_get_data_dir (GEDIT_PLUGIN (object))); + break; + default: + g_return_if_reached (); + } +} + +static void +gedit_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditPluginPrivate *priv = GEDIT_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 +gedit_plugin_finalize (GObject *object) +{ + GeditPluginPrivate *priv = GEDIT_PLUGIN_GET_PRIVATE (object); + + g_free (priv->install_dir); + g_free (priv->data_dir_name); + + G_OBJECT_CLASS (gedit_plugin_parent_class)->finalize (object); +} + +static void +gedit_plugin_class_init (GeditPluginClass *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 = gedit_plugin_get_property; + object_class->set_property = gedit_plugin_set_property; + object_class->finalize = gedit_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 (GeditPluginPrivate)); +} + +static void +gedit_plugin_init (GeditPlugin *plugin) +{ + /* Empty */ +} + +/** + * gedit_plugin_get_install_dir: + * @plugin: a #GeditPlugin + * + * 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 * +gedit_plugin_get_install_dir (GeditPlugin *plugin) +{ + g_return_val_if_fail (GEDIT_IS_PLUGIN (plugin), NULL); + + return g_strdup (GEDIT_PLUGIN_GET_PRIVATE (plugin)->install_dir); +} + +/** + * gedit_plugin_get_data_dir: + * @plugin: a #GeditPlugin + * + * 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 * +gedit_plugin_get_data_dir (GeditPlugin *plugin) +{ + GeditPluginPrivate *priv; + gchar *gedit_lib_dir; + gchar *data_dir; + + g_return_val_if_fail (GEDIT_IS_PLUGIN (plugin), NULL); + + priv = GEDIT_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 gedit_data_dir, + * so it's under $prefix/share/gedit-2/plugins/data_dir_name + * where data_dir_name usually it's the name of the plugin + */ + gedit_lib_dir = gedit_dirs_get_gedit_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, gedit_lib_dir)) + { + gchar *gedit_data_dir; + + gedit_data_dir = gedit_dirs_get_gedit_data_dir (); + + data_dir = g_build_filename (gedit_data_dir, + "plugins", + priv->data_dir_name, + NULL); + + g_free (gedit_data_dir); + } + else + { + data_dir = g_build_filename (priv->install_dir, + priv->data_dir_name, + NULL); + } + + g_free (gedit_lib_dir); + + return data_dir; +} + +/** + * gedit_plugin_activate: + * @plugin: a #GeditPlugin + * @window: a #GeditWindow + * + * Activates the plugin. + */ +void +gedit_plugin_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_PLUGIN (plugin)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + GEDIT_PLUGIN_GET_CLASS (plugin)->activate (plugin, window); +} + +/** + * gedit_plugin_deactivate: + * @plugin: a #GeditPlugin + * @window: a #GeditWindow + * + * Deactivates the plugin. + */ +void +gedit_plugin_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_PLUGIN (plugin)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + GEDIT_PLUGIN_GET_CLASS (plugin)->deactivate (plugin, window); +} + +/** + * gedit_plugin_update_ui: + * @plugin: a #GeditPlugin + * @window: a #GeditWindow + * + * Triggers an update of the user interface to take into account state changes + * caused by the plugin. + */ +void +gedit_plugin_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_PLUGIN (plugin)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + GEDIT_PLUGIN_GET_CLASS (plugin)->update_ui (plugin, window); +} + +/** + * gedit_plugin_is_configurable: + * @plugin: a #GeditPlugin + * + * Whether the plugin is configurable. + * + * Returns: TRUE if the plugin is configurable: + */ +gboolean +gedit_plugin_is_configurable (GeditPlugin *plugin) +{ + g_return_val_if_fail (GEDIT_IS_PLUGIN (plugin), FALSE); + + return GEDIT_PLUGIN_GET_CLASS (plugin)->is_configurable (plugin); +} + +/** + * gedit_plugin_create_configure_dialog: + * @plugin: a #GeditPlugin + * + * Creates the configure dialog widget for the plugin. + * + * Returns: the configure dialog widget for the plugin. + */ +GtkWidget * +gedit_plugin_create_configure_dialog (GeditPlugin *plugin) +{ + g_return_val_if_fail (GEDIT_IS_PLUGIN (plugin), NULL); + + return GEDIT_PLUGIN_GET_CLASS (plugin)->create_configure_dialog (plugin); +} diff --git a/gedit/gedit-plugin.h b/gedit/gedit-plugin.h new file mode 100755 index 00000000..8cf41d80 --- /dev/null +++ b/gedit/gedit-plugin.h @@ -0,0 +1,240 @@ +/* + * gedit-plugin.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PLUGIN_H__ +#define __GEDIT_PLUGIN_H__ + +#include <glib-object.h> + +#include <gedit/gedit-window.h> +#include <gedit/gedit-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 GEDIT_TYPE_PLUGIN (gedit_plugin_get_type()) +#define GEDIT_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PLUGIN, GeditPlugin)) +#define GEDIT_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_PLUGIN, GeditPluginClass)) +#define GEDIT_IS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_PLUGIN)) +#define GEDIT_IS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PLUGIN)) +#define GEDIT_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_PLUGIN, GeditPluginClass)) + +/* + * Main object structure + */ +typedef struct _GeditPlugin GeditPlugin; + +struct _GeditPlugin +{ + GObject parent; +}; + +/* + * Class definition + */ +typedef struct _GeditPluginClass GeditPluginClass; + +struct _GeditPluginClass +{ + GObjectClass parent_class; + + /* Virtual public methods */ + + void (*activate) (GeditPlugin *plugin, + GeditWindow *window); + void (*deactivate) (GeditPlugin *plugin, + GeditWindow *window); + + void (*update_ui) (GeditPlugin *plugin, + GeditWindow *window); + + GtkWidget *(*create_configure_dialog) + (GeditPlugin *plugin); + + /* Plugins should not override this, it's handled automatically by + the GeditPluginClass */ + gboolean (*is_configurable) + (GeditPlugin *plugin); + + /* Padding for future expansion */ + void (*_gedit_reserved1) (void); + void (*_gedit_reserved2) (void); + void (*_gedit_reserved3) (void); + void (*_gedit_reserved4) (void); +}; + +/* + * Public methods + */ +GType gedit_plugin_get_type (void) G_GNUC_CONST; + +gchar *gedit_plugin_get_install_dir (GeditPlugin *plugin); +gchar *gedit_plugin_get_data_dir (GeditPlugin *plugin); + +void gedit_plugin_activate (GeditPlugin *plugin, + GeditWindow *window); +void gedit_plugin_deactivate (GeditPlugin *plugin, + GeditWindow *window); + +void gedit_plugin_update_ui (GeditPlugin *plugin, + GeditWindow *window); + +gboolean gedit_plugin_is_configurable (GeditPlugin *plugin); +GtkWidget *gedit_plugin_create_configure_dialog + (GeditPlugin *plugin); + +/** + * GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, CODE): + * + * Utility macro used to register plugins with additional code. + */ +#define GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, CODE) \ + G_DEFINE_DYNAMIC_TYPE_EXTENDED (PluginName, \ + plugin_name, \ + GEDIT_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_gedit_plugin (GTypeModule *type_module) \ +{ \ + plugin_name##_register_type (type_module); \ + \ + return plugin_name##_get_type(); \ +} + +/** + * GEDIT_PLUGIN_REGISTER_TYPE(PluginName, plugin_name): + * + * Utility macro used to register plugins. + */ +#define GEDIT_PLUGIN_REGISTER_TYPE(PluginName, plugin_name) \ + GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE(PluginName, plugin_name, ;) + +/** + * GEDIT_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 GEDIT_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; \ +} + + +/** + * GEDIT_PLUGIN_DEFINE_TYPE(ObjectName, object_name, PARENT_TYPE): + * + * Utility macro used to register gobject types in plugins. + * + * Deprecated: use G_DEFINE_DYNAMIC instead + */ +#define GEDIT_PLUGIN_DEFINE_TYPE(ObjectName, object_name, PARENT_TYPE) \ + GEDIT_PLUGIN_DEFINE_TYPE_WITH_CODE(ObjectName, object_name, PARENT_TYPE, ;) + +/** + * GEDIT_PLUGIN_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init): + * + * Utility macro used to register interfaces for gobject types in plugins. + */ +#define GEDIT_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 /* __GEDIT_PLUGIN_H__ */ diff --git a/gedit/gedit-plugins-engine.c b/gedit/gedit-plugins-engine.c new file mode 100755 index 00000000..1a34692b --- /dev/null +++ b/gedit/gedit-plugins-engine.c @@ -0,0 +1,861 @@ +/* + * gedit-plugins-engine.c + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-plugins-engine.h" +#include "gedit-plugin-info-priv.h" +#include "gedit-plugin.h" +#include "gedit-debug.h" +#include "gedit-app.h" +#include "gedit-prefs-manager.h" +#include "gedit-plugin-loader.h" +#include "gedit-object-module.h" +#include "gedit-dirs.h" + +#define GEDIT_PLUGINS_ENGINE_BASE_KEY "/apps/gedit-2/plugins" +#define GEDIT_PLUGINS_ENGINE_KEY GEDIT_PLUGINS_ENGINE_BASE_KEY "/active-plugins" + +#define PLUGIN_EXT ".gedit-plugin" +#define LOADER_EXT G_MODULE_SUFFIX + +typedef struct +{ + GeditPluginLoader *loader; + GeditObjectModule *module; +} LoaderInfo; + +/* Signals */ +enum +{ + ACTIVATE_PLUGIN, + DEACTIVATE_PLUGIN, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE(GeditPluginsEngine, gedit_plugins_engine, G_TYPE_OBJECT) + +struct _GeditPluginsEnginePrivate +{ + GList *plugin_list; + GHashTable *loaders; + + gboolean activate_from_prefs; +}; + +GeditPluginsEngine *default_engine = NULL; + +static void gedit_plugins_engine_activate_plugin_real (GeditPluginsEngine *engine, + GeditPluginInfo *info); +static void gedit_plugins_engine_deactivate_plugin_real (GeditPluginsEngine *engine, + GeditPluginInfo *info); + +typedef gboolean (*LoadDirCallback)(GeditPluginsEngine *engine, const gchar *filename, gpointer userdata); + +static gboolean +load_dir_real (GeditPluginsEngine *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); + + gedit_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 (GeditPluginsEngine *engine, + const gchar *filename, + gpointer userdata) +{ + GeditPluginInfo *info; + + info = _gedit_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 (gedit_plugins_engine_get_plugin_info (engine, gedit_plugin_info_get_module_name (info)) != NULL) + { + gedit_debug_message (DEBUG_PLUGINS, "Two or more plugins named '%s'. " + "Only the first will be considered.\n", + gedit_plugin_info_get_module_name (info)); + + _gedit_plugin_info_unref (info); + + return TRUE; + } + + engine->priv->plugin_list = g_list_prepend (engine->priv->plugin_list, info); + + gedit_debug_message (DEBUG_PLUGINS, "Plugin %s loaded", info->name); + return TRUE; +} + +static void +load_all_plugins (GeditPluginsEngine *engine) +{ + gchar *plugin_dir; + const gchar *pdirs_env = NULL; + + /* load user plugins */ + plugin_dir = gedit_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 ("GEDIT_PLUGINS_PATH"); + + gedit_debug_message (DEBUG_PLUGINS, "GEDIT_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 = gedit_dirs_get_gedit_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 (GeditPluginsEngine *engine, + const gchar *loader_id, + GeditObjectModule *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 +gedit_plugins_engine_init (GeditPluginsEngine *engine) +{ + gedit_debug (DEBUG_PLUGINS); + + if (!g_module_supported ()) + { + g_warning ("gedit is not able to initialize the plugins engine."); + return; + } + + engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, + GEDIT_TYPE_PLUGINS_ENGINE, + GeditPluginsEnginePrivate); + + 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) + gedit_plugin_loader_garbage_collect (info->loader); +} + +void +gedit_plugins_engine_garbage_collect (GeditPluginsEngine *engine) +{ + g_hash_table_foreach (engine->priv->loaders, + (GHFunc) loader_garbage_collect, + NULL); +} + +static void +gedit_plugins_engine_finalize (GObject *object) +{ + GeditPluginsEngine *engine = GEDIT_PLUGINS_ENGINE (object); + GList *item; + + gedit_debug (DEBUG_PLUGINS); + + /* Firs deactivate all plugins */ + for (item = engine->priv->plugin_list; item; item = item->next) + { + GeditPluginInfo *info = GEDIT_PLUGIN_INFO (item->data); + + if (gedit_plugin_info_is_active (info)) + gedit_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) + { + GeditPluginInfo *info = GEDIT_PLUGIN_INFO (item->data); + + _gedit_plugin_info_unref (info); + } + + g_list_free (engine->priv->plugin_list); + + G_OBJECT_CLASS (gedit_plugins_engine_parent_class)->finalize (object); +} + +static void +gedit_plugins_engine_class_init (GeditPluginsEngineClass *klass) +{ + GType the_type = G_TYPE_FROM_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_plugins_engine_finalize; + klass->activate_plugin = gedit_plugins_engine_activate_plugin_real; + klass->deactivate_plugin = gedit_plugins_engine_deactivate_plugin_real; + + signals[ACTIVATE_PLUGIN] = + g_signal_new ("activate-plugin", + the_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditPluginsEngineClass, activate_plugin), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + GEDIT_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 (GeditPluginsEngineClass, deactivate_plugin), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + GEDIT_TYPE_PLUGIN_INFO | G_SIGNAL_TYPE_STATIC_SCOPE); + + g_type_class_add_private (klass, sizeof (GeditPluginsEnginePrivate)); +} + +static gboolean +load_loader (GeditPluginsEngine *engine, + const gchar *filename, + gpointer data) +{ + GeditObjectModule *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 = gedit_object_module_new (base, + path, + "register_gedit_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 = gedit_object_module_get_object_type (module); + id = gedit_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 */ + GeditPluginLoader *loader; + loader = (GeditPluginLoader *)gedit_object_module_new_object (info->module, NULL); + + if (loader == NULL || !GEDIT_IS_PLUGIN_LOADER (loader)) + { + g_warning ("Loader object is not a valid GeditPluginLoader instance"); + + if (loader != NULL && G_IS_OBJECT (loader)) + g_object_unref (loader); + } + else + { + info->loader = loader; + } + } +} + +static GeditPluginLoader * +get_plugin_loader (GeditPluginsEngine *engine, GeditPluginInfo *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 = gedit_dirs_get_gedit_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; +} + +GeditPluginsEngine * +gedit_plugins_engine_get_default (void) +{ + if (default_engine != NULL) + return default_engine; + + default_engine = GEDIT_PLUGINS_ENGINE (g_object_new (GEDIT_TYPE_PLUGINS_ENGINE, NULL)); + g_object_add_weak_pointer (G_OBJECT (default_engine), + (gpointer) &default_engine); + return default_engine; +} + +const GList * +gedit_plugins_engine_get_plugin_list (GeditPluginsEngine *engine) +{ + gedit_debug (DEBUG_PLUGINS); + + return engine->priv->plugin_list; +} + +static gint +compare_plugin_info_and_name (GeditPluginInfo *info, + const gchar *module_name) +{ + return strcmp (gedit_plugin_info_get_module_name (info), module_name); +} + +GeditPluginInfo * +gedit_plugins_engine_get_plugin_info (GeditPluginsEngine *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 : (GeditPluginInfo *) l->data; +} + +static void +save_active_plugin_list (GeditPluginsEngine *engine) +{ + GSList *active_plugins = NULL; + GList *l; + + for (l = engine->priv->plugin_list; l != NULL; l = l->next) + { + GeditPluginInfo *info = (GeditPluginInfo *) l->data; + + if (gedit_plugin_info_is_active (info)) + { + active_plugins = g_slist_prepend (active_plugins, + (gpointer)gedit_plugin_info_get_module_name (info)); + } + } + + gedit_prefs_manager_set_active_plugins (active_plugins); + + g_slist_free (active_plugins); +} + +static gboolean +load_plugin (GeditPluginsEngine *engine, + GeditPluginInfo *info) +{ + GeditPluginLoader *loader; + gchar *path; + + if (gedit_plugin_info_is_active (info)) + return TRUE; + + if (!gedit_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 = gedit_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 +gedit_plugins_engine_activate_plugin_real (GeditPluginsEngine *engine, + GeditPluginInfo *info) +{ + const GList *wins; + + if (!load_plugin (engine, info)) + return; + + for (wins = gedit_app_get_windows (gedit_app_get_default ()); + wins != NULL; + wins = wins->next) + { + gedit_plugin_activate (info->plugin, GEDIT_WINDOW (wins->data)); + } +} + +gboolean +gedit_plugins_engine_activate_plugin (GeditPluginsEngine *engine, + GeditPluginInfo *info) +{ + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (info != NULL, FALSE); + + if (!gedit_plugin_info_is_available (info)) + return FALSE; + + if (gedit_plugin_info_is_active (info)) + return TRUE; + + g_signal_emit (engine, signals[ACTIVATE_PLUGIN], 0, info); + + if (gedit_plugin_info_is_active (info)) + save_active_plugin_list (engine); + + return gedit_plugin_info_is_active (info); +} + +static void +call_plugin_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + gedit_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 (gedit_window_get_ui_manager (window)); +} + +static void +gedit_plugins_engine_deactivate_plugin_real (GeditPluginsEngine *engine, + GeditPluginInfo *info) +{ + const GList *wins; + GeditPluginLoader *loader; + + if (!gedit_plugin_info_is_active (info) || + !gedit_plugin_info_is_available (info)) + return; + + for (wins = gedit_app_get_windows (gedit_app_get_default ()); + wins != NULL; + wins = wins->next) + { + call_plugin_deactivate (info->plugin, GEDIT_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); + + gedit_plugin_loader_garbage_collect (loader); + gedit_plugin_loader_unload (loader, info); + + info->plugin = NULL; +} + +gboolean +gedit_plugins_engine_deactivate_plugin (GeditPluginsEngine *engine, + GeditPluginInfo *info) +{ + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (info != NULL, FALSE); + + if (!gedit_plugin_info_is_active (info)) + return TRUE; + + g_signal_emit (engine, signals[DEACTIVATE_PLUGIN], 0, info); + if (!gedit_plugin_info_is_active (info)) + save_active_plugin_list (engine); + + return !gedit_plugin_info_is_active (info); +} + +void +gedit_plugins_engine_activate_plugins (GeditPluginsEngine *engine, + GeditWindow *window) +{ + GSList *active_plugins = NULL; + GList *pl; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (GEDIT_IS_PLUGINS_ENGINE (engine)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + /* the first time, we get the 'active' plugins from mateconf */ + if (engine->priv->activate_from_prefs) + { + active_plugins = gedit_prefs_manager_get_active_plugins (); + } + + for (pl = engine->priv->plugin_list; pl; pl = pl->next) + { + GeditPluginInfo *info = (GeditPluginInfo*)pl->data; + + if (engine->priv->activate_from_prefs && + g_slist_find_custom (active_plugins, + gedit_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 && + !gedit_plugin_info_is_active (info)) + continue; + + if (load_plugin (engine, info)) + gedit_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; + } + + gedit_debug_message (DEBUG_PLUGINS, "End"); + + /* also call update_ui after activation */ + gedit_plugins_engine_update_plugins_ui (engine, window); +} + +void +gedit_plugins_engine_deactivate_plugins (GeditPluginsEngine *engine, + GeditWindow *window) +{ + GList *pl; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (GEDIT_IS_PLUGINS_ENGINE (engine)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + for (pl = engine->priv->plugin_list; pl; pl = pl->next) + { + GeditPluginInfo *info = (GeditPluginInfo*)pl->data; + + /* check if the plugin is actually active */ + if (!gedit_plugin_info_is_active (info)) + continue; + + /* call deactivate for the plugin for this window */ + gedit_plugin_deactivate (info->plugin, window); + } + + gedit_debug_message (DEBUG_PLUGINS, "End"); +} + +void +gedit_plugins_engine_update_plugins_ui (GeditPluginsEngine *engine, + GeditWindow *window) +{ + GList *pl; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (GEDIT_IS_PLUGINS_ENGINE (engine)); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + /* call update_ui for all active plugins */ + for (pl = engine->priv->plugin_list; pl; pl = pl->next) + { + GeditPluginInfo *info = (GeditPluginInfo*)pl->data; + + if (!gedit_plugin_info_is_active (info)) + continue; + + gedit_debug_message (DEBUG_PLUGINS, "Updating UI of %s", info->name); + gedit_plugin_update_ui (info->plugin, window); + } +} + +void +gedit_plugins_engine_configure_plugin (GeditPluginsEngine *engine, + GeditPluginInfo *info, + GtkWindow *parent) +{ + GtkWidget *conf_dlg; + + GtkWindowGroup *wg; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (info != NULL); + + conf_dlg = gedit_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 +gedit_plugins_engine_active_plugins_changed (GeditPluginsEngine *engine) +{ + gboolean to_activate; + GSList *active_plugins; + GList *pl; + + gedit_debug (DEBUG_PLUGINS); + + active_plugins = gedit_prefs_manager_get_active_plugins (); + + for (pl = engine->priv->plugin_list; pl; pl = pl->next) + { + GeditPluginInfo *info = (GeditPluginInfo*)pl->data; + + if (!gedit_plugin_info_is_available (info)) + continue; + + to_activate = (g_slist_find_custom (active_plugins, + gedit_plugin_info_get_module_name (info), + (GCompareFunc)strcmp) != NULL); + + if (!gedit_plugin_info_is_active (info) && to_activate) + g_signal_emit (engine, signals[ACTIVATE_PLUGIN], 0, info); + else if (gedit_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 +gedit_plugins_engine_rescan_plugins (GeditPluginsEngine *engine) +{ + gedit_debug (DEBUG_PLUGINS); + + load_all_plugins (engine); +} diff --git a/gedit/gedit-plugins-engine.h b/gedit/gedit-plugins-engine.h new file mode 100755 index 00000000..f2ebff63 --- /dev/null +++ b/gedit/gedit-plugins-engine.h @@ -0,0 +1,107 @@ +/* + * gedit-plugins-engine.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PLUGINS_ENGINE_H__ +#define __GEDIT_PLUGINS_ENGINE_H__ + +#include <glib.h> +#include "gedit-window.h" +#include "gedit-plugin-info.h" +#include "gedit-plugin.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_PLUGINS_ENGINE (gedit_plugins_engine_get_type ()) +#define GEDIT_PLUGINS_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PLUGINS_ENGINE, GeditPluginsEngine)) +#define GEDIT_PLUGINS_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_PLUGINS_ENGINE, GeditPluginsEngineClass)) +#define GEDIT_IS_PLUGINS_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_PLUGINS_ENGINE)) +#define GEDIT_IS_PLUGINS_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PLUGINS_ENGINE)) +#define GEDIT_PLUGINS_ENGINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_PLUGINS_ENGINE, GeditPluginsEngineClass)) + +typedef struct _GeditPluginsEngine GeditPluginsEngine; +typedef struct _GeditPluginsEnginePrivate GeditPluginsEnginePrivate; + +struct _GeditPluginsEngine +{ + GObject parent; + GeditPluginsEnginePrivate *priv; +}; + +typedef struct _GeditPluginsEngineClass GeditPluginsEngineClass; + +struct _GeditPluginsEngineClass +{ + GObjectClass parent_class; + + void (* activate_plugin) (GeditPluginsEngine *engine, + GeditPluginInfo *info); + + void (* deactivate_plugin) (GeditPluginsEngine *engine, + GeditPluginInfo *info); +}; + +GType gedit_plugins_engine_get_type (void) G_GNUC_CONST; + +GeditPluginsEngine *gedit_plugins_engine_get_default (void); + +void gedit_plugins_engine_garbage_collect (GeditPluginsEngine *engine); + +const GList *gedit_plugins_engine_get_plugin_list (GeditPluginsEngine *engine); + +GeditPluginInfo *gedit_plugins_engine_get_plugin_info (GeditPluginsEngine *engine, + const gchar *name); + +/* plugin load and unloading (overall, for all windows) */ +gboolean gedit_plugins_engine_activate_plugin (GeditPluginsEngine *engine, + GeditPluginInfo *info); +gboolean gedit_plugins_engine_deactivate_plugin (GeditPluginsEngine *engine, + GeditPluginInfo *info); + +void gedit_plugins_engine_configure_plugin (GeditPluginsEngine *engine, + GeditPluginInfo *info, + GtkWindow *parent); + +/* plugin activation/deactivation per window, private to GeditWindow */ +void gedit_plugins_engine_activate_plugins (GeditPluginsEngine *engine, + GeditWindow *window); +void gedit_plugins_engine_deactivate_plugins (GeditPluginsEngine *engine, + GeditWindow *window); +void gedit_plugins_engine_update_plugins_ui (GeditPluginsEngine *engine, + GeditWindow *window); + +/* private for mateconf notification */ +void gedit_plugins_engine_active_plugins_changed + (GeditPluginsEngine *engine); + +void gedit_plugins_engine_rescan_plugins (GeditPluginsEngine *engine); + +G_END_DECLS + +#endif /* __GEDIT_PLUGINS_ENGINE_H__ */ diff --git a/gedit/gedit-prefs-manager-app.c b/gedit/gedit-prefs-manager-app.c new file mode 100755 index 00000000..ce35db8d --- /dev/null +++ b/gedit/gedit-prefs-manager-app.c @@ -0,0 +1,1620 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-prefs-manager.c + * This file is part of gedit + * + * 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 gedit Team, 2002-2003. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "gedit-prefs-manager.h" +#include "gedit-prefs-manager-private.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-app.h" +#include "gedit-debug.h" +#include "gedit-view.h" +#include "gedit-window.h" +#include "gedit-window-private.h" +#include "gedit-plugins-engine.h" +#include "gedit-style-scheme-manager.h" +#include "gedit-dirs.h" + +static void gedit_prefs_manager_editor_font_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_system_font_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_tabs_size_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_wrap_mode_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_line_numbers_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_auto_indent_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_undo_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_right_margin_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_smart_home_end_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_hl_current_line_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_bracket_matching_changed(MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_syntax_hl_enable_changed(MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_search_hl_enable_changed(MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_source_style_scheme_changed + (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_max_recents_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_auto_save_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_prefs_manager_active_plugins_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static void gedit_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 GEDIT_STATE_DEFAULT_WINDOW_STATE 0 +#define GEDIT_STATE_DEFAULT_WINDOW_WIDTH 650 +#define GEDIT_STATE_DEFAULT_WINDOW_HEIGHT 500 +#define GEDIT_STATE_DEFAULT_SIDE_PANEL_SIZE 200 +#define GEDIT_STATE_DEFAULT_BOTTOM_PANEL_SIZE 140 + +#define GEDIT_STATE_FILE_LOCATION "gedit-2" + +#define GEDIT_STATE_WINDOW_GROUP "window" +#define GEDIT_STATE_WINDOW_STATE "state" +#define GEDIT_STATE_WINDOW_HEIGHT "height" +#define GEDIT_STATE_WINDOW_WIDTH "width" +#define GEDIT_STATE_SIDE_PANEL_SIZE "side_panel_size" +#define GEDIT_STATE_BOTTOM_PANEL_SIZE "bottom_panel_size" +#define GEDIT_STATE_SIDE_PANEL_ACTIVE_PAGE "side_panel_active_page" +#define GEDIT_STATE_BOTTOM_PANEL_ACTIVE_PAGE "bottom_panel_active_page" + +#define GEDIT_STATE_FILEFILTER_GROUP "filefilter" +#define GEDIT_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 = gedit_dirs_get_user_config_dir (); + + if (config_dir != NULL) + { + filename = g_build_filename (config_dir, + GEDIT_STATE_FILE_LOCATION, + NULL); + g_free (config_dir); + } + + return filename; +} + +static GKeyFile * +get_gedit_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 gedit state file: %s\n", + err->message); + } + + g_error_free (err); + } + + g_free (filename); + } + + return state_file; +} + +static void +gedit_state_get_int (const gchar *group, + const gchar *key, + gint defval, + gint *result) +{ + GKeyFile *state_file; + gint res; + GError *err = NULL; + + state_file = get_gedit_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 +gedit_state_set_int (const gchar *group, + const gchar *key, + gint value) +{ + GKeyFile *state_file; + + state_file = get_gedit_state_file (); + g_key_file_set_integer (state_file, + group, + key, + value); +} + +static gboolean +gedit_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_gedit_state_file (); + g_return_val_if_fail (state_file != NULL, FALSE); + + config_dir = gedit_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 gedit 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 +gedit_prefs_manager_get_window_state (void) +{ + if (window_state == -1) + { + gedit_state_get_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_WINDOW_STATE, + GEDIT_STATE_DEFAULT_WINDOW_STATE, + &window_state); + } + + return window_state; +} + +void +gedit_prefs_manager_set_window_state (gint ws) +{ + g_return_if_fail (ws > -1); + + window_state = ws; + + gedit_state_set_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_WINDOW_STATE, + ws); +} + +gboolean +gedit_prefs_manager_window_state_can_set (void) +{ + return TRUE; +} + +/* Window size */ +void +gedit_prefs_manager_get_window_size (gint *width, gint *height) +{ + g_return_if_fail (width != NULL && height != NULL); + + if (window_width == -1) + { + gedit_state_get_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_WINDOW_WIDTH, + GEDIT_STATE_DEFAULT_WINDOW_WIDTH, + &window_width); + } + + if (window_height == -1) + { + gedit_state_get_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_WINDOW_HEIGHT, + GEDIT_STATE_DEFAULT_WINDOW_HEIGHT, + &window_height); + } + + *width = window_width; + *height = window_height; +} + +void +gedit_prefs_manager_get_default_window_size (gint *width, gint *height) +{ + g_return_if_fail (width != NULL && height != NULL); + + *width = GEDIT_STATE_DEFAULT_WINDOW_WIDTH; + *height = GEDIT_STATE_DEFAULT_WINDOW_HEIGHT; +} + +void +gedit_prefs_manager_set_window_size (gint width, gint height) +{ + g_return_if_fail (width > -1 && height > -1); + + window_width = width; + window_height = height; + + gedit_state_set_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_WINDOW_WIDTH, + width); + gedit_state_set_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_WINDOW_HEIGHT, + height); +} + +gboolean +gedit_prefs_manager_window_size_can_set (void) +{ + return TRUE; +} + +/* Side panel */ +gint +gedit_prefs_manager_get_side_panel_size (void) +{ + if (side_panel_size == -1) + { + gedit_state_get_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_SIDE_PANEL_SIZE, + GEDIT_STATE_DEFAULT_SIDE_PANEL_SIZE, + &side_panel_size); + } + + return side_panel_size; +} + +gint +gedit_prefs_manager_get_default_side_panel_size (void) +{ + return GEDIT_STATE_DEFAULT_SIDE_PANEL_SIZE; +} + +void +gedit_prefs_manager_set_side_panel_size (gint ps) +{ + g_return_if_fail (ps > -1); + + if (side_panel_size == ps) + return; + + side_panel_size = ps; + gedit_state_set_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_SIDE_PANEL_SIZE, + ps); +} + +gboolean +gedit_prefs_manager_side_panel_size_can_set (void) +{ + return TRUE; +} + +gint +gedit_prefs_manager_get_side_panel_active_page (void) +{ + if (side_panel_active_page == -1) + { + gedit_state_get_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_SIDE_PANEL_ACTIVE_PAGE, + 0, + &side_panel_active_page); + } + + return side_panel_active_page; +} + +void +gedit_prefs_manager_set_side_panel_active_page (gint id) +{ + if (side_panel_active_page == id) + return; + + side_panel_active_page = id; + gedit_state_set_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_SIDE_PANEL_ACTIVE_PAGE, + id); +} + +gboolean +gedit_prefs_manager_side_panel_active_page_can_set (void) +{ + return TRUE; +} + +/* Bottom panel */ +gint +gedit_prefs_manager_get_bottom_panel_size (void) +{ + if (bottom_panel_size == -1) + { + gedit_state_get_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_BOTTOM_PANEL_SIZE, + GEDIT_STATE_DEFAULT_BOTTOM_PANEL_SIZE, + &bottom_panel_size); + } + + return bottom_panel_size; +} + +gint +gedit_prefs_manager_get_default_bottom_panel_size (void) +{ + return GEDIT_STATE_DEFAULT_BOTTOM_PANEL_SIZE; +} + +void +gedit_prefs_manager_set_bottom_panel_size (gint ps) +{ + g_return_if_fail (ps > -1); + + if (bottom_panel_size == ps) + return; + + bottom_panel_size = ps; + gedit_state_set_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_BOTTOM_PANEL_SIZE, + ps); +} + +gboolean +gedit_prefs_manager_bottom_panel_size_can_set (void) +{ + return TRUE; +} + +gint +gedit_prefs_manager_get_bottom_panel_active_page (void) +{ + if (bottom_panel_active_page == -1) + { + gedit_state_get_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_BOTTOM_PANEL_ACTIVE_PAGE, + 0, + &bottom_panel_active_page); + } + + return bottom_panel_active_page; +} + +void +gedit_prefs_manager_set_bottom_panel_active_page (gint id) +{ + if (bottom_panel_active_page == id) + return; + + bottom_panel_active_page = id; + gedit_state_set_int (GEDIT_STATE_WINDOW_GROUP, + GEDIT_STATE_BOTTOM_PANEL_ACTIVE_PAGE, + id); +} + +gboolean +gedit_prefs_manager_bottom_panel_active_page_can_set (void) +{ + return TRUE; +} + +/* File filter */ +gint +gedit_prefs_manager_get_active_file_filter (void) +{ + if (active_file_filter == -1) + { + gedit_state_get_int (GEDIT_STATE_FILEFILTER_GROUP, + GEDIT_STATE_FILEFILTER_ID, + 0, + &active_file_filter); + } + + return active_file_filter; +} + +void +gedit_prefs_manager_set_active_file_filter (gint id) +{ + g_return_if_fail (id >= 0); + + if (active_file_filter == id) + return; + + active_file_filter = id; + gedit_state_set_int (GEDIT_STATE_FILEFILTER_GROUP, + GEDIT_STATE_FILEFILTER_ID, + id); +} + +gboolean +gedit_prefs_manager_active_file_filter_can_set (void) +{ + return TRUE; +} + +/* Normal prefs are stored in MateConf */ + +gboolean +gedit_prefs_manager_app_init (void) +{ + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager == NULL, FALSE); + + gedit_prefs_manager_init (); + + if (gedit_prefs_manager != NULL) + { + /* TODO: notify, add dirs */ + mateconf_client_add_dir (gedit_prefs_manager->mateconf_client, + GPM_PREFS_DIR, + MATECONF_CLIENT_PRELOAD_RECURSIVE, + NULL); + + mateconf_client_add_dir (gedit_prefs_manager->mateconf_client, + GPM_PLUGINS_DIR, + MATECONF_CLIENT_PRELOAD_RECURSIVE, + NULL); + + mateconf_client_add_dir (gedit_prefs_manager->mateconf_client, + GPM_LOCKDOWN_DIR, + MATECONF_CLIENT_PRELOAD_RECURSIVE, + NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_FONT_DIR, + gedit_prefs_manager_editor_font_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_SYSTEM_FONT, + gedit_prefs_manager_system_font_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_TABS_DIR, + gedit_prefs_manager_tabs_size_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_WRAP_MODE_DIR, + gedit_prefs_manager_wrap_mode_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_LINE_NUMBERS_DIR, + gedit_prefs_manager_line_numbers_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_AUTO_INDENT_DIR, + gedit_prefs_manager_auto_indent_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_UNDO_DIR, + gedit_prefs_manager_undo_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_RIGHT_MARGIN_DIR, + gedit_prefs_manager_right_margin_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_SMART_HOME_END_DIR, + gedit_prefs_manager_smart_home_end_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_CURRENT_LINE_DIR, + gedit_prefs_manager_hl_current_line_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_BRACKET_MATCHING_DIR, + gedit_prefs_manager_bracket_matching_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_SYNTAX_HL_ENABLE, + gedit_prefs_manager_syntax_hl_enable_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_SEARCH_HIGHLIGHTING_ENABLE, + gedit_prefs_manager_search_hl_enable_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_SOURCE_STYLE_DIR, + gedit_prefs_manager_source_style_scheme_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_MAX_RECENTS, + gedit_prefs_manager_max_recents_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_SAVE_DIR, + gedit_prefs_manager_auto_save_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_ACTIVE_PLUGINS, + gedit_prefs_manager_active_plugins_changed, + NULL, NULL, NULL); + + mateconf_client_notify_add (gedit_prefs_manager->mateconf_client, + GPM_LOCKDOWN_DIR, + gedit_prefs_manager_lockdown_changed, + NULL, NULL, NULL); + } + + return gedit_prefs_manager != NULL; +} + +/* This function must be called before exiting gedit */ +void +gedit_prefs_manager_app_shutdown (void) +{ + gedit_debug (DEBUG_PREFS); + + gedit_prefs_manager_shutdown (); + + gedit_state_file_sync (); +} + + +static void +gedit_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; + + gedit_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 = gedit_prefs_manager_get_system_font (); + else + font = gedit_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 = gedit_prefs_manager_get_use_default_font (); + } + else + return; + + g_return_if_fail (font != NULL); + + ts = gedit_prefs_manager_get_tabs_size (); + + views = gedit_app_get_views (gedit_app_get_default ()); + l = views; + + while (l != NULL) + { + /* Note: we use def=FALSE to avoid GeditView to query mateconf */ + gedit_view_set_font (GEDIT_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 +gedit_prefs_manager_system_font_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + GList *views; + GList *l; + gchar *font; + gint ts; + + gedit_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 (!gedit_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 = gedit_prefs_manager_get_tabs_size (); + + views = gedit_app_get_views (gedit_app_get_default ()); + l = views; + + while (l != NULL) + { + /* Note: we use def=FALSE to avoid GeditView to query mateconf */ + gedit_view_set_font (GEDIT_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 +gedit_prefs_manager_tabs_size_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_views (gedit_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 = gedit_app_get_views (gedit_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 +gedit_prefs_manager_wrap_mode_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_views (gedit_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 +gedit_prefs_manager_line_numbers_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_views (gedit_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 +gedit_prefs_manager_hl_current_line_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_views (gedit_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 +gedit_prefs_manager_bracket_matching_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_documents (gedit_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 +gedit_prefs_manager_auto_indent_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_views (gedit_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 +gedit_prefs_manager_undo_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_documents (gedit_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 +gedit_prefs_manager_right_margin_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_views (gedit_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 = gedit_app_get_views (gedit_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 +gedit_prefs_manager_smart_home_end_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_views (gedit_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 +gedit_prefs_manager_syntax_hl_enable_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_documents (gedit_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 = gedit_app_get_windows (gedit_app_get_default ()); + while (windows != NULL) + { + GtkUIManager *ui; + GtkAction *a; + + ui = gedit_window_get_ui_manager (GEDIT_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 +gedit_prefs_manager_search_hl_enable_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_documents (gedit_app_get_default ()); + l = docs; + + while (l != NULL) + { + g_return_if_fail (GEDIT_IS_DOCUMENT (l->data)); + + gedit_document_set_enable_search_highlighting (GEDIT_DOCUMENT (l->data), + enable); + + l = l->next; + } + + g_list_free (docs); + } +} + +static void +gedit_prefs_manager_source_style_scheme_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 ( + gedit_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 ( + gedit_get_style_scheme_manager (), + "classic"); + + if (style == NULL) + { + g_warning ("Style scheme 'classic' cannot be found, check your GtkSourceView installation."); + return; + } + } + + docs = gedit_app_get_documents (gedit_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 +gedit_prefs_manager_max_recents_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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 = gedit_app_get_windows (gedit_app_get_default ()); + while (windows != NULL) + { + GeditWindow *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 +gedit_prefs_manager_auto_save_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + GList *docs; + GList *l; + + gedit_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 = gedit_app_get_documents (gedit_app_get_default ()); + l = docs; + + while (l != NULL) + { + GeditDocument *doc = GEDIT_DOCUMENT (l->data); + GeditTab *tab = gedit_tab_get_from_document (doc); + + gedit_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 = gedit_app_get_documents (gedit_app_get_default ()); + l = docs; + + while (l != NULL) + { + GeditDocument *doc = GEDIT_DOCUMENT (l->data); + GeditTab *tab = gedit_tab_get_from_document (doc); + + gedit_tab_set_auto_save_interval (tab, auto_save_interval); + + l = l->next; + } + + g_list_free (docs); + } +} + +static void +gedit_prefs_manager_active_plugins_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + gedit_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)) + { + GeditPluginsEngine *engine; + + engine = gedit_plugins_engine_get_default (); + + gedit_plugins_engine_active_plugins_changed (engine); + } + } +} + +static void +gedit_prefs_manager_lockdown_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + GeditApp *app; + gboolean locked; + + gedit_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 = gedit_app_get_default (); + + if (strcmp (entry->key, GPM_LOCKDOWN_COMMAND_LINE) == 0) + _gedit_app_set_lockdown_bit (app, + GEDIT_LOCKDOWN_COMMAND_LINE, + locked); + + else if (strcmp (entry->key, GPM_LOCKDOWN_PRINTING) == 0) + _gedit_app_set_lockdown_bit (app, + GEDIT_LOCKDOWN_PRINTING, + locked); + + else if (strcmp (entry->key, GPM_LOCKDOWN_PRINT_SETUP) == 0) + _gedit_app_set_lockdown_bit (app, + GEDIT_LOCKDOWN_PRINT_SETUP, + locked); + + else if (strcmp (entry->key, GPM_LOCKDOWN_SAVE_TO_DISK) == 0) + _gedit_app_set_lockdown_bit (app, + GEDIT_LOCKDOWN_SAVE_TO_DISK, + locked); +} diff --git a/gedit/gedit-prefs-manager-app.h b/gedit/gedit-prefs-manager-app.h new file mode 100755 index 00000000..592b643f --- /dev/null +++ b/gedit/gedit-prefs-manager-app.h @@ -0,0 +1,84 @@ +/* + * gedit-prefs-manager-app.h + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + * + */ + +#ifndef __GEDIT_PREFS_MANAGER_APP_H__ +#define __GEDIT_PREFS_MANAGER_APP_H__ + +#include <glib.h> +#include <gedit/gedit-prefs-manager.h> + +/** LIFE CYCLE MANAGEMENT FUNCTIONS **/ + +gboolean gedit_prefs_manager_app_init (void); + +/* This function must be called before exiting gedit */ +void gedit_prefs_manager_app_shutdown (void); + + +/* Window state */ +gint gedit_prefs_manager_get_window_state (void); +void gedit_prefs_manager_set_window_state (gint ws); +gboolean gedit_prefs_manager_window_state_can_set (void); + +/* Window size */ +void gedit_prefs_manager_get_window_size (gint *width, + gint *height); +void gedit_prefs_manager_get_default_window_size (gint *width, + gint *height); +void gedit_prefs_manager_set_window_size (gint width, + gint height); +gboolean gedit_prefs_manager_window_size_can_set (void); + +/* Side panel */ +gint gedit_prefs_manager_get_side_panel_size (void); +gint gedit_prefs_manager_get_default_side_panel_size(void); +void gedit_prefs_manager_set_side_panel_size (gint ps); +gboolean gedit_prefs_manager_side_panel_size_can_set (void); +gint gedit_prefs_manager_get_side_panel_active_page (void); +void gedit_prefs_manager_set_side_panel_active_page (gint id); +gboolean gedit_prefs_manager_side_panel_active_page_can_set (void); + +/* Bottom panel */ +gint gedit_prefs_manager_get_bottom_panel_size (void); +gint gedit_prefs_manager_get_default_bottom_panel_size(void); +void gedit_prefs_manager_set_bottom_panel_size (gint ps); +gboolean gedit_prefs_manager_bottom_panel_size_can_set (void); +gint gedit_prefs_manager_get_bottom_panel_active_page (void); +void gedit_prefs_manager_set_bottom_panel_active_page (gint id); +gboolean gedit_prefs_manager_bottom_panel_active_page_can_set (void); + +/* File filter */ +gint gedit_prefs_manager_get_active_file_filter (void); +void gedit_prefs_manager_set_active_file_filter (gint id); +gboolean gedit_prefs_manager_active_file_filter_can_set (void); + + +#endif /* __GEDIT_PREFS_MANAGER_APP_H__ */ diff --git a/gedit/gedit-prefs-manager-private.h b/gedit/gedit-prefs-manager-private.h new file mode 100755 index 00000000..7a8e94a5 --- /dev/null +++ b/gedit/gedit-prefs-manager-private.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-prefs-manager-private.h + * This file is part of gedit + * + * 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 gedit Team, 2002. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef __GEDIT_PREFS_MANAGER_PRIVATE_H__ +#define __GEDIT_PREFS_MANAGER_PRIVATE_H__ + +#include <mateconf/mateconf-client.h> + +typedef struct _GeditPrefsManager GeditPrefsManager; + +struct _GeditPrefsManager { + MateConfClient *mateconf_client; +}; + +extern GeditPrefsManager *gedit_prefs_manager; + +#endif /* __GEDIT_PREFS_MANAGER_PRIVATE_H__ */ + + diff --git a/gedit/gedit-prefs-manager.c b/gedit/gedit-prefs-manager.c new file mode 100755 index 00000000..c606e748 --- /dev/null +++ b/gedit/gedit-prefs-manager.c @@ -0,0 +1,1241 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-prefs-manager.c + * This file is part of gedit + * + * 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 gedit Team, 2002. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-prefs-manager.h" +#include "gedit-prefs-manager-private.h" +#include "gedit-debug.h" +#include "gedit-encodings.h" +#include "gedit-utils.h" + +#define DEFINE_BOOL_PREF(name, key, def) gboolean \ +gedit_prefs_manager_get_ ## name (void) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + return gedit_prefs_manager_get_bool (key, \ + (def)); \ +} \ + \ +void \ +gedit_prefs_manager_set_ ## name (gboolean v) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + gedit_prefs_manager_set_bool (key, \ + v); \ +} \ + \ +gboolean \ +gedit_prefs_manager_ ## name ## _can_set (void) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + return gedit_prefs_manager_key_is_writable (key); \ +} + + + +#define DEFINE_INT_PREF(name, key, def) gint \ +gedit_prefs_manager_get_ ## name (void) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + return gedit_prefs_manager_get_int (key, \ + (def)); \ +} \ + \ +void \ +gedit_prefs_manager_set_ ## name (gint v) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + gedit_prefs_manager_set_int (key, \ + v); \ +} \ + \ +gboolean \ +gedit_prefs_manager_ ## name ## _can_set (void) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + return gedit_prefs_manager_key_is_writable (key); \ +} + + + +#define DEFINE_STRING_PREF(name, key, def) gchar* \ +gedit_prefs_manager_get_ ## name (void) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + return gedit_prefs_manager_get_string (key, \ + def); \ +} \ + \ +void \ +gedit_prefs_manager_set_ ## name (const gchar* v) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + gedit_prefs_manager_set_string (key, \ + v); \ +} \ + \ +gboolean \ +gedit_prefs_manager_ ## name ## _can_set (void) \ +{ \ + gedit_debug (DEBUG_PREFS); \ + \ + return gedit_prefs_manager_key_is_writable (key); \ +} + + +GeditPrefsManager *gedit_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 gedit_prefs_manager_get_bool (const gchar* key, + gboolean def); + +static gint gedit_prefs_manager_get_int (const gchar* key, + gint def); + +static gchar *gedit_prefs_manager_get_string (const gchar* key, + const gchar* def); + + +gboolean +gedit_prefs_manager_init (void) +{ + gedit_debug (DEBUG_PREFS); + + if (gedit_prefs_manager == NULL) + { + MateConfClient *mateconf_client; + + mateconf_client = mateconf_client_get_default (); + if (mateconf_client == NULL) + { + g_warning (_("Cannot initialize preferences manager.")); + return FALSE; + } + + gedit_prefs_manager = g_new0 (GeditPrefsManager, 1); + + gedit_prefs_manager->mateconf_client = mateconf_client; + } + + if (gedit_prefs_manager->mateconf_client == NULL) + { + g_free (gedit_prefs_manager); + gedit_prefs_manager = NULL; + } + + return gedit_prefs_manager != NULL; + +} + +void +gedit_prefs_manager_shutdown (void) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (gedit_prefs_manager != NULL); + + g_object_unref (gedit_prefs_manager->mateconf_client); + gedit_prefs_manager->mateconf_client = NULL; +} + +static gboolean +gedit_prefs_manager_get_bool (const gchar* key, gboolean def) +{ + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, def); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, def); + + return mateconf_client_get_bool_with_default (gedit_prefs_manager->mateconf_client, + key, + def, + NULL); +} + +static gint +gedit_prefs_manager_get_int (const gchar* key, gint def) +{ + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, def); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, def); + + return mateconf_client_get_int_with_default (gedit_prefs_manager->mateconf_client, + key, + def, + NULL); +} + +static gchar * +gedit_prefs_manager_get_string (const gchar* key, const gchar* def) +{ + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, + def ? g_strdup (def) : NULL); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, + def ? g_strdup (def) : NULL); + + return mateconf_client_get_string_with_default (gedit_prefs_manager->mateconf_client, + key, + def, + NULL); +} + +static void +gedit_prefs_manager_set_bool (const gchar* key, gboolean value) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (gedit_prefs_manager != NULL); + g_return_if_fail (gedit_prefs_manager->mateconf_client != NULL); + g_return_if_fail (mateconf_client_key_is_writable ( + gedit_prefs_manager->mateconf_client, key, NULL)); + + mateconf_client_set_bool (gedit_prefs_manager->mateconf_client, key, value, NULL); +} + +static void +gedit_prefs_manager_set_int (const gchar* key, gint value) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (gedit_prefs_manager != NULL); + g_return_if_fail (gedit_prefs_manager->mateconf_client != NULL); + g_return_if_fail (mateconf_client_key_is_writable ( + gedit_prefs_manager->mateconf_client, key, NULL)); + + mateconf_client_set_int (gedit_prefs_manager->mateconf_client, key, value, NULL); +} + +static void +gedit_prefs_manager_set_string (const gchar* key, const gchar* value) +{ + gedit_debug (DEBUG_PREFS); + + g_return_if_fail (value != NULL); + + g_return_if_fail (gedit_prefs_manager != NULL); + g_return_if_fail (gedit_prefs_manager->mateconf_client != NULL); + g_return_if_fail (mateconf_client_key_is_writable ( + gedit_prefs_manager->mateconf_client, key, NULL)); + + mateconf_client_set_string (gedit_prefs_manager->mateconf_client, key, value, NULL); +} + +static gboolean +gedit_prefs_manager_key_is_writable (const gchar* key) +{ + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, FALSE); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, FALSE); + + return mateconf_client_key_is_writable (gedit_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 * +gedit_prefs_manager_get_system_font (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 +gedit_prefs_manager_get_wrap_mode (void) +{ + gchar *str; + GtkWrapMode res; + + gedit_debug (DEBUG_PREFS); + + str = gedit_prefs_manager_get_string (GPM_WRAP_MODE, + GPM_DEFAULT_WRAP_MODE); + + res = get_wrap_mode_from_string (str); + + g_free (str); + + return res; +} + +void +gedit_prefs_manager_set_wrap_mode (GtkWrapMode wp) +{ + const gchar * str; + + gedit_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"; + } + + gedit_prefs_manager_set_string (GPM_WRAP_MODE, + str); +} + +gboolean +gedit_prefs_manager_wrap_mode_can_set (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 */ +GeditToolbarSetting +gedit_prefs_manager_get_toolbar_buttons_style (void) +{ + gchar *str; + GeditToolbarSetting res; + + gedit_debug (DEBUG_PREFS); + + str = gedit_prefs_manager_get_string (GPM_TOOLBAR_BUTTONS_STYLE, + GPM_DEFAULT_TOOLBAR_BUTTONS_STYLE); + + if (strcmp (str, "GEDIT_TOOLBAR_ICONS") == 0) + res = GEDIT_TOOLBAR_ICONS; + else + { + if (strcmp (str, "GEDIT_TOOLBAR_ICONS_AND_TEXT") == 0) + res = GEDIT_TOOLBAR_ICONS_AND_TEXT; + else + { + if (strcmp (str, "GEDIT_TOOLBAR_ICONS_BOTH_HORIZ") == 0) + res = GEDIT_TOOLBAR_ICONS_BOTH_HORIZ; + else + res = GEDIT_TOOLBAR_SYSTEM; + } + } + + g_free (str); + + return res; +} + +void +gedit_prefs_manager_set_toolbar_buttons_style (GeditToolbarSetting tbs) +{ + const gchar * str; + + gedit_debug (DEBUG_PREFS); + + switch (tbs) + { + case GEDIT_TOOLBAR_ICONS: + str = "GEDIT_TOOLBAR_ICONS"; + break; + + case GEDIT_TOOLBAR_ICONS_AND_TEXT: + str = "GEDIT_TOOLBAR_ICONS_AND_TEXT"; + break; + + case GEDIT_TOOLBAR_ICONS_BOTH_HORIZ: + str = "GEDIT_TOOLBAR_ICONS_BOTH_HORIZ"; + break; + default: /* GEDIT_TOOLBAR_SYSTEM */ + str = "GEDIT_TOOLBAR_SYSTEM"; + } + + gedit_prefs_manager_set_string (GPM_TOOLBAR_BUTTONS_STYLE, + str); + +} + +gboolean +gedit_prefs_manager_toolbar_buttons_style_can_set (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 +gedit_prefs_manager_get_print_wrap_mode (void) +{ + gchar *str; + GtkWrapMode res; + + gedit_debug (DEBUG_PREFS); + + str = gedit_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 +gedit_prefs_manager_set_print_wrap_mode (GtkWrapMode pwp) +{ + const gchar *str; + + gedit_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"; + } + + gedit_prefs_manager_set_string (GPM_PRINT_WRAP_MODE, str); +} + +gboolean +gedit_prefs_manager_print_wrap_mode_can_set (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 * +gedit_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 * +gedit_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 * +gedit_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 +gedit_prefs_manager_get_max_recents (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 * +gedit_prefs_manager_get_auto_detected_encodings (void) +{ + GSList *strings; + GSList *res = NULL; + + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, NULL); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, NULL); + + strings = mateconf_client_get_list (gedit_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 GeditEncoding *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 = gedit_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); + } + + gedit_debug_message (DEBUG_PREFS, "Done"); + + return res; +} + +GSList * +gedit_prefs_manager_get_shown_in_menu_encodings (void) +{ + GSList *strings; + GSList *res = NULL; + + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, NULL); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, NULL); + + strings = mateconf_client_get_list (gedit_prefs_manager->mateconf_client, + GPM_SHOWN_IN_MENU_ENCODINGS, + MATECONF_VALUE_STRING, + NULL); + + if (strings != NULL) + { + GSList *tmp; + const GeditEncoding *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 = gedit_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 +gedit_prefs_manager_set_shown_in_menu_encodings (const GSList *encs) +{ + GSList *list = NULL; + + g_return_if_fail (gedit_prefs_manager != NULL); + g_return_if_fail (gedit_prefs_manager->mateconf_client != NULL); + g_return_if_fail (gedit_prefs_manager_shown_in_menu_encodings_can_set ()); + + while (encs != NULL) + { + const GeditEncoding *enc; + const gchar *charset; + + enc = (const GeditEncoding *)encs->data; + + charset = gedit_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 (gedit_prefs_manager->mateconf_client, + GPM_SHOWN_IN_MENU_ENCODINGS, + MATECONF_VALUE_STRING, + list, + NULL); + + g_slist_free (list); +} + +gboolean +gedit_prefs_manager_shown_in_menu_encodings_can_set (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 +gedit_prefs_manager_get_smart_home_end (void) +{ + gchar *str; + GtkSourceSmartHomeEndType res; + + gedit_debug (DEBUG_PREFS); + + str = gedit_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 +gedit_prefs_manager_set_smart_home_end (GtkSourceSmartHomeEndType smart_he) +{ + const gchar *str; + + gedit_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"; + } + + gedit_prefs_manager_set_string (GPM_WRAP_MODE, str); +} + +gboolean +gedit_prefs_manager_smart_home_end_can_set (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 * +gedit_prefs_manager_get_writable_vfs_schemes (void) +{ + GSList *strings; + + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, NULL); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, NULL); + + strings = mateconf_client_get_list (gedit_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")); + + gedit_debug_message (DEBUG_PREFS, "Done"); + + return strings; +} + +gboolean +gedit_prefs_manager_get_restore_cursor_position (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_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 * +gedit_prefs_manager_get_active_plugins (void) +{ + GSList *plugins; + + gedit_debug (DEBUG_PREFS); + + g_return_val_if_fail (gedit_prefs_manager != NULL, NULL); + g_return_val_if_fail (gedit_prefs_manager->mateconf_client != NULL, NULL); + + plugins = mateconf_client_get_list (gedit_prefs_manager->mateconf_client, + GPM_ACTIVE_PLUGINS, + MATECONF_VALUE_STRING, + NULL); + + return plugins; +} + +void +gedit_prefs_manager_set_active_plugins (const GSList *plugins) +{ + g_return_if_fail (gedit_prefs_manager != NULL); + g_return_if_fail (gedit_prefs_manager->mateconf_client != NULL); + g_return_if_fail (gedit_prefs_manager_active_plugins_can_set ()); + + mateconf_client_set_list (gedit_prefs_manager->mateconf_client, + GPM_ACTIVE_PLUGINS, + MATECONF_VALUE_STRING, + (GSList *) plugins, + NULL); +} + +gboolean +gedit_prefs_manager_active_plugins_can_set (void) +{ + gedit_debug (DEBUG_PREFS); + + return gedit_prefs_manager_key_is_writable (GPM_ACTIVE_PLUGINS); +} + +/* Global Lockdown */ + +GeditLockdownMask +gedit_prefs_manager_get_lockdown (void) +{ + guint lockdown = 0; + + if (gedit_prefs_manager_get_bool (GPM_LOCKDOWN_COMMAND_LINE, FALSE)) + lockdown |= GEDIT_LOCKDOWN_COMMAND_LINE; + + if (gedit_prefs_manager_get_bool (GPM_LOCKDOWN_PRINTING, FALSE)) + lockdown |= GEDIT_LOCKDOWN_PRINTING; + + if (gedit_prefs_manager_get_bool (GPM_LOCKDOWN_PRINT_SETUP, FALSE)) + lockdown |= GEDIT_LOCKDOWN_PRINT_SETUP; + + if (gedit_prefs_manager_get_bool (GPM_LOCKDOWN_SAVE_TO_DISK, FALSE)) + lockdown |= GEDIT_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/gedit/gedit-prefs-manager.h b/gedit/gedit-prefs-manager.h new file mode 100755 index 00000000..82041617 --- /dev/null +++ b/gedit/gedit-prefs-manager.h @@ -0,0 +1,423 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-prefs-manager.h + * This file is part of gedit + * + * 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 gedit Team, 2002. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef __GEDIT_PREFS_MANAGER_H__ +#define __GEDIT_PREFS_MANAGER_H__ + +#include <gtk/gtk.h> +#include <glib.h> +#include <gtksourceview/gtksourceview.h> +#include "gedit-app.h" + +#define GEDIT_BASE_KEY "/apps/gedit-2" + +#define GPM_PREFS_DIR GEDIT_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 GEDIT_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 gedit.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 "GEDIT_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 { + GEDIT_TOOLBAR_SYSTEM = 0, + GEDIT_TOOLBAR_ICONS, + GEDIT_TOOLBAR_ICONS_AND_TEXT, + GEDIT_TOOLBAR_ICONS_BOTH_HORIZ +} GeditToolbarSetting; + +/** LIFE CYCLE MANAGEMENT FUNCTIONS **/ + +gboolean gedit_prefs_manager_init (void); + +/* This function must be called before exiting gedit */ +void gedit_prefs_manager_shutdown (void); + + +/** PREFS MANAGEMENT FUNCTIONS **/ + +/* Use default font */ +gboolean gedit_prefs_manager_get_use_default_font (void); +void gedit_prefs_manager_set_use_default_font (gboolean udf); +gboolean gedit_prefs_manager_use_default_font_can_set (void); + +/* Editor font */ +gchar *gedit_prefs_manager_get_editor_font (void); +void gedit_prefs_manager_set_editor_font (const gchar *font); +gboolean gedit_prefs_manager_editor_font_can_set (void); + +/* System font */ +gchar *gedit_prefs_manager_get_system_font (void); + +/* Create backup copy */ +gboolean gedit_prefs_manager_get_create_backup_copy (void); +void gedit_prefs_manager_set_create_backup_copy (gboolean cbc); +gboolean gedit_prefs_manager_create_backup_copy_can_set (void); + +/* Auto save */ +gboolean gedit_prefs_manager_get_auto_save (void); +void gedit_prefs_manager_set_auto_save (gboolean as); +gboolean gedit_prefs_manager_auto_save_can_set (void); + +/* Auto save interval */ +gint gedit_prefs_manager_get_auto_save_interval (void); +void gedit_prefs_manager_set_auto_save_interval (gint asi); +gboolean gedit_prefs_manager_auto_save_interval_can_set (void); + +/* Undo actions limit: if < 1 then no limits */ +gint gedit_prefs_manager_get_undo_actions_limit (void); +void gedit_prefs_manager_set_undo_actions_limit (gint ual); +gboolean gedit_prefs_manager_undo_actions_limit_can_set (void); + +/* Wrap mode */ +GtkWrapMode gedit_prefs_manager_get_wrap_mode (void); +void gedit_prefs_manager_set_wrap_mode (GtkWrapMode wp); +gboolean gedit_prefs_manager_wrap_mode_can_set (void); + +/* Tabs size */ +gint gedit_prefs_manager_get_tabs_size (void); +void gedit_prefs_manager_set_tabs_size (gint ts); +gboolean gedit_prefs_manager_tabs_size_can_set (void); + +/* Insert spaces */ +gboolean gedit_prefs_manager_get_insert_spaces (void); +void gedit_prefs_manager_set_insert_spaces (gboolean ai); +gboolean gedit_prefs_manager_insert_spaces_can_set (void); + +/* Auto indent */ +gboolean gedit_prefs_manager_get_auto_indent (void); +void gedit_prefs_manager_set_auto_indent (gboolean ai); +gboolean gedit_prefs_manager_auto_indent_can_set (void); + +/* Display line numbers */ +gboolean gedit_prefs_manager_get_display_line_numbers (void); +void gedit_prefs_manager_set_display_line_numbers (gboolean dln); +gboolean gedit_prefs_manager_display_line_numbers_can_set (void); + +/* Toolbar visible */ +gboolean gedit_prefs_manager_get_toolbar_visible (void); +void gedit_prefs_manager_set_toolbar_visible (gboolean tv); +gboolean gedit_prefs_manager_toolbar_visible_can_set (void); + +/* Toolbar buttons style */ +GeditToolbarSetting gedit_prefs_manager_get_toolbar_buttons_style (void); +void gedit_prefs_manager_set_toolbar_buttons_style (GeditToolbarSetting tbs); +gboolean gedit_prefs_manager_toolbar_buttons_style_can_set (void); + +/* Statusbar visible */ +gboolean gedit_prefs_manager_get_statusbar_visible (void); +void gedit_prefs_manager_set_statusbar_visible (gboolean sv); +gboolean gedit_prefs_manager_statusbar_visible_can_set (void); + +/* Side pane visible */ +gboolean gedit_prefs_manager_get_side_pane_visible (void); +void gedit_prefs_manager_set_side_pane_visible (gboolean tv); +gboolean gedit_prefs_manager_side_pane_visible_can_set (void); + +/* Bottom panel visible */ +gboolean gedit_prefs_manager_get_bottom_panel_visible (void); +void gedit_prefs_manager_set_bottom_panel_visible (gboolean tv); +gboolean gedit_prefs_manager_bottom_panel_visible_can_set(void); +/* Print syntax highlighting */ +gboolean gedit_prefs_manager_get_print_syntax_hl (void); +void gedit_prefs_manager_set_print_syntax_hl (gboolean ps); +gboolean gedit_prefs_manager_print_syntax_hl_can_set (void); + +/* Print header */ +gboolean gedit_prefs_manager_get_print_header (void); +void gedit_prefs_manager_set_print_header (gboolean ph); +gboolean gedit_prefs_manager_print_header_can_set (void); + +/* Wrap mode while printing */ +GtkWrapMode gedit_prefs_manager_get_print_wrap_mode (void); +void gedit_prefs_manager_set_print_wrap_mode (GtkWrapMode pwm); +gboolean gedit_prefs_manager_print_wrap_mode_can_set (void); + +/* Print line numbers */ +gint gedit_prefs_manager_get_print_line_numbers (void); +void gedit_prefs_manager_set_print_line_numbers (gint pln); +gboolean gedit_prefs_manager_print_line_numbers_can_set (void); + +/* Font used to print the body of documents */ +gchar *gedit_prefs_manager_get_print_font_body (void); +void gedit_prefs_manager_set_print_font_body (const gchar *font); +gboolean gedit_prefs_manager_print_font_body_can_set (void); +const gchar *gedit_prefs_manager_get_default_print_font_body (void); + +/* Font used to print headers */ +gchar *gedit_prefs_manager_get_print_font_header (void); +void gedit_prefs_manager_set_print_font_header (const gchar *font); +gboolean gedit_prefs_manager_print_font_header_can_set (void); +const gchar *gedit_prefs_manager_get_default_print_font_header (void); + +/* Font used to print line numbers */ +gchar *gedit_prefs_manager_get_print_font_numbers (void); +void gedit_prefs_manager_set_print_font_numbers (const gchar *font); +gboolean gedit_prefs_manager_print_font_numbers_can_set (void); +const gchar *gedit_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 gedit_prefs_manager_get_max_recents (void); + +/* Encodings */ +GSList *gedit_prefs_manager_get_auto_detected_encodings (void); + +GSList *gedit_prefs_manager_get_shown_in_menu_encodings (void); +void gedit_prefs_manager_set_shown_in_menu_encodings (const GSList *encs); +gboolean gedit_prefs_manager_shown_in_menu_encodings_can_set (void); + +/* Highlight current line */ +gboolean gedit_prefs_manager_get_highlight_current_line (void); +void gedit_prefs_manager_set_highlight_current_line (gboolean hl); +gboolean gedit_prefs_manager_highlight_current_line_can_set (void); + +/* Highlight matching bracket */ +gboolean gedit_prefs_manager_get_bracket_matching (void); +void gedit_prefs_manager_set_bracket_matching (gboolean bm); +gboolean gedit_prefs_manager_bracket_matching_can_set (void); + +/* Display right margin */ +gboolean gedit_prefs_manager_get_display_right_margin (void); +void gedit_prefs_manager_set_display_right_margin (gboolean drm); +gboolean gedit_prefs_manager_display_right_margin_can_set (void); + +/* Right margin position */ +gint gedit_prefs_manager_get_right_margin_position (void); +void gedit_prefs_manager_set_right_margin_position (gint rmp); +gboolean gedit_prefs_manager_right_margin_position_can_set (void); + +/* Smart home end */ +GtkSourceSmartHomeEndType + gedit_prefs_manager_get_smart_home_end (void); +void gedit_prefs_manager_set_smart_home_end (GtkSourceSmartHomeEndType smart_he); +gboolean gedit_prefs_manager_smart_home_end_can_set (void); + +/* Enable syntax highlighting */ +gboolean gedit_prefs_manager_get_enable_syntax_highlighting (void); +void gedit_prefs_manager_set_enable_syntax_highlighting (gboolean esh); +gboolean gedit_prefs_manager_enable_syntax_highlighting_can_set (void); + +/* Writable VFS schemes */ +GSList *gedit_prefs_manager_get_writable_vfs_schemes (void); + +/* Restore cursor position */ +gboolean gedit_prefs_manager_get_restore_cursor_position (void); + +/* Enable search highlighting */ +gboolean gedit_prefs_manager_get_enable_search_highlighting (void); +void gedit_prefs_manager_set_enable_search_highlighting (gboolean esh); +gboolean gedit_prefs_manager_enable_search_highlighting_can_set (void); + +/* Style scheme */ +gchar *gedit_prefs_manager_get_source_style_scheme (void); +void gedit_prefs_manager_set_source_style_scheme (const gchar *scheme); +gboolean gedit_prefs_manager_source_style_scheme_can_set(void); + +/* Plugins */ +GSList *gedit_prefs_manager_get_active_plugins (void); +void gedit_prefs_manager_set_active_plugins (const GSList *plugins); +gboolean gedit_prefs_manager_active_plugins_can_set (void); + +/* Global lockdown */ +GeditLockdownMask gedit_prefs_manager_get_lockdown (void); + +#endif /* __GEDIT_PREFS_MANAGER_H__ */ + + diff --git a/gedit/gedit-print-job.c b/gedit/gedit-print-job.c new file mode 100755 index 00000000..00acba27 --- /dev/null +++ b/gedit/gedit-print-job.c @@ -0,0 +1,865 @@ +/* + * gedit-print.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id: gedit-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 "gedit-print-job.h" +#include "gedit-debug.h" +#include "gedit-prefs-manager.h" +#include "gedit-print-preview.h" +#include "gedit-marshal.h" +#include "gedit-utils.h" +#include "gedit-dirs.h" + + +#define GEDIT_PRINT_JOB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_PRINT_JOB, \ + GeditPrintJobPrivate)) + +struct _GeditPrintJobPrivate +{ + GeditView *view; + GeditDocument *doc; + + GtkPrintOperation *operation; + GtkSourcePrintCompositor *compositor; + + GtkPrintSettings *settings; + + GtkWidget *preview; + + GeditPrintJobStatus 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 (GeditPrintJob, gedit_print_job, G_TYPE_OBJECT) + +static void +set_view (GeditPrintJob *job, GeditView *view) +{ + job->priv->view = view; + job->priv->doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); +} + +static void +gedit_print_job_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditPrintJob *job = GEDIT_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 +gedit_print_job_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditPrintJob *job = GEDIT_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 +gedit_print_job_finalize (GObject *object) +{ + GeditPrintJob *job = GEDIT_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 (gedit_print_job_parent_class)->finalize (object); +} + +static void +gedit_print_job_class_init (GeditPrintJobClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gedit_print_job_get_property; + object_class->set_property = gedit_print_job_set_property; + object_class->finalize = gedit_print_job_finalize; + + g_object_class_install_property (object_class, + PROP_VIEW, + g_param_spec_object ("view", + "Gedit View", + "Gedit View to print", + GEDIT_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 (GeditPrintJobClass, 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 (GeditPrintJobClass, 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 (GeditPrintJobClass, done), + NULL, NULL, + gedit_marshal_VOID__UINT_POINTER, + G_TYPE_NONE, + 2, + G_TYPE_UINT, + G_TYPE_POINTER); + + g_type_class_add_private (object_class, sizeof (GeditPrintJobPrivate)); +} + +static void +line_numbers_checkbutton_toggled (GtkToggleButton *button, + GeditPrintJob *job) +{ + if (gtk_toggle_button_get_active (button)) + { + gtk_widget_set_sensitive (job->priv->line_numbers_hbox, + gedit_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, + GeditPrintJob *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, + GeditPrintJob *job) + +{ + if (gedit_prefs_manager_print_font_body_can_set ()) + { + const gchar *font; + + font = gedit_prefs_manager_get_default_print_font_body (); + + gtk_font_button_set_font_name ( + GTK_FONT_BUTTON (job->priv->body_fontbutton), + font); + } + + if (gedit_prefs_manager_print_font_header_can_set ()) + { + const gchar *font; + + font = gedit_prefs_manager_get_default_print_font_header (); + + gtk_font_button_set_font_name ( + GTK_FONT_BUTTON (job->priv->headers_fontbutton), + font); + } + + if (gedit_prefs_manager_print_font_numbers_can_set ()) + { + const gchar *font; + + font = gedit_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, + GeditPrintJob *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 = gedit_dirs_get_ui_file ("gedit-print-preferences.ui"); + ret = gedit_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), + gedit_prefs_manager_get_print_syntax_hl ()); + gtk_widget_set_sensitive (job->priv->syntax_checkbutton, + gedit_prefs_manager_print_syntax_hl_can_set ()); + + /* Print page headers */ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (job->priv->page_header_checkbutton), + gedit_prefs_manager_get_print_header ()); + gtk_widget_set_sensitive (job->priv->page_header_checkbutton, + gedit_prefs_manager_print_header_can_set ()); + + /* Line numbers */ + line_numbers = gedit_prefs_manager_get_print_line_numbers (); + can_set = gedit_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 = gedit_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 = gedit_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 = gedit_prefs_manager_get_print_font_body (); + gtk_font_button_set_font_name (GTK_FONT_BUTTON (job->priv->body_fontbutton), + font); + g_free (font); + + font = gedit_prefs_manager_get_print_font_header (); + gtk_font_button_set_font_name (GTK_FONT_BUTTON (job->priv->headers_fontbutton), + font); + g_free (font); + + font = gedit_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 = gedit_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 = gedit_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 = gedit_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, + GeditPrintJob *job) +{ + gedit_prefs_manager_set_print_syntax_hl (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->syntax_checkbutton))); + + gedit_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))) + { + gedit_prefs_manager_set_print_line_numbers ( + MAX (1, gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (job->priv->line_numbers_spinbutton)))); + } + else + { + gedit_prefs_manager_set_print_line_numbers (0); + } + + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (job->priv->text_wrapping_checkbutton))) + { + gedit_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))) + { + gedit_prefs_manager_set_print_wrap_mode (GTK_WRAP_WORD); + } + else + { + gedit_prefs_manager_set_print_wrap_mode (GTK_WRAP_CHAR); + } + } + + gedit_prefs_manager_set_print_font_body (gtk_font_button_get_font_name (GTK_FONT_BUTTON (job->priv->body_fontbutton))); + gedit_prefs_manager_set_print_font_header (gtk_font_button_get_font_name (GTK_FONT_BUTTON (job->priv->headers_fontbutton))); + gedit_prefs_manager_set_print_font_numbers (gtk_font_button_get_font_name (GTK_FONT_BUTTON (job->priv->numbers_fontbutton))); +} + +static void +create_compositor (GeditPrintJob *job) +{ + gchar *print_font_body; + gchar *print_font_header; + gchar *print_font_numbers; + + /* Create and initialize print compositor */ + print_font_body = gedit_prefs_manager_get_print_font_body (); + print_font_header = gedit_prefs_manager_get_print_font_header (); + print_font_numbers = gedit_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)) && + gedit_prefs_manager_get_print_syntax_hl (), + "wrap-mode", gedit_prefs_manager_get_print_wrap_mode (), + "print-line-numbers", gedit_prefs_manager_get_print_line_numbers (), + "print-header", gedit_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 (gedit_prefs_manager_get_print_header ()) + { + gchar *doc_name; + gchar *name_to_display; + gchar *left; + + doc_name = gedit_document_get_uri_for_display (job->priv->doc); + name_to_display = gedit_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, + GeditPrintJob *job) +{ + create_compositor (job); + + job->priv->status = GEDIT_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, + GeditPrintJob *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, + GeditPrintJob *job) +{ + job->priv->preview = gedit_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, + GeditPrintJob *job) +{ + gboolean res; + + job->priv->status = GEDIT_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, + GeditPrintJob *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 = GEDIT_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, + GeditPrintJob *job) +{ + g_object_unref (job->priv->compositor); + job->priv->compositor = NULL; +} + +static void +done_cb (GtkPrintOperation *operation, + GtkPrintOperationResult result, + GeditPrintJob *job) +{ + GError *error = NULL; + GeditPrintJobResult print_result; + + switch (result) + { + case GTK_PRINT_OPERATION_RESULT_CANCEL: + print_result = GEDIT_PRINT_JOB_RESULT_CANCEL; + break; + + case GTK_PRINT_OPERATION_RESULT_APPLY: + print_result = GEDIT_PRINT_JOB_RESULT_OK; + break; + + case GTK_PRINT_OPERATION_RESULT_ERROR: + print_result = GEDIT_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 gedit_print_job_print can can only be called once on a given GeditPrintJob */ +GtkPrintOperationResult +gedit_print_job_print (GeditPrintJob *job, + GtkPrintOperationAction action, + GtkPageSetup *page_setup, + GtkPrintSettings *settings, + GtkWindow *parent, + GError **error) +{ + GeditPrintJobPrivate *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 = gedit_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 +gedit_print_job_init (GeditPrintJob *job) +{ + job->priv = GEDIT_PRINT_JOB_GET_PRIVATE (job); + + job->priv->status = GEDIT_PRINT_JOB_STATUS_INIT; + + job->priv->status_string = g_strdup (_("Preparing...")); +} + +GeditPrintJob * +gedit_print_job_new (GeditView *view) +{ + GeditPrintJob *job; + + g_return_val_if_fail (GEDIT_IS_VIEW (view), NULL); + + job = GEDIT_PRINT_JOB (g_object_new (GEDIT_TYPE_PRINT_JOB, + "view", view, + NULL)); + + return job; +} + +void +gedit_print_job_cancel (GeditPrintJob *job) +{ + g_return_if_fail (GEDIT_IS_PRINT_JOB (job)); + + gtk_print_operation_cancel (job->priv->operation); +} + +const gchar * +gedit_print_job_get_status_string (GeditPrintJob *job) +{ + g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), NULL); + g_return_val_if_fail (job->priv->status_string != NULL, NULL); + + return job->priv->status_string; +} + +gdouble +gedit_print_job_get_progress (GeditPrintJob *job) +{ + g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), 0.0); + + return job->priv->progress; +} + +GtkPrintSettings * +gedit_print_job_get_print_settings (GeditPrintJob *job) +{ + g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), NULL); + + return gtk_print_operation_get_print_settings (job->priv->operation); +} + +GtkPageSetup * +gedit_print_job_get_page_setup (GeditPrintJob *job) +{ + g_return_val_if_fail (GEDIT_IS_PRINT_JOB (job), NULL); + + return gtk_print_operation_get_default_page_setup (job->priv->operation); +} diff --git a/gedit/gedit-print-job.h b/gedit/gedit-print-job.h new file mode 100755 index 00000000..956dc730 --- /dev/null +++ b/gedit/gedit-print-job.h @@ -0,0 +1,133 @@ +/* + * gedit-print-job.h + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PRINT_JOB_H__ +#define __GEDIT_PRINT_JOB_H__ + +#include <gtk/gtk.h> +#include <gedit/gedit-view.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_PRINT_JOB (gedit_print_job_get_type()) +#define GEDIT_PRINT_JOB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PRINT_JOB, GeditPrintJob)) +#define GEDIT_PRINT_JOB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_PRINT_JOB, GeditPrintJobClass)) +#define GEDIT_IS_PRINT_JOB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_PRINT_JOB)) +#define GEDIT_IS_PRINT_JOB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PRINT_JOB)) +#define GEDIT_PRINT_JOB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_PRINT_JOB, GeditPrintJobClass)) + + +typedef enum +{ + GEDIT_PRINT_JOB_STATUS_INIT, + GEDIT_PRINT_JOB_STATUS_PAGINATING, + GEDIT_PRINT_JOB_STATUS_DRAWING, + GEDIT_PRINT_JOB_STATUS_DONE +} GeditPrintJobStatus; + +typedef enum +{ + GEDIT_PRINT_JOB_RESULT_OK, + GEDIT_PRINT_JOB_RESULT_CANCEL, + GEDIT_PRINT_JOB_RESULT_ERROR +} GeditPrintJobResult; + +/* Private structure type */ +typedef struct _GeditPrintJobPrivate GeditPrintJobPrivate; + +/* + * Main object structure + */ +typedef struct _GeditPrintJob GeditPrintJob; + + +struct _GeditPrintJob +{ + GObject parent; + + /* <private> */ + GeditPrintJobPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditPrintJobClass GeditPrintJobClass; + +struct _GeditPrintJobClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* printing) (GeditPrintJob *job, + GeditPrintJobStatus status); + + void (* show_preview) (GeditPrintJob *job, + GtkWidget *preview); + + void (*done) (GeditPrintJob *job, + GeditPrintJobResult result, + const GError *error); +}; + +/* + * Public methods + */ +GType gedit_print_job_get_type (void) G_GNUC_CONST; + +GeditPrintJob *gedit_print_job_new (GeditView *view); + +void gedit_print_job_set_export_filename (GeditPrintJob *job, + const gchar *filename); + +GtkPrintOperationResult gedit_print_job_print (GeditPrintJob *job, + GtkPrintOperationAction action, + GtkPageSetup *page_setup, + GtkPrintSettings *settings, + GtkWindow *parent, + GError **error); + +void gedit_print_job_cancel (GeditPrintJob *job); + +const gchar *gedit_print_job_get_status_string (GeditPrintJob *job); + +gdouble gedit_print_job_get_progress (GeditPrintJob *job); + +GtkPrintSettings *gedit_print_job_get_print_settings (GeditPrintJob *job); + +GtkPageSetup *gedit_print_job_get_page_setup (GeditPrintJob *job); + +G_END_DECLS + +#endif /* __GEDIT_PRINT_JOB_H__ */ diff --git a/gedit/gedit-print-preferences.ui b/gedit/gedit-print-preferences.ui new file mode 100755 index 00000000..3917eeba --- /dev/null +++ b/gedit/gedit-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/gedit/gedit-print-preview.c b/gedit/gedit-print-preview.c new file mode 100755 index 00000000..293a930b --- /dev/null +++ b/gedit/gedit-print-preview.c @@ -0,0 +1,1327 @@ +/* + * gedit-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 gedit Team, 1998-2006. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id: gedit-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 "gedit-print-preview.h" + +#define PRINTER_DPI (72.) + +struct _GeditPrintPreviewPrivate +{ + 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 (GeditPrintPreview, gedit_print_preview, GTK_TYPE_VBOX) + +static void +gedit_print_preview_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + //GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_print_preview_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + //GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_print_preview_finalize (GObject *object) +{ + //GeditPrintPreview *preview = GEDIT_PRINT_PREVIEW (object); + + G_OBJECT_CLASS (gedit_print_preview_parent_class)->finalize (object); +} + +static void +gedit_print_preview_grab_focus (GtkWidget *widget) +{ + GeditPrintPreview *preview; + + preview = GEDIT_PRINT_PREVIEW (widget); + + gtk_widget_grab_focus (GTK_WIDGET (preview->priv->layout)); +} + +static void +gedit_print_preview_class_init (GeditPrintPreviewClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gedit_print_preview_get_property; + object_class->set_property = gedit_print_preview_set_property; + object_class->finalize = gedit_print_preview_finalize; + + widget_class->grab_focus = gedit_print_preview_grab_focus; + + g_type_class_add_private (object_class, sizeof(GeditPrintPreviewPrivate)); +} + +static void +update_layout_size (GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 (GeditPrintPreview *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 (GeditPrintPreview *preview) +{ + return preview->priv->paper_w * preview->priv->dpi; +} + +static double +get_paper_height (GeditPrintPreview *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 (GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 (GeditPrintPreview *preview, + double zoom) +{ + GeditPrintPreviewPrivate *priv; + + priv = preview->priv; + + priv->scale = zoom; + + update_tile_size (preview); + update_layout_size (preview); +} + +static void +set_zoom_fit_to_size (GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 (GeditPrintPreview *preview) +{ + set_zoom_factor (preview, + preview->priv->scale * ZOOM_IN_FACTOR); +} + +static void +zoom_out (GeditPrintPreview *preview) +{ + set_zoom_factor (preview, + preview->priv->scale * ZOOM_OUT_FACTOR); +} + +static void +goto_page (GeditPrintPreview *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, + GeditPrintPreview *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, + GeditPrintPreview *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, + GeditPrintPreview *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, + GeditPrintPreview *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, GeditPrintPreview *preview) +{ + set_rows_and_cols (preview, 1, 1); +} + +static void +on_1x2_clicked (GtkMenuItem *i, GeditPrintPreview *preview) +{ + set_rows_and_cols (preview, 1, 2); +} + +static void +on_2x1_clicked (GtkMenuItem *i, GeditPrintPreview *preview) +{ + set_rows_and_cols (preview, 2, 1); +} + +static void +on_2x2_clicked (GtkMenuItem *i, GeditPrintPreview *preview) +{ + set_rows_and_cols (preview, 2, 2); +} + +static void +multi_button_clicked (GtkWidget *button, + GeditPrintPreview *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, + GeditPrintPreview *preview) +{ + set_zoom_factor (preview, 1); +} + +static void +zoom_fit_button_clicked (GtkWidget *button, + GeditPrintPreview *preview) +{ + set_zoom_fit_to_size (preview); +} + +static void +zoom_in_button_clicked (GtkWidget *button, + GeditPrintPreview *preview) +{ + zoom_in (preview); +} + +static void +zoom_out_button_clicked (GtkWidget *button, + GeditPrintPreview *preview) +{ + zoom_out (preview); +} + +static void +close_button_clicked (GtkWidget *button, + GeditPrintPreview *preview) +{ + gtk_widget_destroy (GTK_WIDGET (preview)); +} + +static void +create_bar (GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 (GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 (GeditPrintPreview *preview, + gint x, + gint y) +{ + GeditPrintPreviewPrivate *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, + GeditPrintPreview *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, + GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 (GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 +gedit_print_preview_init (GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE (preview, + GEDIT_TYPE_PRINT_PREVIEW, + GeditPrintPreviewPrivate); + + 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, + GeditPrintPreview *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, + GeditPrintPreview *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, + GeditPrintPreview *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, + GeditPrintPreview *preview) +{ + GeditPrintPreviewPrivate *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 (GeditPrintPreview *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 (GeditPrintPreview *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, + GeditPrintPreview *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 (GeditPrintPreview *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, + GeditPrintPreview *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 (GeditPrintPreview *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 * +gedit_print_preview_new (GtkPrintOperation *op, + GtkPrintOperationPreview *gtk_preview, + GtkPrintContext *context) +{ + GeditPrintPreview *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 (GEDIT_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/gedit/gedit-print-preview.h b/gedit/gedit-print-preview.h new file mode 100755 index 00000000..f86ef87a --- /dev/null +++ b/gedit/gedit-print-preview.h @@ -0,0 +1,71 @@ +/* + * gedit-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 gedit Team, 1998-2006. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id: gedit-commands-search.c 5931 2007-09-25 20:05:40Z pborelli $ + */ + + +#ifndef __GEDIT_PRINT_PREVIEW_H__ +#define __GEDIT_PRINT_PREVIEW_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_PRINT_PREVIEW (gedit_print_preview_get_type ()) +#define GEDIT_PRINT_PREVIEW(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GEDIT_TYPE_PRINT_PREVIEW, GeditPrintPreview)) +#define GEDIT_PRINT_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_PRINT_PREVIEW, GeditPrintPreviewClass)) +#define GEDIT_IS_PRINT_PREVIEW(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GEDIT_TYPE_PRINT_PREVIEW)) +#define GEDIT_IS_PRINT_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PRINT_PREVIEW)) +#define GEDIT_PRINT_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_PRINT_PREVIEW, GeditPrintPreviewClass)) + +typedef struct _GeditPrintPreview GeditPrintPreview; +typedef struct _GeditPrintPreviewPrivate GeditPrintPreviewPrivate; +typedef struct _GeditPrintPreviewClass GeditPrintPreviewClass; + +struct _GeditPrintPreview +{ + GtkVBox parent; + GeditPrintPreviewPrivate *priv; +}; + +struct _GeditPrintPreviewClass +{ + GtkVBoxClass parent_class; + + void (* close) (GeditPrintPreview *preview); +}; + + +GType gedit_print_preview_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_print_preview_new (GtkPrintOperation *op, + GtkPrintOperationPreview *gtk_preview, + GtkPrintContext *context); + +G_END_DECLS + +#endif /* __GEDIT_PRINT_PREVIEW_H__ */ diff --git a/gedit/gedit-progress-message-area.c b/gedit/gedit-progress-message-area.c new file mode 100755 index 00000000..d408baee --- /dev/null +++ b/gedit/gedit-progress-message-area.c @@ -0,0 +1,261 @@ +/* + * gedit-progress-message-area.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-progress-message-area.h" + +enum { + PROP_0, + PROP_HAS_CANCEL_BUTTON +}; + + +#define GEDIT_PROGRESS_MESSAGE_AREA_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_PROGRESS_MESSAGE_AREA, GeditProgressMessageAreaPrivate)) + +struct _GeditProgressMessageAreaPrivate +{ + GtkWidget *image; + GtkWidget *label; + GtkWidget *progress; +}; + +#if !GTK_CHECK_VERSION (2, 17, 1) +G_DEFINE_TYPE(GeditProgressMessageArea, gedit_progress_message_area, GEDIT_TYPE_MESSAGE_AREA) +#else +G_DEFINE_TYPE(GeditProgressMessageArea, gedit_progress_message_area, GTK_TYPE_INFO_BAR) +#endif + +static void +gedit_progress_message_area_set_has_cancel_button (GeditProgressMessageArea *area, + gboolean has_button) +{ + if (has_button) +#if !GTK_CHECK_VERSION (2, 17, 1) + gedit_message_area_add_button (GEDIT_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 +gedit_progress_message_area_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditProgressMessageArea *area; + + area = GEDIT_PROGRESS_MESSAGE_AREA (object); + + switch (prop_id) + { + case PROP_HAS_CANCEL_BUTTON: + gedit_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 +gedit_progress_message_area_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditProgressMessageArea *area; + + area = GEDIT_PROGRESS_MESSAGE_AREA (object); + + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_progress_message_area_class_init (GeditProgressMessageAreaClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gedit_progress_message_area_set_property; + gobject_class->get_property = gedit_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(GeditProgressMessageAreaPrivate)); +} + +static void +gedit_progress_message_area_init (GeditProgressMessageArea *area) +{ + GtkWidget *vbox; + GtkWidget *hbox; + + area->priv = GEDIT_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) + gedit_message_area_set_contents (GEDIT_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 * +gedit_progress_message_area_new (const gchar *stock_id, + const gchar *markup, + gboolean has_cancel) +{ + GeditProgressMessageArea *area; + + g_return_val_if_fail (stock_id != NULL, NULL); + g_return_val_if_fail (markup != NULL, NULL); + + area = GEDIT_PROGRESS_MESSAGE_AREA (g_object_new (GEDIT_TYPE_PROGRESS_MESSAGE_AREA, + "has-cancel-button", has_cancel, + NULL)); + + gedit_progress_message_area_set_stock_image (area, + stock_id); + + gedit_progress_message_area_set_markup (area, + markup); + + return GTK_WIDGET (area); +} + +void +gedit_progress_message_area_set_stock_image (GeditProgressMessageArea *area, + const gchar *stock_id) +{ + g_return_if_fail (GEDIT_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 +gedit_progress_message_area_set_markup (GeditProgressMessageArea *area, + const gchar *markup) +{ + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (area)); + g_return_if_fail (markup != NULL); + + gtk_label_set_markup (GTK_LABEL (area->priv->label), + markup); +} + +void +gedit_progress_message_area_set_text (GeditProgressMessageArea *area, + const gchar *text) +{ + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (area)); + g_return_if_fail (text != NULL); + + gtk_label_set_text (GTK_LABEL (area->priv->label), + text); +} + +void +gedit_progress_message_area_set_fraction (GeditProgressMessageArea *area, + gdouble fraction) +{ + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (area)); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (area->priv->progress), + fraction); +} + +void +gedit_progress_message_area_pulse (GeditProgressMessageArea *area) +{ + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (area)); + + gtk_progress_bar_pulse (GTK_PROGRESS_BAR (area->priv->progress)); +} diff --git a/gedit/gedit-progress-message-area.h b/gedit/gedit-progress-message-area.h new file mode 100755 index 00000000..ed5d8e32 --- /dev/null +++ b/gedit/gedit-progress-message-area.h @@ -0,0 +1,112 @@ +/* + * gedit-progress-message-area.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_PROGRESS_MESSAGE_AREA_H__ +#define __GEDIT_PROGRESS_MESSAGE_AREA_H__ + +#if !GTK_CHECK_VERSION (2, 17, 1) +#include <gedit/gedit-message-area.h> +#else +#include <gtk/gtk.h> +#endif + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_PROGRESS_MESSAGE_AREA (gedit_progress_message_area_get_type()) +#define GEDIT_PROGRESS_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_PROGRESS_MESSAGE_AREA, GeditProgressMessageArea)) +#define GEDIT_PROGRESS_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_PROGRESS_MESSAGE_AREA, GeditProgressMessageAreaClass)) +#define GEDIT_IS_PROGRESS_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_PROGRESS_MESSAGE_AREA)) +#define GEDIT_IS_PROGRESS_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_PROGRESS_MESSAGE_AREA)) +#define GEDIT_PROGRESS_MESSAGE_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_PROGRESS_MESSAGE_AREA, GeditProgressMessageAreaClass)) + +/* Private structure type */ +typedef struct _GeditProgressMessageAreaPrivate GeditProgressMessageAreaPrivate; + +/* + * Main object structure + */ +typedef struct _GeditProgressMessageArea GeditProgressMessageArea; + +struct _GeditProgressMessageArea +{ +#if !GTK_CHECK_VERSION (2, 17, 1) + GeditMessageArea parent; +#else + GtkInfoBar parent; +#endif + + /*< private > */ + GeditProgressMessageAreaPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditProgressMessageAreaClass GeditProgressMessageAreaClass; + +struct _GeditProgressMessageAreaClass +{ +#if !GTK_CHECK_VERSION (2, 17, 1) + GeditMessageAreaClass parent_class; +#else + GtkInfoBarClass parent_class; +#endif +}; + +/* + * Public methods + */ +GType gedit_progress_message_area_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_progress_message_area_new (const gchar *stock_id, + const gchar *markup, + gboolean has_cancel); + +void gedit_progress_message_area_set_stock_image (GeditProgressMessageArea *area, + const gchar *stock_id); + +void gedit_progress_message_area_set_markup (GeditProgressMessageArea *area, + const gchar *markup); + +void gedit_progress_message_area_set_text (GeditProgressMessageArea *area, + const gchar *text); + +void gedit_progress_message_area_set_fraction (GeditProgressMessageArea *area, + gdouble fraction); + +void gedit_progress_message_area_pulse (GeditProgressMessageArea *area); + + +G_END_DECLS + +#endif /* __GEDIT_PROGRESS_MESSAGE_AREA_H__ */ diff --git a/gedit/gedit-session.c b/gedit/gedit-session.c new file mode 100755 index 00000000..3dc9f126 --- /dev/null +++ b/gedit/gedit-session.c @@ -0,0 +1,601 @@ +/* + * gedit-session.c - Basic session management for gedit + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-session.h" + +#include "gedit-debug.h" +#include "gedit-plugins-engine.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-metadata-manager.h" +#include "gedit-window.h" +#include "gedit-app.h" +#include "gedit-commands.h" +#include "dialogs/gedit-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 GEDIT_SESSION_LIST_OF_DOCS_TO_SAVE "gedit-session-list-of-docs-to-save-key" + +static void +save_window_session (GKeyFile *state_file, + const gchar *group_name, + GeditWindow *window) +{ + const gchar *role; + int width, height; + GeditPanel *panel; + GList *docs, *l; + GPtrArray *doc_array; + GeditDocument *active_document; + gchar *uri; + + gedit_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 = gedit_window_get_side_panel (window); + g_key_file_set_boolean (state_file, group_name, "side-panel-visible", + GTK_WIDGET_VISIBLE (panel)); + + panel = gedit_window_get_bottom_panel (window); + g_key_file_set_boolean (state_file, group_name, "bottom-panel-visible", + GTK_WIDGET_VISIBLE (panel)); + + active_document = gedit_window_get_active_document (window); + if (active_document) + { + uri = gedit_document_get_uri (active_document); + g_key_file_set_string (state_file, group_name, + "active-document", uri); + } + + docs = gedit_window_get_documents (window); + + doc_array = g_ptr_array_new (); + for (l = docs; l != NULL; l = g_list_next (l)) + { + uri = gedit_document_get_uri (GEDIT_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 = gedit_app_get_windows (gedit_app_get_default ()); + n = 1; + + while (windows != NULL) + { + group_name = g_strdup_printf ("gedit window %d", n); + save_window_session (state_file, + group_name, + GEDIT_WINDOW (windows->data)); + g_free (group_name); + + windows = g_list_next (windows); + n++; + } +} + +static void +window_handled (GeditWindow *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 (GeditWindow *window, + GParamSpec *pspec, + gpointer data) +{ + GeditWindowState state; + GList *unsaved_docs; + GList *docs_to_save; + GList *l; + gboolean done = TRUE; + + state = gedit_window_get_state (window); + + /* we are still saving */ + if (state & GEDIT_WINDOW_STATE_SAVING) + return; + + unsaved_docs = gedit_window_get_unsaved_documents (window); + + docs_to_save = g_object_get_data (G_OBJECT (window), + GEDIT_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), + GEDIT_SESSION_LIST_OF_DOCS_TO_SAVE, + NULL); + + window_handled (window); + } + + g_list_free (unsaved_docs); +} + +static void +close_confirmation_dialog_response_handler (GeditCloseConfirmationDialog *dlg, + gint response_id, + GeditWindow *window) +{ + GList *selected_documents; + GSList *l; + + gedit_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 = gedit_close_confirmation_dialog_get_selected_documents (dlg); + + g_return_if_fail (g_object_get_data (G_OBJECT (window), + GEDIT_SESSION_LIST_OF_DOCS_TO_SAVE) == NULL); + + g_object_set_data (G_OBJECT (window), + GEDIT_SESSION_LIST_OF_DOCS_TO_SAVE, + selected_documents); + + _gedit_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 (GeditWindow *window) +{ + GList *unsaved_docs; + GtkWidget *dlg; + + gedit_debug (DEBUG_SESSION); + + unsaved_docs = gedit_window_get_unsaved_documents (window); + + g_return_if_fail (unsaved_docs != NULL); + + if (unsaved_docs->next == NULL) + { + /* There is only one unsaved document */ + GeditTab *tab; + GeditDocument *doc; + + doc = GEDIT_DOCUMENT (unsaved_docs->data); + + tab = gedit_tab_get_from_document (doc); + g_return_if_fail (tab != NULL); + + gedit_window_set_active_tab (window, tab); + + dlg = gedit_close_confirmation_dialog_new_single ( + GTK_WINDOW (window), + doc, + TRUE); + } + else + { + dlg = gedit_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 (GEDIT_WINDOW (window_dirty_list->data)); +} + +/* quit_requested handler for the master client */ +static void +client_quit_requested_cb (EggSMClient *client, gpointer data) +{ + GeditApp *app; + const GList *l; + + gedit_debug (DEBUG_SESSION); + + app = gedit_app_get_default (); + + if (window_dirty_list != NULL) + { + g_critical ("global variable window_dirty_list not NULL"); + window_dirty_list = NULL; + } + + for (l = gedit_app_get_windows (app); l != NULL; l = l->next) + { + if (gedit_window_get_unsaved_documents (GEDIT_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 (); + + gedit_debug_message (DEBUG_SESSION, "END"); +} + +/* quit handler for the master client */ +static void +client_quit_cb (EggSMClient *client, gpointer data) +{ +#if 0 + gedit_debug (DEBUG_SESSION); + + if (!client->save_yourself_emitted) + gedit_file_close_all (); + + gedit_debug_message (DEBUG_FILE, "All files closed."); + + matecomponent_mdi_destroy (MATECOMPONENT_MDI (gedit_mdi)); + + gedit_debug_message (DEBUG_FILE, "Unref gedit_mdi."); + + g_object_unref (G_OBJECT (gedit_mdi)); + + gedit_debug_message (DEBUG_FILE, "Unref gedit_mdi: DONE"); + + gedit_debug_message (DEBUG_FILE, "Unref gedit_app_server."); + + matecomponent_object_unref (gedit_app_server); + + gedit_debug_message (DEBUG_FILE, "Unref gedit_app_server: DONE"); +#endif + + gtk_main_quit (); +} + +/** + * gedit_session_init: + * + * Initializes session management support. This function should be called near + * the beginning of the program. + **/ +void +gedit_session_init (void) +{ + gedit_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); +} + +/** + * gedit_session_is_restored: + * + * Returns whether this gedit 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 +gedit_session_is_restored (void) +{ + gboolean restored; + + gedit_debug (DEBUG_SESSION); + + if (!master_client) + return FALSE; + + restored = egg_sm_client_is_resumed (master_client); + + gedit_debug_message (DEBUG_SESSION, restored ? "RESTORED" : "NOT RESTORED"); + + return restored; +} + +static void +parse_window (GKeyFile *state_file, const char *group_name) +{ + GeditWindow *window; + gchar *role, *active_document, **documents; + int width, height; + gboolean visible; + GeditPanel *panel; + GError *error = NULL; + + role = g_key_file_get_string (state_file, group_name, "role", NULL); + + gedit_debug_message (DEBUG_SESSION, "Window role: %s", role); + + window = _gedit_app_restore_window (gedit_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 = gedit_window_get_side_panel (window); + + if (visible) + { + gedit_debug_message (DEBUG_SESSION, "Side panel visible"); + gtk_widget_show (GTK_WIDGET (panel)); + } + else + { + gedit_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 = gedit_window_get_bottom_panel (window); + if (visible) + { + gedit_debug_message (DEBUG_SESSION, "Bottom panel visible"); + gtk_widget_show (GTK_WIDGET (panel)); + } + else + { + gedit_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; + + gedit_debug_message (DEBUG_SESSION, + "URI: %s (%s)", + documents[i], + jump_to ? "active" : "not active"); + gedit_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)); +} + +/** + * gedit_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 +gedit_session_load (void) +{ + GKeyFile *state_file; + gchar **groups; + int i; + + gedit_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], "gedit window ")) + parse_window (state_file, groups[i]); + } + + g_strfreev (groups); + g_key_file_free (state_file); + + return TRUE; +} diff --git a/gedit/gedit-session.h b/gedit/gedit-session.h new file mode 100755 index 00000000..ba59c131 --- /dev/null +++ b/gedit/gedit-session.h @@ -0,0 +1,47 @@ +/* + * gedit-session.h - Basic session management for gedit + * This file is part of gedit + * + * 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 gedit Team, 2002-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id + */ + +#ifndef __GEDIT_SESSION_H__ +#define __GEDIT_SESSION_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +void gedit_session_init (void); +gboolean gedit_session_is_restored (void); +gboolean gedit_session_load (void); + +G_END_DECLS + +#endif /* __GEDIT_SESSION_H__ */ diff --git a/gedit/gedit-smart-charset-converter.c b/gedit/gedit-smart-charset-converter.c new file mode 100755 index 00000000..e32b0b17 --- /dev/null +++ b/gedit/gedit-smart-charset-converter.c @@ -0,0 +1,422 @@ +/* + * gedit-smart-charset-converter.c + * This file is part of gedit + * + * Copyright (C) 2009 - Ignacio Casal Quinteiro + * + * gedit 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. + * + * gedit 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 gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "gedit-smart-charset-converter.h" +#include "gedit-debug.h" +#include "gedit-document.h" + +#include <gio/gio.h> +#include <glib/gi18n.h> + +#define GEDIT_SMART_CHARSET_CONVERTER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_SMART_CHARSET_CONVERTER, GeditSmartCharsetConverterPrivate)) + +struct _GeditSmartCharsetConverterPrivate +{ + GCharsetConverter *charset_conv; + + GSList *encodings; + GSList *current_encoding; + + guint is_utf8 : 1; + guint use_first : 1; +}; + +static void gedit_smart_charset_converter_iface_init (GConverterIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GeditSmartCharsetConverter, gedit_smart_charset_converter, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, + gedit_smart_charset_converter_iface_init)) + +static void +gedit_smart_charset_converter_finalize (GObject *object) +{ + GeditSmartCharsetConverter *smart = GEDIT_SMART_CHARSET_CONVERTER (object); + + g_slist_free (smart->priv->encodings); + + gedit_debug_message (DEBUG_UTILS, "finalizing smart charset converter"); + + G_OBJECT_CLASS (gedit_smart_charset_converter_parent_class)->finalize (object); +} + +static void +gedit_smart_charset_converter_dispose (GObject *object) +{ + GeditSmartCharsetConverter *smart = GEDIT_SMART_CHARSET_CONVERTER (object); + + if (smart->priv->charset_conv != NULL) + { + g_object_unref (smart->priv->charset_conv); + smart->priv->charset_conv = NULL; + } + + gedit_debug_message (DEBUG_UTILS, "disposing smart charset converter"); + + G_OBJECT_CLASS (gedit_smart_charset_converter_parent_class)->dispose (object); +} + +static void +gedit_smart_charset_converter_class_init (GeditSmartCharsetConverterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_smart_charset_converter_finalize; + object_class->dispose = gedit_smart_charset_converter_dispose; + + g_type_class_add_private (object_class, sizeof (GeditSmartCharsetConverterPrivate)); +} + +static void +gedit_smart_charset_converter_init (GeditSmartCharsetConverter *smart) +{ + smart->priv = GEDIT_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; + + gedit_debug_message (DEBUG_UTILS, "initializing smart charset converter"); +} + +static const GeditEncoding * +get_encoding (GeditSmartCharsetConverter *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 GeditEncoding *)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 GeditEncoding *)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 (GeditSmartCharsetConverter *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 GeditEncoding *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; + } + + gedit_debug_message (DEBUG_UTILS, "trying charset: %s", + gedit_encoding_get_charset (smart->priv->current_encoding->data)); + + if (enc == gedit_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", + gedit_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 +gedit_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) +{ + GeditSmartCharsetConverter *smart = GEDIT_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 gedit_convert */ + g_set_error_literal (error, GEDIT_DOCUMENT_ERROR, + GEDIT_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 +gedit_smart_charset_converter_reset (GConverter *converter) +{ + GeditSmartCharsetConverter *smart = GEDIT_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 +gedit_smart_charset_converter_iface_init (GConverterIface *iface) +{ + iface->convert = gedit_smart_charset_converter_convert; + iface->reset = gedit_smart_charset_converter_reset; +} + +GeditSmartCharsetConverter * +gedit_smart_charset_converter_new (GSList *candidate_encodings) +{ + GeditSmartCharsetConverter *smart; + + g_return_val_if_fail (candidate_encodings != NULL, NULL); + + smart = g_object_new (GEDIT_TYPE_SMART_CHARSET_CONVERTER, NULL); + + smart->priv->encodings = g_slist_copy (candidate_encodings); + + return smart; +} + +const GeditEncoding * +gedit_smart_charset_converter_get_guessed (GeditSmartCharsetConverter *smart) +{ + g_return_val_if_fail (GEDIT_IS_SMART_CHARSET_CONVERTER (smart), NULL); + + if (smart->priv->current_encoding != NULL) + { + return (const GeditEncoding *)smart->priv->current_encoding->data; + } + else if (smart->priv->is_utf8) + { + return gedit_encoding_get_utf8 (); + } + + return NULL; +} + +guint +gedit_smart_charset_converter_get_num_fallbacks (GeditSmartCharsetConverter *smart) +{ + g_return_val_if_fail (GEDIT_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/gedit/gedit-smart-charset-converter.h b/gedit/gedit-smart-charset-converter.h new file mode 100755 index 00000000..803e07a5 --- /dev/null +++ b/gedit/gedit-smart-charset-converter.h @@ -0,0 +1,66 @@ +/* + * gedit-smart-charset-converter.h + * This file is part of gedit + * + * Copyright (C) 2009 - Ignacio Casal Quinteiro + * + * gedit 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. + * + * gedit 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 gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef __GEDIT_SMART_CHARSET_CONVERTER_H__ +#define __GEDIT_SMART_CHARSET_CONVERTER_H__ + +#include <glib-object.h> + +#include "gedit-encodings.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_SMART_CHARSET_CONVERTER (gedit_smart_charset_converter_get_type ()) +#define GEDIT_SMART_CHARSET_CONVERTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_SMART_CHARSET_CONVERTER, GeditSmartCharsetConverter)) +#define GEDIT_SMART_CHARSET_CONVERTER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_SMART_CHARSET_CONVERTER, GeditSmartCharsetConverter const)) +#define GEDIT_SMART_CHARSET_CONVERTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_SMART_CHARSET_CONVERTER, GeditSmartCharsetConverterClass)) +#define GEDIT_IS_SMART_CHARSET_CONVERTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_SMART_CHARSET_CONVERTER)) +#define GEDIT_IS_SMART_CHARSET_CONVERTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SMART_CHARSET_CONVERTER)) +#define GEDIT_SMART_CHARSET_CONVERTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_SMART_CHARSET_CONVERTER, GeditSmartCharsetConverterClass)) + +typedef struct _GeditSmartCharsetConverter GeditSmartCharsetConverter; +typedef struct _GeditSmartCharsetConverterClass GeditSmartCharsetConverterClass; +typedef struct _GeditSmartCharsetConverterPrivate GeditSmartCharsetConverterPrivate; + +struct _GeditSmartCharsetConverter +{ + GObject parent; + + GeditSmartCharsetConverterPrivate *priv; +}; + +struct _GeditSmartCharsetConverterClass +{ + GObjectClass parent_class; +}; + +GType gedit_smart_charset_converter_get_type (void) G_GNUC_CONST; + +GeditSmartCharsetConverter *gedit_smart_charset_converter_new (GSList *candidate_encodings); + +const GeditEncoding *gedit_smart_charset_converter_get_guessed (GeditSmartCharsetConverter *smart); + +guint gedit_smart_charset_converter_get_num_fallbacks(GeditSmartCharsetConverter *smart); + +G_END_DECLS + +#endif /* __GEDIT_SMART_CHARSET_CONVERTER_H__ */ diff --git a/gedit/gedit-spinner.c b/gedit/gedit-spinner.c new file mode 100755 index 00000000..5cb74587 --- /dev/null +++ b/gedit/gedit-spinner.c @@ -0,0 +1,989 @@ +/* + * gedit-spinner.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-spinner.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gtk/gtk.h> + +/* Spinner cache implementation */ + +#define GEDIT_TYPE_SPINNER_CACHE (gedit_spinner_cache_get_type()) +#define GEDIT_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), GEDIT_TYPE_SPINNER_CACHE, GeditSpinnerCache)) +#define GEDIT_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_SPINNER_CACHE, GeditSpinnerCacheClass)) +#define GEDIT_IS_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), GEDIT_TYPE_SPINNER_CACHE)) +#define GEDIT_IS_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GEDIT_TYPE_SPINNER_CACHE)) +#define GEDIT_SPINNER_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_SPINNER_CACHE, GeditSpinnerCacheClass)) + +typedef struct _GeditSpinnerCache GeditSpinnerCache; +typedef struct _GeditSpinnerCacheClass GeditSpinnerCacheClass; +typedef struct _GeditSpinnerCachePrivate GeditSpinnerCachePrivate; + +struct _GeditSpinnerCacheClass +{ + GObjectClass parent_class; +}; + +struct _GeditSpinnerCache +{ + GObject parent_object; + + /*< private >*/ + GeditSpinnerCachePrivate *priv; +}; + +#define GEDIT_SPINNER_CACHE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_SPINNER_CACHE, GeditSpinnerCachePrivate)) + +struct _GeditSpinnerCachePrivate +{ + /* Hash table of GdkScreen -> GeditSpinnerCacheData */ + GHashTable *hash; +}; + +typedef struct +{ + guint ref_count; + GtkIconSize size; + gint width; + gint height; + GdkPixbuf **animation_pixbufs; + guint n_animation_pixbufs; +} GeditSpinnerImages; + +#define LAST_ICON_SIZE GTK_ICON_SIZE_DIALOG + 1 +#define SPINNER_ICON_NAME "process-working" +#define SPINNER_FALLBACK_ICON_NAME "mate-spinner" +#define GEDIT_SPINNER_IMAGES_INVALID ((GeditSpinnerImages *) 0x1) + +typedef struct +{ + GdkScreen *screen; + GtkIconTheme *icon_theme; + GeditSpinnerImages *images[LAST_ICON_SIZE]; +} GeditSpinnerCacheData; + +static void gedit_spinner_cache_class_init (GeditSpinnerCacheClass *klass); +static void gedit_spinner_cache_init (GeditSpinnerCache *cache); + +static GObjectClass *gedit_spinner_cache_parent_class; + +static GType +gedit_spinner_cache_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + { + const GTypeInfo our_info = + { + sizeof (GeditSpinnerCacheClass), + NULL, + NULL, + (GClassInitFunc) gedit_spinner_cache_class_init, + NULL, + NULL, + sizeof (GeditSpinnerCache), + 0, + (GInstanceInitFunc) gedit_spinner_cache_init + }; + + type = g_type_register_static (G_TYPE_OBJECT, + "GeditSpinnerCache", + &our_info, 0); + } + + return type; +} + +static GeditSpinnerImages * +gedit_spinner_images_ref (GeditSpinnerImages *images) +{ + g_return_val_if_fail (images != NULL, NULL); + + images->ref_count++; + + return images; +} + +static void +gedit_spinner_images_unref (GeditSpinnerImages *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 +gedit_spinner_cache_data_unload (GeditSpinnerCacheData *data) +{ + GtkIconSize size; + GeditSpinnerImages *images; + + g_return_if_fail (data != NULL); + + /* LOG ("GeditSpinnerDataCache 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 != GEDIT_SPINNER_IMAGES_INVALID) + { + gedit_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 GeditSpinnerImages * +gedit_spinner_images_load (GdkScreen *screen, + GtkIconTheme *icon_theme, + GtkIconSize icon_size) +{ + GeditSpinnerImages *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 ("GeditSpinnerCacheData 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 (GeditSpinnerImages, 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 GeditSpinnerCacheData * +gedit_spinner_cache_data_new (GdkScreen *screen) +{ + GeditSpinnerCacheData *data; + + data = g_new0 (GeditSpinnerCacheData, 1); + + data->screen = screen; + data->icon_theme = gtk_icon_theme_get_for_screen (screen); + g_signal_connect_swapped (data->icon_theme, + "changed", + G_CALLBACK (gedit_spinner_cache_data_unload), + data); + + return data; +} + +static void +gedit_spinner_cache_data_free (GeditSpinnerCacheData *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 (gedit_spinner_cache_data_unload), + data); + + gedit_spinner_cache_data_unload (data); + + g_free (data); +} + +static GeditSpinnerImages * +gedit_spinner_cache_get_images (GeditSpinnerCache *cache, + GdkScreen *screen, + GtkIconSize icon_size) +{ + GeditSpinnerCachePrivate *priv = cache->priv; + GeditSpinnerCacheData *data; + GeditSpinnerImages *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 = gedit_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 == GEDIT_SPINNER_IMAGES_INVALID) + { + /* Load failed, but don't try endlessly again! */ + return NULL; + } + + if (images != NULL) + { + /* Return cached data */ + return gedit_spinner_images_ref (images); + } + + images = gedit_spinner_images_load (screen, data->icon_theme, icon_size); + + if (images == NULL) + { + /* Mark as failed-to-load */ + data->images[icon_size] = GEDIT_SPINNER_IMAGES_INVALID; + + return NULL; + } + + data->images[icon_size] = images; + + return gedit_spinner_images_ref (images); +} + +static void +gedit_spinner_cache_init (GeditSpinnerCache *cache) +{ + GeditSpinnerCachePrivate *priv; + + priv = cache->priv = GEDIT_SPINNER_CACHE_GET_PRIVATE (cache); + + /* LOG ("GeditSpinnerCache initialising"); */ + + priv->hash = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify) gedit_spinner_cache_data_free); +} + +static void +gedit_spinner_cache_finalize (GObject *object) +{ + GeditSpinnerCache *cache = GEDIT_SPINNER_CACHE (object); + GeditSpinnerCachePrivate *priv = cache->priv; + + g_hash_table_destroy (priv->hash); + + /* LOG ("GeditSpinnerCache finalised"); */ + + G_OBJECT_CLASS (gedit_spinner_cache_parent_class)->finalize (object); +} + +static void +gedit_spinner_cache_class_init (GeditSpinnerCacheClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gedit_spinner_cache_parent_class = g_type_class_peek_parent (klass); + + object_class->finalize = gedit_spinner_cache_finalize; + + g_type_class_add_private (object_class, sizeof (GeditSpinnerCachePrivate)); +} + +static GeditSpinnerCache *spinner_cache = NULL; + +static GeditSpinnerCache * +gedit_spinner_cache_ref (void) +{ + if (spinner_cache == NULL) + { + GeditSpinnerCache **cache_ptr; + + spinner_cache = g_object_new (GEDIT_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 GEDIT_SPINNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_SPINNER, GeditSpinnerPrivate)) + +struct _GeditSpinnerPrivate +{ + GtkIconTheme *icon_theme; + GeditSpinnerCache *cache; + GtkIconSize size; + GeditSpinnerImages *images; + guint current_image; + guint timeout; + guint timer_task; + guint spinning : 1; + guint need_load : 1; +}; + +static void gedit_spinner_class_init (GeditSpinnerClass *class); +static void gedit_spinner_init (GeditSpinner *spinner); + +static GObjectClass *parent_class; + +GType +gedit_spinner_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + { + const GTypeInfo our_info = + { + sizeof (GeditSpinnerClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gedit_spinner_class_init, + NULL, + NULL, /* class_data */ + sizeof (GeditSpinner), + 0, /* n_preallocs */ + (GInstanceInitFunc) gedit_spinner_init + }; + + type = g_type_register_static (GTK_TYPE_WIDGET, + "GeditSpinner", + &our_info, 0); + } + + return type; +} + +static gboolean +gedit_spinner_load_images (GeditSpinner *spinner) +{ + GeditSpinnerPrivate *priv = spinner->priv; + + if (priv->need_load) + { + /* START_PROFILER ("gedit_spinner_load_images") */ + + priv->images = + gedit_spinner_cache_get_images (priv->cache, + gtk_widget_get_screen (GTK_WIDGET (spinner)), + priv->size); + + /* STOP_PROFILER ("gedit_spinner_load_images") */ + + priv->current_image = 0; /* 'rest' icon */ + priv->need_load = FALSE; + } + + return priv->images != NULL; +} + +static void +gedit_spinner_unload_images (GeditSpinner *spinner) +{ + GeditSpinnerPrivate *priv = spinner->priv; + + if (priv->images != NULL) + { + gedit_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, + GeditSpinner *spinner) +{ + gedit_spinner_unload_images (spinner); + gtk_widget_queue_resize (GTK_WIDGET (spinner)); +} + +static void +gedit_spinner_init (GeditSpinner *spinner) +{ + GeditSpinnerPrivate *priv; + + priv = spinner->priv = GEDIT_SPINNER_GET_PRIVATE (spinner); + + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (spinner), GTK_NO_WINDOW); + + priv->cache = gedit_spinner_cache_ref (); + priv->size = GTK_ICON_SIZE_DIALOG; + priv->spinning = FALSE; + priv->timeout = SPINNER_TIMEOUT; + priv->need_load = TRUE; +} + +static int +gedit_spinner_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GeditSpinner *spinner = GEDIT_SPINNER (widget); + GeditSpinnerPrivate *priv = spinner->priv; + GeditSpinnerImages *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 && + !gedit_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 (GeditSpinner *spinner) +{ + GeditSpinnerPrivate *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; +} + +/** + * gedit_spinner_start: + * @spinner: a #GeditSpinner + * + * Start the spinner animation. + **/ +void +gedit_spinner_start (GeditSpinner *spinner) +{ + GeditSpinnerPrivate *priv = spinner->priv; + + priv->spinning = TRUE; + + if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)) && + priv->timer_task == 0 && + gedit_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 +gedit_spinner_remove_update_callback (GeditSpinner *spinner) +{ + GeditSpinnerPrivate *priv = spinner->priv; + + if (priv->timer_task != 0) + { + g_source_remove (priv->timer_task); + priv->timer_task = 0; + } +} + +/** + * gedit_spinner_stop: + * @spinner: a #GeditSpinner + * + * Stop the spinner animation. + **/ +void +gedit_spinner_stop (GeditSpinner *spinner) +{ + GeditSpinnerPrivate *priv = spinner->priv; + + priv->spinning = FALSE; + priv->current_image = 0; + + if (priv->timer_task != 0) + { + gedit_spinner_remove_update_callback (spinner); + + if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner))) + gtk_widget_queue_draw (GTK_WIDGET (spinner)); + } +} + +/* + * gedit_spinner_set_size: + * @spinner: a #GeditSpinner + * @size: the size of type %GtkIconSize + * + * Set the size of the spinner. + **/ +void +gedit_spinner_set_size (GeditSpinner *spinner, + GtkIconSize size) +{ + if (size == GTK_ICON_SIZE_INVALID) + { + size = GTK_ICON_SIZE_DIALOG; + } + + if (size != spinner->priv->size) + { + gedit_spinner_unload_images (spinner); + + spinner->priv->size = size; + + gtk_widget_queue_resize (GTK_WIDGET (spinner)); + } +} + +#if 0 +/* +* gedit_spinner_set_timeout: +* @spinner: a #GeditSpinner +* @timeout: time delay between updates to the spinner. +* +* Sets the timeout delay for spinner updates. +**/ +void +gedit_spinner_set_timeout (GeditSpinner *spinner, + guint timeout) +{ + GeditSpinnerPrivate *priv = spinner->priv; + + if (timeout != priv->timeout) + { + gedit_spinner_stop (spinner); + + priv->timeout = timeout; + + if (priv->spinning) + { + gedit_spinner_start (spinner); + } + } +} +#endif + +static void +gedit_spinner_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GeditSpinner *spinner = GEDIT_SPINNER (widget); + GeditSpinnerPrivate *priv = spinner->priv; + + if ((priv->need_load && + !gedit_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 +gedit_spinner_map (GtkWidget *widget) +{ + GeditSpinner *spinner = GEDIT_SPINNER (widget); + GeditSpinnerPrivate *priv = spinner->priv; + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + if (priv->spinning) + { + gedit_spinner_start (spinner); + } +} + +static void +gedit_spinner_unmap (GtkWidget *widget) +{ + GeditSpinner *spinner = GEDIT_SPINNER (widget); + + gedit_spinner_remove_update_callback (spinner); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gedit_spinner_dispose (GObject *object) +{ + GeditSpinner *spinner = GEDIT_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 +gedit_spinner_finalize (GObject *object) +{ + GeditSpinner *spinner = GEDIT_SPINNER (object); + + gedit_spinner_remove_update_callback (spinner); + gedit_spinner_unload_images (spinner); + + g_object_unref (spinner->priv->cache); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gedit_spinner_screen_changed (GtkWidget *widget, + GdkScreen *old_screen) +{ + GeditSpinner *spinner = GEDIT_SPINNER (widget); + GeditSpinnerPrivate *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. + */ + gedit_spinner_remove_update_callback (spinner); + + gedit_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 +gedit_spinner_class_init (GeditSpinnerClass *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 = gedit_spinner_dispose; + object_class->finalize = gedit_spinner_finalize; + + widget_class->expose_event = gedit_spinner_expose; + widget_class->size_request = gedit_spinner_size_request; + widget_class->map = gedit_spinner_map; + widget_class->unmap = gedit_spinner_unmap; + widget_class->screen_changed = gedit_spinner_screen_changed; + + g_type_class_add_private (object_class, sizeof (GeditSpinnerPrivate)); +} + +/* + * gedit_spinner_new: + * + * Create a new #GeditSpinner. The spinner is a widget + * that gives the user feedback about network status with + * an animated image. + * + * Return Value: the spinner #GtkWidget + **/ +GtkWidget * +gedit_spinner_new (void) +{ + return GTK_WIDGET (g_object_new (GEDIT_TYPE_SPINNER, NULL)); +} diff --git a/gedit/gedit-spinner.h b/gedit/gedit-spinner.h new file mode 100755 index 00000000..807ba7c3 --- /dev/null +++ b/gedit/gedit-spinner.h @@ -0,0 +1,95 @@ +/* + * gedit-spinner.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_SPINNER_H__ +#define __GEDIT_SPINNER_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_SPINNER (gedit_spinner_get_type ()) +#define GEDIT_SPINNER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_SPINNER, GeditSpinner)) +#define GEDIT_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_SPINNER, GeditSpinnerClass)) +#define GEDIT_IS_SPINNER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_SPINNER)) +#define GEDIT_IS_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_SPINNER)) +#define GEDIT_SPINNER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_SPINNER, GeditSpinnerClass)) + + +/* Private structure type */ +typedef struct _GeditSpinnerPrivate GeditSpinnerPrivate; + +/* + * Main object structure + */ +typedef struct _GeditSpinner GeditSpinner; + +struct _GeditSpinner +{ + GtkWidget parent; + + /*< private >*/ + GeditSpinnerPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditSpinnerClass GeditSpinnerClass; + +struct _GeditSpinnerClass +{ + GtkWidgetClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_spinner_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_spinner_new (void); + +void gedit_spinner_start (GeditSpinner *throbber); + +void gedit_spinner_stop (GeditSpinner *throbber); + +void gedit_spinner_set_size (GeditSpinner *spinner, + GtkIconSize size); + +G_END_DECLS + +#endif /* __GEDIT_SPINNER_H__ */ diff --git a/gedit/gedit-status-combo-box.c b/gedit/gedit-status-combo-box.c new file mode 100755 index 00000000..71ea8c9f --- /dev/null +++ b/gedit/gedit-status-combo-box.c @@ -0,0 +1,418 @@ +/* + * gedit-status-combo-box.c + * This file is part of gedit + * + * 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 "gedit-status-combo-box.h" + +#define COMBO_BOX_TEXT_DATA "GeditStatusComboBoxTextData" + +#define GEDIT_STATUS_COMBO_BOX_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_STATUS_COMBO_BOX, GeditStatusComboBoxPrivate)) + +struct _GeditStatusComboBoxPrivate +{ + 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(GeditStatusComboBox, gedit_status_combo_box, GTK_TYPE_EVENT_BOX) + +static void +gedit_status_combo_box_finalize (GObject *object) +{ + G_OBJECT_CLASS (gedit_status_combo_box_parent_class)->finalize (object); +} + +static void +gedit_status_combo_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditStatusComboBox *obj = GEDIT_STATUS_COMBO_BOX (object); + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, gedit_status_combo_box_get_label (obj)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_status_combo_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditStatusComboBox *obj = GEDIT_STATUS_COMBO_BOX (object); + + switch (prop_id) + { + case PROP_LABEL: + gedit_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 +gedit_status_combo_box_changed (GeditStatusComboBox *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 +gedit_status_combo_box_class_init (GeditStatusComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_status_combo_box_finalize; + object_class->get_property = gedit_status_combo_box_get_property; + object_class->set_property = gedit_status_combo_box_set_property; + + klass->changed = gedit_status_combo_box_changed; + + signals[CHANGED] = + g_signal_new ("changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditStatusComboBoxClass, + 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 \"gedit-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 \"*.gedit-status-combo-button\" style \"gedit-status-combo-button-style\""); + + g_type_class_add_private (object_class, sizeof(GeditStatusComboBoxPrivate)); +} + +static void +menu_deactivate (GtkMenu *menu, + GeditStatusComboBox *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, + GeditStatusComboBox *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, + GeditStatusComboBox *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 (GeditStatusComboBox *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 +gedit_status_combo_box_init (GeditStatusComboBox *self) +{ + self->priv = GEDIT_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, "gedit-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 * +gedit_status_combo_box_new (const gchar *label) +{ + return g_object_new (GEDIT_TYPE_STATUS_COMBO_BOX, "label", label, NULL); +} + +void +gedit_status_combo_box_set_label (GeditStatusComboBox *combo, + const gchar *label) +{ + gchar *text; + + g_return_if_fail (GEDIT_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 * +gedit_status_combo_box_get_label (GeditStatusComboBox *combo) +{ + g_return_val_if_fail (GEDIT_IS_STATUS_COMBO_BOX (combo), NULL); + + return gtk_label_get_label (GTK_LABEL (combo->priv->label)); +} + +static void +item_activated (GtkMenuItem *item, + GeditStatusComboBox *combo) +{ + gedit_status_combo_box_set_item (combo, item); +} + +void +gedit_status_combo_box_add_item (GeditStatusComboBox *combo, + GtkMenuItem *item, + const gchar *text) +{ + g_return_if_fail (GEDIT_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)); + + gedit_status_combo_box_set_item_text (combo, item, text); + g_signal_connect (item, "activate", G_CALLBACK (item_activated), combo); +} + +void +gedit_status_combo_box_remove_item (GeditStatusComboBox *combo, + GtkMenuItem *item) +{ + g_return_if_fail (GEDIT_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 * +gedit_status_combo_box_get_items (GeditStatusComboBox *combo) +{ + g_return_val_if_fail (GEDIT_IS_STATUS_COMBO_BOX (combo), NULL); + + return gtk_container_get_children (GTK_CONTAINER (combo->priv->menu)); +} + +const gchar * +gedit_status_combo_box_get_item_text (GeditStatusComboBox *combo, + GtkMenuItem *item) +{ + const gchar *ret = NULL; + + g_return_val_if_fail (GEDIT_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 +gedit_status_combo_box_set_item_text (GeditStatusComboBox *combo, + GtkMenuItem *item, + const gchar *text) +{ + g_return_if_fail (GEDIT_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 +gedit_status_combo_box_set_item (GeditStatusComboBox *combo, + GtkMenuItem *item) +{ + g_return_if_fail (GEDIT_IS_STATUS_COMBO_BOX (combo)); + g_return_if_fail (GTK_IS_MENU_ITEM (item)); + + g_signal_emit (combo, signals[CHANGED], 0, item, NULL); +} + +GtkLabel * +gedit_status_combo_box_get_item_label (GeditStatusComboBox *combo) +{ + g_return_val_if_fail (GEDIT_IS_STATUS_COMBO_BOX (combo), NULL); + + return GTK_LABEL (combo->priv->item); +} + diff --git a/gedit/gedit-status-combo-box.h b/gedit/gedit-status-combo-box.h new file mode 100755 index 00000000..e3593a8a --- /dev/null +++ b/gedit/gedit-status-combo-box.h @@ -0,0 +1,82 @@ +/* + * gedit-status-combo-box.h + * This file is part of gedit + * + * 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 __GEDIT_STATUS_COMBO_BOX_H__ +#define __GEDIT_STATUS_COMBO_BOX_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_STATUS_COMBO_BOX (gedit_status_combo_box_get_type ()) +#define GEDIT_STATUS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_STATUS_COMBO_BOX, GeditStatusComboBox)) +#define GEDIT_STATUS_COMBO_BOX_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_STATUS_COMBO_BOX, GeditStatusComboBox const)) +#define GEDIT_STATUS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_STATUS_COMBO_BOX, GeditStatusComboBoxClass)) +#define GEDIT_IS_STATUS_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_STATUS_COMBO_BOX)) +#define GEDIT_IS_STATUS_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_STATUS_COMBO_BOX)) +#define GEDIT_STATUS_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_STATUS_COMBO_BOX, GeditStatusComboBoxClass)) + +typedef struct _GeditStatusComboBox GeditStatusComboBox; +typedef struct _GeditStatusComboBoxClass GeditStatusComboBoxClass; +typedef struct _GeditStatusComboBoxPrivate GeditStatusComboBoxPrivate; + +struct _GeditStatusComboBox { + GtkEventBox parent; + + GeditStatusComboBoxPrivate *priv; +}; + +struct _GeditStatusComboBoxClass { + GtkEventBoxClass parent_class; + + void (*changed) (GeditStatusComboBox *combo, + GtkMenuItem *item); +}; + +GType gedit_status_combo_box_get_type (void) G_GNUC_CONST; +GtkWidget *gedit_status_combo_box_new (const gchar *label); + +const gchar *gedit_status_combo_box_get_label (GeditStatusComboBox *combo); +void gedit_status_combo_box_set_label (GeditStatusComboBox *combo, + const gchar *label); + +void gedit_status_combo_box_add_item (GeditStatusComboBox *combo, + GtkMenuItem *item, + const gchar *text); +void gedit_status_combo_box_remove_item (GeditStatusComboBox *combo, + GtkMenuItem *item); + +GList *gedit_status_combo_box_get_items (GeditStatusComboBox *combo); +const gchar *gedit_status_combo_box_get_item_text (GeditStatusComboBox *combo, + GtkMenuItem *item); +void gedit_status_combo_box_set_item_text (GeditStatusComboBox *combo, + GtkMenuItem *item, + const gchar *text); + +void gedit_status_combo_box_set_item (GeditStatusComboBox *combo, + GtkMenuItem *item); + +GtkLabel *gedit_status_combo_box_get_item_label (GeditStatusComboBox *combo); + +G_END_DECLS + +#endif /* __GEDIT_STATUS_COMBO_BOX_H__ */ diff --git a/gedit/gedit-statusbar.c b/gedit/gedit-statusbar.c new file mode 100755 index 00000000..178147c8 --- /dev/null +++ b/gedit/gedit-statusbar.c @@ -0,0 +1,448 @@ +/* + * gedit-statusbar.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-statusbar.h" + +#define GEDIT_STATUSBAR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object),\ + GEDIT_TYPE_STATUSBAR,\ + GeditStatusbarPrivate)) + +struct _GeditStatusbarPrivate +{ + 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(GeditStatusbar, gedit_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 +gedit_statusbar_notify (GObject *object, + GParamSpec *pspec) +{ + /* don't allow gtk_statusbar_set_has_resize_grip to mess with us. + * See _gedit_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 (gedit_statusbar_parent_class)->notify) + G_OBJECT_CLASS (gedit_statusbar_parent_class)->notify (object, pspec); +} + +static void +gedit_statusbar_finalize (GObject *object) +{ + GeditStatusbar *statusbar = GEDIT_STATUSBAR (object); + + if (statusbar->priv->flash_timeout > 0) + g_source_remove (statusbar->priv->flash_timeout); + + G_OBJECT_CLASS (gedit_statusbar_parent_class)->finalize (object); +} + +static void +gedit_statusbar_class_init (GeditStatusbarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->notify = gedit_statusbar_notify; + object_class->finalize = gedit_statusbar_finalize; + + g_type_class_add_private (object_class, sizeof (GeditStatusbarPrivate)); +} + +#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 +gedit_statusbar_init (GeditStatusbar *statusbar) +{ + GtkWidget *hbox; + GtkWidget *error_image; + + statusbar->priv = GEDIT_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); +} + +/** + * gedit_statusbar_new: + * + * Creates a new #GeditStatusbar. + * + * Return value: the new #GeditStatusbar object + **/ +GtkWidget * +gedit_statusbar_new (void) +{ + return GTK_WIDGET (g_object_new (GEDIT_TYPE_STATUSBAR, NULL)); +} + +/** + * gedit_set_has_resize_grip: + * @statusbar: a #GeditStatusbar + * @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 +_gedit_statusbar_set_has_resize_grip (GeditStatusbar *bar, + gboolean show) +{ + g_return_if_fail (GEDIT_IS_STATUSBAR (bar)); + + gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (bar->priv->overwrite_mode_statusbar), + show); +} + +/** + * gedit_statusbar_set_overwrite: + * @statusbar: a #GeditStatusbar + * @overwrite: if the overwrite mode is set + * + * Sets the overwrite mode on the statusbar. + **/ +void +gedit_statusbar_set_overwrite (GeditStatusbar *statusbar, + gboolean overwrite) +{ + gchar *msg; + + g_return_if_fail (GEDIT_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 +gedit_statusbar_clear_overwrite (GeditStatusbar *statusbar) +{ + g_return_if_fail (GEDIT_IS_STATUSBAR (statusbar)); + + gtk_statusbar_pop (GTK_STATUSBAR (statusbar->priv->overwrite_mode_statusbar), 0); +} + +/** + * gedit_statusbar_cursor_position: + * @statusbar: an #GeditStatusbar + * @line: line position + * @col: column position + * + * Sets the cursor position on the statusbar. + **/ +void +gedit_statusbar_set_cursor_position (GeditStatusbar *statusbar, + gint line, + gint col) +{ + gchar *msg; + + g_return_if_fail (GEDIT_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 (GeditStatusbar *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; +} + +/** + * gedit_statusbar_flash_message: + * @statusbar: a #GeditStatusbar + * @context_id: message context_id + * @format: message to flash on the statusbar + * + * Flash a temporary message on the statusbar. + */ +void +gedit_statusbar_flash_message (GeditStatusbar *statusbar, + guint context_id, + const gchar *format, ...) +{ + const guint32 flash_length = 3000; /* three seconds */ + va_list args; + gchar *msg; + + g_return_if_fail (GEDIT_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 +gedit_statusbar_set_window_state (GeditStatusbar *statusbar, + GeditWindowState state, + gint num_of_errors) +{ + g_return_if_fail (GEDIT_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 & GEDIT_WINDOW_STATE_SAVING) + { + gtk_widget_show (statusbar->priv->state_frame); + gtk_widget_show (statusbar->priv->save_image); + } + if (state & GEDIT_WINDOW_STATE_LOADING) + { + gtk_widget_show (statusbar->priv->state_frame); + gtk_widget_show (statusbar->priv->load_image); + } + + if (state & GEDIT_WINDOW_STATE_PRINTING) + { + gtk_widget_show (statusbar->priv->state_frame); + gtk_widget_show (statusbar->priv->print_image); + } + + if (state & GEDIT_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/gedit/gedit-statusbar.h b/gedit/gedit-statusbar.h new file mode 100755 index 00000000..b98790b7 --- /dev/null +++ b/gedit/gedit-statusbar.h @@ -0,0 +1,99 @@ +/* + * gedit-statusbar.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef GEDIT_STATUSBAR_H +#define GEDIT_STATUSBAR_H + +#include <gtk/gtk.h> +#include <gedit/gedit-window.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_STATUSBAR (gedit_statusbar_get_type ()) +#define GEDIT_STATUSBAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_STATUSBAR, GeditStatusbar)) +#define GEDIT_STATUSBAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_STATUSBAR, GeditStatusbarClass)) +#define GEDIT_IS_STATUSBAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_STATUSBAR)) +#define GEDIT_IS_STATUSBAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_STATUSBAR)) +#define GEDIT_STATUSBAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_STATUSBAR, GeditStatusbarClass)) + +typedef struct _GeditStatusbar GeditStatusbar; +typedef struct _GeditStatusbarPrivate GeditStatusbarPrivate; +typedef struct _GeditStatusbarClass GeditStatusbarClass; + +struct _GeditStatusbar +{ + GtkStatusbar parent; + + /* <private/> */ + GeditStatusbarPrivate *priv; +}; + +struct _GeditStatusbarClass +{ + GtkStatusbarClass parent_class; +}; + +GType gedit_statusbar_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_statusbar_new (void); + +/* FIXME: status is not defined in any .h */ +#define GeditStatus gint +void gedit_statusbar_set_window_state (GeditStatusbar *statusbar, + GeditWindowState state, + gint num_of_errors); + +void gedit_statusbar_set_overwrite (GeditStatusbar *statusbar, + gboolean overwrite); + +void gedit_statusbar_set_cursor_position (GeditStatusbar *statusbar, + gint line, + gint col); + +void gedit_statusbar_clear_overwrite (GeditStatusbar *statusbar); + +void gedit_statusbar_flash_message (GeditStatusbar *statusbar, + guint context_id, + const gchar *format, + ...) G_GNUC_PRINTF(3, 4); +/* FIXME: these would be nice for plugins... +void gedit_statusbar_add_widget (GeditStatusbar *statusbar, + GtkWidget *widget); +void gedit_statusbar_remove_widget (GeditStatusbar *statusbar, + GtkWidget *widget); +*/ + +/* + * Non exported functions + */ +void _gedit_statusbar_set_has_resize_grip (GeditStatusbar *statusbar, + gboolean show); + +G_END_DECLS + +#endif diff --git a/gedit/gedit-style-scheme-manager.c b/gedit/gedit-style-scheme-manager.c new file mode 100755 index 00000000..a65fedd7 --- /dev/null +++ b/gedit/gedit-style-scheme-manager.c @@ -0,0 +1,364 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-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 gedit Team, 2007. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-style-scheme-manager.h" +#include "gedit-prefs-manager.h" +#include "gedit-dirs.h" + +static GtkSourceStyleSchemeManager *style_scheme_manager = NULL; + +static gchar * +get_gedit_styles_path (void) +{ + gchar *config_dir; + gchar *dir = NULL; + + config_dir = gedit_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_gedit_styles_path (GtkSourceStyleSchemeManager *mgr) +{ + gchar *dir; + + dir = get_gedit_styles_path(); + + if (dir != NULL) + { + gtk_source_style_scheme_manager_append_search_path (mgr, dir); + g_free (dir); + } +} + +GtkSourceStyleSchemeManager * +gedit_get_style_scheme_manager (void) +{ + if (style_scheme_manager == NULL) + { + style_scheme_manager = gtk_source_style_scheme_manager_new (); + add_gedit_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 * +gedit_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 +_gedit_style_scheme_manager_scheme_is_gedit_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_gedit_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; +} + +/** + * _gedit_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 #GEDIT_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 * +_gedit_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_gedit_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 GEDIT_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 ( + gedit_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; +} + +/** + * _gedit_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 +_gedit_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/gedit/gedit-style-scheme-manager.h b/gedit/gedit-style-scheme-manager.h new file mode 100755 index 00000000..406719ae --- /dev/null +++ b/gedit/gedit-style-scheme-manager.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-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: gedit-source-style-manager.h 5598 2007-04-15 13:16:24Z pborelli $ + */ + +#ifndef __GEDIT_STYLE_SCHEME_MANAGER_H__ +#define __GEDIT_STYLE_SCHEME_MANAGER_H__ + +#include <gtksourceview/gtksourcestyleschememanager.h> + +G_BEGIN_DECLS + +GtkSourceStyleSchemeManager * + gedit_get_style_scheme_manager (void); + +/* Returns a sorted list of style schemes */ +GSList *gedit_style_scheme_manager_list_schemes_sorted + (GtkSourceStyleSchemeManager *manager); + +/* + * Non exported functions + */ +gboolean _gedit_style_scheme_manager_scheme_is_gedit_user_scheme + (GtkSourceStyleSchemeManager *manager, + const gchar *scheme_id); + +const gchar *_gedit_style_scheme_manager_install_scheme + (GtkSourceStyleSchemeManager *manager, + const gchar *fname); + +gboolean _gedit_style_scheme_manager_uninstall_scheme + (GtkSourceStyleSchemeManager *manager, + const gchar *id); + +G_END_DECLS + +#endif /* __GEDIT_STYLE_SCHEME_MANAGER_H__ */ diff --git a/gedit/gedit-tab-label.c b/gedit/gedit-tab-label.c new file mode 100755 index 00000000..a358ae94 --- /dev/null +++ b/gedit/gedit-tab-label.c @@ -0,0 +1,371 @@ +/* + * gedit-tab-label.c + * This file is part of gedit + * + * 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 "gedit-tab-label.h" +#include "gedit-close-button.h" + +#ifdef BUILD_SPINNER +#include "gedit-spinner.h" +#endif + +#define GEDIT_TAB_LABEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_TAB_LABEL, GeditTabLabelPrivate)) + +/* Signals */ +enum +{ + CLOSE_CLICKED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_TAB +}; + +struct _GeditTabLabelPrivate +{ + GeditTab *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 (GeditTabLabel, gedit_tab_label, GTK_TYPE_HBOX) + +static void +gedit_tab_label_finalize (GObject *object) +{ + G_OBJECT_CLASS (gedit_tab_label_parent_class)->finalize (object); +} + +static void +gedit_tab_label_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditTabLabel *tab_label = GEDIT_TAB_LABEL (object); + + switch (prop_id) + { + case PROP_TAB: + tab_label->priv->tab = GEDIT_TAB (g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_tab_label_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditTabLabel *tab_label = GEDIT_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, + GeditTabLabel *tab_label) +{ + g_signal_emit (tab_label, signals[CLOSE_CLICKED], 0, NULL); +} + +static void +sync_tip (GeditTab *tab, GeditTabLabel *tab_label) +{ + gchar *str; + + str = _gedit_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 (GeditTab *tab, GParamSpec *pspec, GeditTabLabel *tab_label) +{ + gchar *str; + + g_return_if_fail (tab == tab_label->priv->tab); + + str = _gedit_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 (GeditTab *tab, GParamSpec *pspec, GeditTabLabel *tab_label) +{ + GeditTabState state; + + g_return_if_fail (tab == tab_label->priv->tab); + + state = gedit_tab_get_state (tab); + + gtk_widget_set_sensitive (tab_label->priv->close_button, + tab_label->priv->close_button_sensitive && + (state != GEDIT_TAB_STATE_CLOSING) && + (state != GEDIT_TAB_STATE_SAVING) && + (state != GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) && + (state != GEDIT_TAB_STATE_SAVING_ERROR)); + + if ((state == GEDIT_TAB_STATE_LOADING) || + (state == GEDIT_TAB_STATE_SAVING) || + (state == GEDIT_TAB_STATE_REVERTING)) + { + gtk_widget_hide (tab_label->priv->icon); + + gtk_widget_show (tab_label->priv->spinner); +#ifdef BUILD_SPINNER + gedit_spinner_start (GEDIT_SPINNER (tab_label->priv->spinner)); +#else + gtk_spinner_start (GTK_SPINNER (tab_label->priv->spinner)); +#endif + } + else + { + GdkPixbuf *pixbuf; + + pixbuf = _gedit_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 + gedit_spinner_stop (GEDIT_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 +gedit_tab_label_constructed (GObject *object) +{ + GeditTabLabel *tab_label = GEDIT_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 +gedit_tab_label_class_init (GeditTabLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_tab_label_finalize; + object_class->set_property = gedit_tab_label_set_property; + object_class->get_property = gedit_tab_label_get_property; + object_class->constructed = gedit_tab_label_constructed; + + signals[CLOSE_CLICKED] = + g_signal_new ("close-clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditTabLabelClass, 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 GeditTab", + GEDIT_TYPE_TAB, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (object_class, sizeof(GeditTabLabelPrivate)); +} + +static void +gedit_tab_label_init (GeditTabLabel *tab_label) +{ + GtkWidget *ebox; + GtkWidget *hbox; + GtkWidget *close_button; + GtkWidget *spinner; + GtkWidget *icon; + GtkWidget *label; + GtkWidget *dummy_label; + + tab_label->priv = GEDIT_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 = gedit_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 = gedit_spinner_new (); + gedit_spinner_set_size (GEDIT_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 +gedit_tab_label_set_close_button_sensitive (GeditTabLabel *tab_label, + gboolean sensitive) +{ + GeditTabState state; + + g_return_if_fail (GEDIT_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 = gedit_tab_get_state (tab_label->priv->tab); + + gtk_widget_set_sensitive (tab_label->priv->close_button, + tab_label->priv->close_button_sensitive && + (state != GEDIT_TAB_STATE_CLOSING) && + (state != GEDIT_TAB_STATE_SAVING) && + (state != GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) && + (state != GEDIT_TAB_STATE_PRINTING) && + (state != GEDIT_TAB_STATE_PRINT_PREVIEWING) && + (state != GEDIT_TAB_STATE_SAVING_ERROR)); +} + +GeditTab * +gedit_tab_label_get_tab (GeditTabLabel *tab_label) +{ + g_return_val_if_fail (GEDIT_IS_TAB_LABEL (tab_label), NULL); + + return tab_label->priv->tab; +} + +GtkWidget * +gedit_tab_label_new (GeditTab *tab) +{ + GeditTabLabel *tab_label; + + tab_label = g_object_new (GEDIT_TYPE_TAB_LABEL, + "homogeneous", FALSE, + "tab", tab, + NULL); + + return GTK_WIDGET (tab_label); +} diff --git a/gedit/gedit-tab-label.h b/gedit/gedit-tab-label.h new file mode 100755 index 00000000..4b0360ad --- /dev/null +++ b/gedit/gedit-tab-label.h @@ -0,0 +1,66 @@ +/* + * gedit-tab-label.h + * This file is part of gedit + * + * 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 __GEDIT_TAB_LABEL_H__ +#define __GEDIT_TAB_LABEL_H__ + +#include <gtk/gtk.h> +#include <gedit/gedit-tab.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_TAB_LABEL (gedit_tab_label_get_type ()) +#define GEDIT_TAB_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_TAB_LABEL, GeditTabLabel)) +#define GEDIT_TAB_LABEL_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_TAB_LABEL, GeditTabLabel const)) +#define GEDIT_TAB_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_TAB_LABEL, GeditTabLabelClass)) +#define GEDIT_IS_TAB_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_TAB_LABEL)) +#define GEDIT_IS_TAB_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_TAB_LABEL)) +#define GEDIT_TAB_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_TAB_LABEL, GeditTabLabelClass)) + +typedef struct _GeditTabLabel GeditTabLabel; +typedef struct _GeditTabLabelClass GeditTabLabelClass; +typedef struct _GeditTabLabelPrivate GeditTabLabelPrivate; + +struct _GeditTabLabel { + GtkHBox parent; + + GeditTabLabelPrivate *priv; +}; + +struct _GeditTabLabelClass { + GtkHBoxClass parent_class; + + void (* close_clicked) (GeditTabLabel *tab_label); +}; + +GType gedit_tab_label_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_tab_label_new (GeditTab *tab); + +GeditTab *gedit_tab_label_get_tab (GeditTabLabel *tab_label); + +void gedit_tab_label_set_close_button_sensitive (GeditTabLabel *tab_label, + gboolean sensitive); + +G_END_DECLS + +#endif /* __GEDIT_TAB_LABEL_H__ */ diff --git a/gedit/gedit-tab.c b/gedit/gedit-tab.c new file mode 100755 index 00000000..9f26c692 --- /dev/null +++ b/gedit/gedit-tab.c @@ -0,0 +1,2832 @@ +/* + * gedit-tab.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-app.h" +#include "gedit-notebook.h" +#include "gedit-tab.h" +#include "gedit-utils.h" +#include "gedit-io-error-message-area.h" +#include "gedit-print-job.h" +#include "gedit-print-preview.h" +#include "gedit-progress-message-area.h" +#include "gedit-debug.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-enum-types.h" + +#if !GTK_CHECK_VERSION (2, 17, 1) +#include "gedit-message-area.h" +#endif + +#define GEDIT_TAB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_TAB, GeditTabPrivate)) + +#define GEDIT_TAB_KEY "GEDIT_TAB_KEY" + +struct _GeditTabPrivate +{ + GeditTabState state; + + GtkWidget *view; + GtkWidget *view_scrolled_window; + + GtkWidget *message_area; + GtkWidget *print_preview; + + GeditPrintJob *print_job; + + /* tmp data for saving */ + gchar *tmp_save_uri; + + /* tmp data for loading */ + gint tmp_line_pos; + const GeditEncoding *tmp_encoding; + + GTimer *timer; + guint times_called; + + GeditDocumentSaveFlags 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(GeditTab, gedit_tab, GTK_TYPE_VBOX) + +enum +{ + PROP_0, + PROP_NAME, + PROP_STATE, + PROP_AUTO_SAVE, + PROP_AUTO_SAVE_INTERVAL +}; + +static gboolean gedit_tab_auto_save (GeditTab *tab); + +static void +install_auto_save_timeout (GeditTab *tab) +{ + gint timeout; + + gedit_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 != GEDIT_TAB_STATE_LOADING); + g_return_if_fail (tab->priv->state != GEDIT_TAB_STATE_SAVING); + g_return_if_fail (tab->priv->state != GEDIT_TAB_STATE_REVERTING); + g_return_if_fail (tab->priv->state != GEDIT_TAB_STATE_LOADING_ERROR); + g_return_if_fail (tab->priv->state != GEDIT_TAB_STATE_SAVING_ERROR); + g_return_if_fail (tab->priv->state != GEDIT_TAB_STATE_SAVING_ERROR); + g_return_if_fail (tab->priv->state != GEDIT_TAB_STATE_REVERTING_ERROR); + + /* Add a new timeout */ + timeout = g_timeout_add_seconds (tab->priv->auto_save_interval * 60, + (GSourceFunc) gedit_tab_auto_save, + tab); + + tab->priv->auto_save_timeout = timeout; +} + +static gboolean +install_auto_save_timeout_if_needed (GeditTab *tab) +{ + GeditDocument *doc; + + gedit_debug (DEBUG_TAB); + + g_return_val_if_fail (tab->priv->auto_save_timeout <= 0, FALSE); + g_return_val_if_fail ((tab->priv->state == GEDIT_TAB_STATE_NORMAL) || + (tab->priv->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) || + (tab->priv->state == GEDIT_TAB_STATE_CLOSING), FALSE); + + if (tab->priv->state == GEDIT_TAB_STATE_CLOSING) + return FALSE; + + doc = gedit_tab_get_document (tab); + + if (tab->priv->auto_save && + !gedit_document_is_untitled (doc) && + !gedit_document_get_readonly (doc)) + { + install_auto_save_timeout (tab); + + return TRUE; + } + + return FALSE; +} + +static void +remove_auto_save_timeout (GeditTab *tab) +{ + gedit_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 +gedit_tab_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditTab *tab = GEDIT_TAB (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_take_string (value, + _gedit_tab_get_name (tab)); + break; + case PROP_STATE: + g_value_set_enum (value, + gedit_tab_get_state (tab)); + break; + case PROP_AUTO_SAVE: + g_value_set_boolean (value, + gedit_tab_get_auto_save_enabled (tab)); + break; + case PROP_AUTO_SAVE_INTERVAL: + g_value_set_int (value, + gedit_tab_get_auto_save_interval (tab)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_tab_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditTab *tab = GEDIT_TAB (object); + + switch (prop_id) + { + case PROP_AUTO_SAVE: + gedit_tab_set_auto_save_enabled (tab, + g_value_get_boolean (value)); + break; + case PROP_AUTO_SAVE_INTERVAL: + gedit_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 +gedit_tab_finalize (GObject *object) +{ + GeditTab *tab = GEDIT_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 (gedit_tab_parent_class)->finalize (object); +} + +static void +gedit_tab_class_init (GeditTabClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_tab_finalize; + object_class->get_property = gedit_tab_get_property; + object_class->set_property = gedit_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", + GEDIT_TYPE_TAB_STATE, + GEDIT_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 (GeditTabPrivate)); +} + +/** + * gedit_tab_get_state: + * @tab: a #GeditTab + * + * Gets the #GeditTabState of @tab. + * + * Returns: the #GeditTabState of @tab + */ +GeditTabState +gedit_tab_get_state (GeditTab *tab) +{ + g_return_val_if_fail (GEDIT_IS_TAB (tab), GEDIT_TAB_STATE_NORMAL); + + return tab->priv->state; +} + +static void +set_cursor_according_to_state (GtkTextView *view, + GeditTabState 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 == GEDIT_TAB_STATE_LOADING) || + (state == GEDIT_TAB_STATE_REVERTING) || + (state == GEDIT_TAB_STATE_SAVING) || + (state == GEDIT_TAB_STATE_PRINTING) || + (state == GEDIT_TAB_STATE_PRINT_PREVIEWING) || + (state == GEDIT_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, + GeditTab *tab) +{ + set_cursor_according_to_state (view, tab->priv->state); +} + +static void +set_view_properties_according_to_state (GeditTab *tab, + GeditTabState state) +{ + gboolean val; + + val = ((state == GEDIT_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 != GEDIT_TAB_STATE_LOADING) && + (state != GEDIT_TAB_STATE_CLOSING)); + gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (tab->priv->view), val); + + val = ((state != GEDIT_TAB_STATE_LOADING) && + (state != GEDIT_TAB_STATE_CLOSING) && + (gedit_prefs_manager_get_highlight_current_line ())); + gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (tab->priv->view), val); +} + +static void +gedit_tab_set_state (GeditTab *tab, + GeditTabState state) +{ + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail ((state >= 0) && (state < GEDIT_TAB_NUM_OF_STATES)); + + if (tab->priv->state == state) + return; + + tab->priv->state = state; + + set_view_properties_according_to_state (tab, state); + + if ((state == GEDIT_TAB_STATE_LOADING_ERROR) || /* FIXME: add other states if needed */ + (state == GEDIT_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 (GeditDocument *document, + GParamSpec *pspec, + GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + /* Notify the change in the URI */ + g_object_notify (G_OBJECT (tab), "name"); +} + +static void +document_shortname_notify_handler (GeditDocument *document, + GParamSpec *pspec, + GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + /* Notify the change in the shortname */ + g_object_notify (G_OBJECT (tab), "name"); +} + +static void +document_modified_changed (GtkTextBuffer *document, + GeditTab *tab) +{ + g_object_notify (G_OBJECT (tab), "name"); +} + +static void +set_message_area (GeditTab *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 (GeditTab *tab) +{ + GeditNotebook *notebook; + + notebook = GEDIT_NOTEBOOK (gtk_widget_get_parent (GTK_WIDGET (tab))); + + gedit_notebook_remove_tab (notebook, tab); +} + +static void +io_loading_error_message_area_response (GtkWidget *message_area, + gint response_id, + GeditTab *tab) +{ + GeditDocument *doc; + GeditView *view; + gchar *uri; + const GeditEncoding *encoding; + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + view = gedit_tab_get_view (tab); + g_return_if_fail (GEDIT_IS_VIEW (view)); + + uri = gedit_document_get_uri (doc); + g_return_if_fail (uri != NULL); + + switch (response_id) + { + case GTK_RESPONSE_OK: + encoding = gedit_conversion_error_message_area_get_encoding ( + GTK_WIDGET (message_area)); + + if (encoding != NULL) + { + tab->priv->tmp_encoding = encoding; + } + + set_message_area (tab, NULL); + gedit_tab_set_state (tab, GEDIT_TAB_STATE_LOADING); + + g_return_if_fail (tab->priv->auto_save_timeout <= 0); + + gedit_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); + _gedit_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: + _gedit_recent_remove (GEDIT_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, + GeditTab *tab) +{ + GeditView *view; + + view = gedit_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, + GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area)); + + g_object_ref (tab); + gedit_document_load_cancel (gedit_tab_get_document (tab)); + g_object_unref (tab); +} + +static void +unrecoverable_reverting_error_message_area_response (GtkWidget *message_area, + gint response_id, + GeditTab *tab) +{ + GeditView *view; + + gedit_tab_set_state (tab, + GEDIT_TAB_STATE_NORMAL); + + set_message_area (tab, NULL); + + view = gedit_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 (GeditTab *tab) +{ + GtkWidget *area; + GeditDocument *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; + + gedit_debug (DEBUG_TAB); + + doc = gedit_tab_get_document (tab); + g_return_if_fail (doc != NULL); + + name = gedit_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 = gedit_utils_str_middle_truncate (name, MAX_MSG_LENGTH); + g_free (name); + name = str; + } + else + { + GFile *file; + + file = gedit_document_get_location (doc); + if (file != NULL) + { + gchar *str; + + str = gedit_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 = gedit_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 == GEDIT_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.mate.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 = gedit_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.mate.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 = gedit_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 (GeditTab *tab) +{ + GtkWidget *area; + GeditDocument *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; + + gedit_debug (DEBUG_TAB); + + doc = gedit_tab_get_document (tab); + g_return_if_fail (doc != NULL); + + short_name = gedit_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 = gedit_utils_str_middle_truncate (short_name, + MAX_MSG_LENGTH); + g_free (short_name); + } + else + { + gchar *str; + + from = short_name; + + to = gedit_utils_uri_for_display (tab->priv->tmp_save_uri); + + str = gedit_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.mate.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 = gedit_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 (GeditTab *tab, + goffset size, + goffset total_size) +{ + if (tab->priv->message_area == NULL) + return; + + gedit_debug_message (DEBUG_TAB, "%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, size, total_size); + + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area)); + + if (total_size == 0) + { + if (size != 0) + gedit_progress_message_area_pulse ( + GEDIT_PROGRESS_MESSAGE_AREA (tab->priv->message_area)); + else + gedit_progress_message_area_set_fraction ( + GEDIT_PROGRESS_MESSAGE_AREA (tab->priv->message_area), + 0); + } + else + { + gdouble frac; + + frac = (gdouble)size / (gdouble)total_size; + + gedit_progress_message_area_set_fraction ( + GEDIT_PROGRESS_MESSAGE_AREA (tab->priv->message_area), + frac); + } +} + +static void +document_loading (GeditDocument *document, + goffset size, + goffset total_size, + GeditTab *tab) +{ + gdouble et; + gdouble total_time; + + g_return_if_fail ((tab->priv->state == GEDIT_TAB_STATE_LOADING) || + (tab->priv->state == GEDIT_TAB_STATE_REVERTING)); + + gedit_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 (GeditTab *tab) +{ + remove_tab (tab); + + return FALSE; +} + +static void +document_loaded (GeditDocument *document, + const GError *error, + GeditTab *tab) +{ + GtkWidget *emsg; + GFile *location; + gchar *uri; + const GeditEncoding *encoding; + + g_return_if_fail ((tab->priv->state == GEDIT_TAB_STATE_LOADING) || + (tab->priv->state == GEDIT_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 = gedit_document_get_location (document); + uri = gedit_document_get_uri (document); + + /* if the error is CONVERSION FALLBACK don't treat it as a normal error */ + if (error != NULL && + (error->domain != GEDIT_DOCUMENT_ERROR || error->code != GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK)) + { + if (tab->priv->state == GEDIT_TAB_STATE_LOADING) + gedit_tab_set_state (tab, GEDIT_TAB_STATE_LOADING_ERROR); + else + gedit_tab_set_state (tab, GEDIT_TAB_STATE_REVERTING_ERROR); + + encoding = gedit_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 + { + _gedit_recent_remove (GEDIT_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), uri); + + if (tab->priv->state == GEDIT_TAB_STATE_LOADING_ERROR) + { + emsg = gedit_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 == GEDIT_TAB_STATE_REVERTING_ERROR); + + emsg = gedit_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) + gedit_message_area_set_default_response (GEDIT_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 = gedit_document_get_mime_type (document); + _gedit_recent_add (GEDIT_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), + uri, + mime); + g_free (mime); + + if (error && + error->domain == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK) + { + GtkWidget *emsg; + + _gedit_document_set_readonly (document, TRUE); + + emsg = gedit_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) + gedit_message_area_set_default_response (GEDIT_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 */ + gedit_view_scroll_to_cursor (GEDIT_VIEW (tab->priv->view)); + + all_documents = gedit_app_get_documents (gedit_app_get_default ()); + + for (l = all_documents; l != NULL; l = g_list_next (l)) + { + GeditDocument *d = GEDIT_DOCUMENT (l->data); + + if (d != document) + { + GFile *loc; + + loc = gedit_document_get_location (d); + + if ((loc != NULL) && + g_file_equal (location, loc)) + { + GtkWidget *w; + GeditView *view; + + view = gedit_tab_get_view (tab); + + tab->priv->not_editable = TRUE; + + w = gedit_file_already_open_warning_message_area_new (uri); + + set_message_area (tab, w); + +#if !GTK_CHECK_VERSION (2, 17, 1) + gedit_message_area_set_default_response (GEDIT_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); + + gedit_tab_set_state (tab, GEDIT_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 (GeditDocument *document, + goffset size, + goffset total_size, + GeditTab *tab) +{ + gdouble et; + gdouble total_time; + + g_return_if_fail (tab->priv->state == GEDIT_TAB_STATE_SAVING); + + gedit_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 (GeditTab *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, + GeditTab *tab) +{ + GeditView *view; + + if (tab->priv->print_preview != NULL) + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW); + else + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + + end_saving (tab); + + set_message_area (tab, NULL); + + view = gedit_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, + GeditTab *tab) +{ + if (response_id == GTK_RESPONSE_YES) + { + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_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); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING); + + /* don't bug the user again with this... */ + tab->priv->save_flags |= GEDIT_DOCUMENT_SAVE_IGNORE_BACKUP; + + g_return_if_fail (tab->priv->auto_save_timeout <= 0); + + /* Force saving */ + gedit_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, + GeditTab *tab) +{ + if (response_id == GTK_RESPONSE_YES) + { + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_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); + + gedit_tab_set_state (tab, GEDIT_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 */ + gedit_document_save (doc, tab->priv->save_flags | GEDIT_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, + GeditTab *tab) +{ + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + if (response_id == GTK_RESPONSE_OK) + { + const GeditEncoding *encoding; + + encoding = gedit_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); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING); + + tab->priv->tmp_encoding = encoding; + + gedit_debug_message (DEBUG_TAB, "Force saving with URI '%s'", tab->priv->tmp_save_uri); + + g_return_if_fail (tab->priv->auto_save_timeout <= 0); + + gedit_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 (GeditDocument *document, + const GError *error, + GeditTab *tab) +{ + GtkWidget *emsg; + + g_return_if_fail (tab->priv->state == GEDIT_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) + { + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING_ERROR); + + if (error->domain == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_DOCUMENT_ERROR_EXTERNALLY_MODIFIED) + { + /* This error is recoverable */ + emsg = gedit_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 == GEDIT_DOCUMENT_ERROR && + error->code == GEDIT_DOCUMENT_ERROR_CANT_CREATE_BACKUP) || + (error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_CANT_CREATE_BACKUP)) + { + /* This error is recoverable */ + emsg = gedit_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 == GEDIT_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 */ + _gedit_recent_remove (GEDIT_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), + tab->priv->tmp_save_uri); + + emsg = gedit_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 = gedit_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) + gedit_message_area_set_default_response (GEDIT_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 = gedit_document_get_mime_type (document); + + _gedit_recent_add (GEDIT_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tab))), + tab->priv->tmp_save_uri, + mime); + g_free (mime); + + if (tab->priv->print_preview != NULL) + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW); + else + gedit_tab_set_state (tab, GEDIT_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, + GeditTab *tab) +{ + GeditView *view; + + set_message_area (tab, NULL); + view = gedit_tab_get_view (tab); + + if (response_id == GTK_RESPONSE_OK) + { + _gedit_tab_revert (tab); + } + else + { + tab->priv->ask_if_externally_modified = FALSE; + + /* go back to normal state */ + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + } + + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +display_externally_modified_notification (GeditTab *tab) +{ + GtkWidget *message_area; + GeditDocument *doc; + gchar *uri; + gboolean document_modified; + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + /* uri cannot be NULL, we're here because + * the file we're editing changed on disk */ + uri = gedit_document_get_uri (doc); + g_return_if_fail (uri != NULL); + + document_modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER(doc)); + message_area = gedit_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, + GeditTab *tab) +{ + GeditDocument *doc; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), FALSE); + + /* we try to detect file changes only in the normal state */ + if (tab->priv->state != GEDIT_TAB_STATE_NORMAL) + { + return FALSE; + } + + /* we already asked, don't bug the user again */ + if (!tab->priv->ask_if_externally_modified) + { + return FALSE; + } + + doc = gedit_tab_get_document (tab); + + /* If file was never saved or is remote we do not check */ + if (!gedit_document_is_local (doc)) + { + return FALSE; + } + + if (_gedit_document_check_externally_modified (doc)) + { + gedit_tab_set_state (tab, GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION); + + display_externally_modified_notification (tab); + + return FALSE; + } + + return FALSE; +} + +static GMountOperation * +tab_mount_operation_factory (GeditDocument *doc, + gpointer userdata) +{ + GeditTab *tab = GEDIT_TAB (userdata); + GtkWidget *window; + + window = gtk_widget_get_toplevel (GTK_WIDGET (tab)); + return gtk_mount_operation_new (GTK_WINDOW (window)); +} + +static void +gedit_tab_init (GeditTab *tab) +{ + GtkWidget *sw; + GeditDocument *doc; + GeditLockdownMask lockdown; + + tab->priv = GEDIT_TAB_GET_PRIVATE (tab); + + tab->priv->state = GEDIT_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 = gedit_app_get_lockdown (gedit_app_get_default ()); + tab->priv->auto_save = gedit_prefs_manager_get_auto_save () && + !(lockdown & GEDIT_LOCKDOWN_SAVE_TO_DISK); + tab->priv->auto_save = (tab->priv->auto_save != FALSE); + + tab->priv->auto_save_interval = gedit_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 = gedit_document_new (); + g_object_set_data (G_OBJECT (doc), GEDIT_TAB_KEY, tab); + + _gedit_document_set_mount_operation_factory (doc, + tab_mount_operation_factory, + tab); + + tab->priv->view = gedit_view_new (doc); + g_object_unref (doc); + gtk_widget_show (tab->priv->view); + g_object_set_data (G_OBJECT (tab->priv->view), GEDIT_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 * +_gedit_tab_new (void) +{ + return GTK_WIDGET (g_object_new (GEDIT_TYPE_TAB, NULL)); +} + +/* Whether create is TRUE, creates a new empty document if location does + not refer to an existing file */ +GtkWidget * +_gedit_tab_new_from_uri (const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create) +{ + GeditTab *tab; + + g_return_val_if_fail (uri != NULL, NULL); + + tab = GEDIT_TAB (_gedit_tab_new ()); + + _gedit_tab_load (tab, + uri, + encoding, + line_pos, + create); + + return GTK_WIDGET (tab); +} + +/** + * gedit_tab_get_view: + * @tab: a #GeditTab + * + * Gets the #GeditView inside @tab. + * + * Returns: the #GeditView inside @tab + */ +GeditView * +gedit_tab_get_view (GeditTab *tab) +{ + return GEDIT_VIEW (tab->priv->view); +} + +/** + * gedit_tab_get_document: + * @tab: a #GeditTab + * + * Gets the #GeditDocument associated to @tab. + * + * Returns: the #GeditDocument associated to @tab + */ +GeditDocument * +gedit_tab_get_document (GeditTab *tab) +{ + return GEDIT_DOCUMENT (gtk_text_view_get_buffer ( + GTK_TEXT_VIEW (tab->priv->view))); +} + +#define MAX_DOC_NAME_LENGTH 40 + +gchar * +_gedit_tab_get_name (GeditTab *tab) +{ + GeditDocument *doc; + gchar *name; + gchar *docname; + gchar *tab_name; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + doc = gedit_tab_get_document (tab); + + name = gedit_document_get_short_name_for_display (doc); + + /* Truncate the name so it doesn't get insanely wide. */ + docname = gedit_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 (gedit_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 * +_gedit_tab_get_tooltips (GeditTab *tab) +{ + GeditDocument *doc; + gchar *tip; + gchar *uri; + gchar *ruri; + gchar *ruri_markup; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), NULL); + + doc = gedit_tab_get_document (tab); + + uri = gedit_document_get_uri_for_display (doc); + g_return_val_if_fail (uri != NULL, NULL); + + ruri = gedit_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 GeditEncoding *enc; + + case GEDIT_TAB_STATE_LOADING_ERROR: + tip = g_strdup_printf (_("Error opening file %s"), + ruri_markup); + break; + + case GEDIT_TAB_STATE_REVERTING_ERROR: + tip = g_strdup_printf (_("Error reverting file %s"), + ruri_markup); + break; + + case GEDIT_TAB_STATE_SAVING_ERROR: + tip = g_strdup_printf (_("Error saving file %s"), + ruri_markup); + break; + default: + content_type = gedit_document_get_content_type (doc); + mime_type = gedit_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 = gedit_document_get_encoding (doc); + + if (enc == NULL) + encoding = g_strdup (_("Unicode (UTF-8)")); + else + encoding = gedit_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 * +_gedit_tab_get_icon (GeditTab *tab) +{ + GdkPixbuf *pixbuf; + GtkIconTheme *theme; + GdkScreen *screen; + gint icon_size; + + g_return_val_if_fail (GEDIT_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 GEDIT_TAB_STATE_LOADING: + pixbuf = get_stock_icon (theme, + GTK_STOCK_OPEN, + icon_size); + break; + + case GEDIT_TAB_STATE_REVERTING: + pixbuf = get_stock_icon (theme, + GTK_STOCK_REVERT_TO_SAVED, + icon_size); + break; + + case GEDIT_TAB_STATE_SAVING: + pixbuf = get_stock_icon (theme, + GTK_STOCK_SAVE, + icon_size); + break; + + case GEDIT_TAB_STATE_PRINTING: + pixbuf = get_stock_icon (theme, + GTK_STOCK_PRINT, + icon_size); + break; + + case GEDIT_TAB_STATE_PRINT_PREVIEWING: + case GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW: + pixbuf = get_stock_icon (theme, + GTK_STOCK_PRINT_PREVIEW, + icon_size); + break; + + case GEDIT_TAB_STATE_LOADING_ERROR: + case GEDIT_TAB_STATE_REVERTING_ERROR: + case GEDIT_TAB_STATE_SAVING_ERROR: + case GEDIT_TAB_STATE_GENERIC_ERROR: + pixbuf = get_stock_icon (theme, + GTK_STOCK_DIALOG_ERROR, + icon_size); + break; + + case GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION: + pixbuf = get_stock_icon (theme, + GTK_STOCK_DIALOG_WARNING, + icon_size); + break; + + default: + { + GFile *location; + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + location = gedit_document_get_location (doc); + pixbuf = get_icon (theme, location, icon_size); + + if (location) + g_object_unref (location); + } + } + + return pixbuf; +} + +/** + * gedit_tab_get_from_document: + * @doc: a #GeditDocument + * + * Gets the #GeditTab associated with @doc. + * + * Returns: the #GeditTab associated with @doc + */ +GeditTab * +gedit_tab_get_from_document (GeditDocument *doc) +{ + gpointer res; + + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + res = g_object_get_data (G_OBJECT (doc), GEDIT_TAB_KEY); + + return (res != NULL) ? GEDIT_TAB (res) : NULL; +} + +void +_gedit_tab_load (GeditTab *tab, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create) +{ + GeditDocument *doc; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (tab->priv->state == GEDIT_TAB_STATE_NORMAL); + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + gedit_tab_set_state (tab, GEDIT_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); + + gedit_document_load (doc, + uri, + encoding, + line_pos, + create); +} + +void +_gedit_tab_revert (GeditTab *tab) +{ + GeditDocument *doc; + gchar *uri; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail ((tab->priv->state == GEDIT_TAB_STATE_NORMAL) || + (tab->priv->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)); + + if (tab->priv->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) + { + set_message_area (tab, NULL); + } + + doc = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_REVERTING); + + uri = gedit_document_get_uri (doc); + g_return_if_fail (uri != NULL); + + tab->priv->tmp_line_pos = 0; + tab->priv->tmp_encoding = gedit_document_get_encoding (doc); + + if (tab->priv->auto_save_timeout > 0) + remove_auto_save_timeout (tab); + + gedit_document_load (doc, + uri, + tab->priv->tmp_encoding, + 0, + FALSE); + + g_free (uri); +} + +void +_gedit_tab_save (GeditTab *tab) +{ + GeditDocument *doc; + GeditDocumentSaveFlags save_flags; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail ((tab->priv->state == GEDIT_TAB_STATE_NORMAL) || + (tab->priv->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) || + (tab->priv->state == GEDIT_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 = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + g_return_if_fail (!gedit_document_is_untitled (doc)); + + if (tab->priv->state == GEDIT_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 | GEDIT_DOCUMENT_SAVE_IGNORE_MTIME; + } + else + { + save_flags = tab->priv->save_flags; + } + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING); + + /* uri used in error messages, will be freed in document_saved */ + tab->priv->tmp_save_uri = gedit_document_get_uri (doc); + tab->priv->tmp_encoding = gedit_document_get_encoding (doc); + + if (tab->priv->auto_save_timeout > 0) + remove_auto_save_timeout (tab); + + gedit_document_save (doc, save_flags); +} + +static gboolean +gedit_tab_auto_save (GeditTab *tab) +{ + GeditDocument *doc; + + gedit_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 = gedit_tab_get_document (tab); + + g_return_val_if_fail (!gedit_document_is_untitled (doc), FALSE); + g_return_val_if_fail (!gedit_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))) + { + gedit_debug_message (DEBUG_TAB, "Document not modified"); + + return TRUE; + } + + if ((tab->priv->state != GEDIT_TAB_STATE_NORMAL) && + (tab->priv->state != GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)) + { + /* Retry after 30 seconds */ + guint timeout; + + gedit_debug_message (DEBUG_TAB, "Retry after 30 seconds"); + + /* Add a new timeout */ + timeout = g_timeout_add_seconds (30, + (GSourceFunc) gedit_tab_auto_save, + tab); + + tab->priv->auto_save_timeout = timeout; + + /* Returns FALSE so the old timeout is "destroyed" */ + return FALSE; + } + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SAVING); + + /* uri used in error messages, will be freed in document_saved */ + tab->priv->tmp_save_uri = gedit_document_get_uri (doc); + tab->priv->tmp_encoding = gedit_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 */ + gedit_document_save (doc, tab->priv->save_flags | GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP); + + gedit_debug_message (DEBUG_TAB, "Done"); + + /* Returns FALSE so the old timeout is "destroyed" */ + return FALSE; +} + +void +_gedit_tab_save_as (GeditTab *tab, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentNewlineType newline_type) +{ + GeditDocument *doc; + GeditDocumentSaveFlags save_flags; + + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail ((tab->priv->state == GEDIT_TAB_STATE_NORMAL) || + (tab->priv->state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) || + (tab->priv->state == GEDIT_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 = gedit_tab_get_document (tab); + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + /* reset the save flags, when saving as */ + tab->priv->save_flags = 0; + + if (tab->priv->state == GEDIT_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 | GEDIT_DOCUMENT_SAVE_IGNORE_MTIME; + } + else + { + save_flags = tab->priv->save_flags; + } + + gedit_tab_set_state (tab, GEDIT_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 */ + gedit_document_set_newline_type (doc, newline_type); + gedit_document_save_as (doc, uri, encoding, tab->priv->save_flags); +} + +#define GEDIT_PAGE_SETUP_KEY "gedit-page-setup-key" +#define GEDIT_PRINT_SETTINGS_KEY "gedit-print-settings-key" + +static GtkPageSetup * +get_page_setup (GeditTab *tab) +{ + gpointer data; + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + data = g_object_get_data (G_OBJECT (doc), + GEDIT_PAGE_SETUP_KEY); + + if (data == NULL) + { + return _gedit_app_get_default_page_setup (gedit_app_get_default()); + } + else + { + return gtk_page_setup_copy (GTK_PAGE_SETUP (data)); + } +} + +static GtkPrintSettings * +get_print_settings (GeditTab *tab) +{ + gpointer data; + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + data = g_object_get_data (G_OBJECT (doc), + GEDIT_PRINT_SETTINGS_KEY); + + if (data == NULL) + { + return _gedit_app_get_default_print_settings (gedit_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 (GeditPrintJob *job, + GeditPrintJobStatus status, + GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area)); + + gtk_widget_show (tab->priv->message_area); + + gedit_progress_message_area_set_text (GEDIT_PROGRESS_MESSAGE_AREA (tab->priv->message_area), + gedit_print_job_get_status_string (job)); + + gedit_progress_message_area_set_fraction (GEDIT_PROGRESS_MESSAGE_AREA (tab->priv->message_area), + gedit_print_job_get_progress (job)); +} + +static void +store_print_settings (GeditTab *tab, + GeditPrintJob *job) +{ + GeditDocument *doc; + GtkPrintSettings *settings; + GtkPageSetup *page_setup; + + doc = gedit_tab_get_document (tab); + + settings = gedit_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), + GEDIT_PRINT_SETTINGS_KEY, + g_object_ref (settings), + (GDestroyNotify)g_object_unref); + + /* make them the default */ + _gedit_app_set_default_print_settings (gedit_app_get_default (), + settings); + + page_setup = gedit_print_job_get_page_setup (job); + + /* remember page setup for this document */ + g_object_set_data_full (G_OBJECT (doc), + GEDIT_PAGE_SETUP_KEY, + g_object_ref (page_setup), + (GDestroyNotify)g_object_unref); + + /* make it the default */ + _gedit_app_set_default_page_setup (gedit_app_get_default (), + page_setup); +} + +static void +done_printing_cb (GeditPrintJob *job, + GeditPrintJobResult result, + const GError *error, + GeditTab *tab) +{ + GeditView *view; + + g_return_if_fail (tab->priv->state == GEDIT_TAB_STATE_PRINT_PREVIEWING || + tab->priv->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW || + tab->priv->state == GEDIT_TAB_STATE_PRINTING); + + if (tab->priv->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) + { + /* print preview has been destroyed... */ + tab->priv->print_preview = NULL; + } + else + { + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area)); + + set_message_area (tab, NULL); /* destroy the message area */ + } + + // TODO: check status and error + + if (result == GEDIT_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 == GEDIT_TAB_STATE_PRINTING); + } +#endif + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + + view = gedit_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, + GeditTab *tab) +{ + tab->priv->print_preview = NULL; + + if (tab->priv->state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) + { + GeditView *view; + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + + view = gedit_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 == GEDIT_TAB_STATE_PRINTING); + } +} +#endif + +static void +show_preview_cb (GeditPrintJob *job, + GeditPrintPreview *preview, + GeditTab *tab) +{ +// g_return_if_fail (tab->priv->state == GEDIT_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); +*/ + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW); +} + +#if 0 + +static void +set_print_preview (GeditTab *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, GeditTab *tab) +{ + MatePrintJob *gjob; + GtkWidget *preview = NULL; + + g_return_if_fail (GEDIT_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 = gedit_print_job_preview_new (gjob); + g_object_unref (gjob); + + set_print_preview (tab, preview); + + gtk_widget_show (preview); + g_object_unref (pjob); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW); +} + + +#endif + +static void +print_cancelled (GtkWidget *area, + gint response_id, + GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_PROGRESS_MESSAGE_AREA (tab->priv->message_area)); + + gedit_print_job_cancel (tab->priv->print_job); + + g_debug ("print_cancelled"); +} + +static void +show_printing_message_area (GeditTab *tab, gboolean preview) +{ + GtkWidget *area; + + if (preview) + area = gedit_progress_message_area_new (GTK_STOCK_PRINT_PREVIEW, + "", + TRUE); + else + area = gedit_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, + GeditTab *tab) +{ + if (setup != NULL) + { + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + /* remember it for this document */ + g_object_set_data_full (G_OBJECT (doc), + GEDIT_PAGE_SETUP_KEY, + g_object_ref (setup), + (GDestroyNotify)g_object_unref); + + /* make it the default */ + _gedit_app_set_default_page_setup (gedit_app_get_default(), + setup); + } +} + +void +_gedit_tab_page_setup (GeditTab *tab) +{ + GtkPageSetup *setup; + GtkPrintSettings *settings; + + g_return_if_fail (GEDIT_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 +gedit_tab_print_or_print_preview (GeditTab *tab, + GtkPrintOperationAction print_action) +{ + GeditView *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 == GEDIT_TAB_STATE_NORMAL); + + view = gedit_tab_get_view (tab); + + is_preview = (print_action == GTK_PRINT_OPERATION_ACTION_PREVIEW); + + tab->priv->print_job = gedit_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) + gedit_tab_set_state (tab, GEDIT_TAB_STATE_PRINT_PREVIEWING); + else + gedit_tab_set_state (tab, GEDIT_TAB_STATE_PRINTING); + + setup = get_page_setup (tab); + settings = get_print_settings (tab); + + res = gedit_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 */ + gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL); + g_warning ("Async print preview failed (%s)", error->message); + g_object_unref (tab->priv->print_job); + g_error_free (error); + } +} + +void +_gedit_tab_print (GeditTab *tab) +{ + g_return_if_fail (GEDIT_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 == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) + { + gtk_widget_destroy (tab->priv->print_preview); + } + + gedit_tab_print_or_print_preview (tab, + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); +} + +void +_gedit_tab_print_preview (GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_TAB (tab)); + + gedit_tab_print_or_print_preview (tab, + GTK_PRINT_OPERATION_ACTION_PREVIEW); +} + +void +_gedit_tab_mark_for_closing (GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail (tab->priv->state == GEDIT_TAB_STATE_NORMAL); + + gedit_tab_set_state (tab, GEDIT_TAB_STATE_CLOSING); +} + +gboolean +_gedit_tab_can_close (GeditTab *tab) +{ + GeditDocument *doc; + GeditTabState ts; + + g_return_val_if_fail (GEDIT_IS_TAB (tab), FALSE); + + ts = gedit_tab_get_state (tab); + + /* if we are loading or reverting, the tab can be closed */ + if ((ts == GEDIT_TAB_STATE_LOADING) || + (ts == GEDIT_TAB_STATE_LOADING_ERROR) || + (ts == GEDIT_TAB_STATE_REVERTING) || + (ts == GEDIT_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 == GEDIT_TAB_STATE_SAVING_ERROR) + return FALSE; + + doc = gedit_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)) && + !gedit_document_get_deleted (doc)); +} + +/** + * gedit_tab_get_auto_save_enabled: + * @tab: a #GeditTab + * + * Gets the current state for the autosave feature + * + * Return value: %TRUE if the autosave is enabled, else %FALSE + **/ +gboolean +gedit_tab_get_auto_save_enabled (GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + g_return_val_if_fail (GEDIT_IS_TAB (tab), FALSE); + + return tab->priv->auto_save; +} + +/** + * gedit_tab_set_auto_save_enabled: + * @tab: a #GeditTab + * @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 +gedit_tab_set_auto_save_enabled (GeditTab *tab, + gboolean enable) +{ + GeditDocument *doc = NULL; + GeditLockdownMask lockdown; + + gedit_debug (DEBUG_TAB); + + g_return_if_fail (GEDIT_IS_TAB (tab)); + + /* Force disabling when lockdown is active */ + lockdown = gedit_app_get_lockdown (gedit_app_get_default()); + if (lockdown & GEDIT_LOCKDOWN_SAVE_TO_DISK) + enable = FALSE; + + doc = gedit_tab_get_document (tab); + + if (tab->priv->auto_save == enable) + return; + + tab->priv->auto_save = enable; + + if (enable && + (tab->priv->auto_save_timeout <=0) && + !gedit_document_is_untitled (doc) && + !gedit_document_get_readonly (doc)) + { + if ((tab->priv->state != GEDIT_TAB_STATE_LOADING) && + (tab->priv->state != GEDIT_TAB_STATE_SAVING) && + (tab->priv->state != GEDIT_TAB_STATE_REVERTING) && + (tab->priv->state != GEDIT_TAB_STATE_LOADING_ERROR) && + (tab->priv->state != GEDIT_TAB_STATE_SAVING_ERROR) && + (tab->priv->state != GEDIT_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)) || + gedit_document_is_untitled (doc) || gedit_document_get_readonly (doc)); +} + +/** + * gedit_tab_get_auto_save_interval: + * @tab: a #GeditTab + * + * Gets the current interval for the autosaves + * + * Return value: the value of the autosave + **/ +gint +gedit_tab_get_auto_save_interval (GeditTab *tab) +{ + gedit_debug (DEBUG_TAB); + + g_return_val_if_fail (GEDIT_IS_TAB (tab), 0); + + return tab->priv->auto_save_interval; +} + +/** + * gedit_tab_set_auto_save_interval: + * @tab: a #GeditTab + * @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 +gedit_tab_set_auto_save_interval (GeditTab *tab, + gint interval) +{ + GeditDocument *doc = NULL; + + gedit_debug (DEBUG_TAB); + + g_return_if_fail (GEDIT_IS_TAB (tab)); + + doc = gedit_tab_get_document(tab); + + g_return_if_fail (GEDIT_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 (!gedit_document_is_untitled (doc)); + g_return_if_fail (!gedit_document_get_readonly (doc)); + + remove_auto_save_timeout (tab); + + install_auto_save_timeout (tab); + } +} + +void +gedit_tab_set_info_bar (GeditTab *tab, + GtkWidget *info_bar) +{ + g_return_if_fail (GEDIT_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/gedit/gedit-tab.h b/gedit/gedit-tab.h new file mode 100755 index 00000000..68262083 --- /dev/null +++ b/gedit/gedit-tab.h @@ -0,0 +1,165 @@ +/* + * gedit-tab.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_TAB_H__ +#define __GEDIT_TAB_H__ + +#include <gtk/gtk.h> + +#include <gedit/gedit-view.h> +#include <gedit/gedit-document.h> + +G_BEGIN_DECLS + +typedef enum +{ + GEDIT_TAB_STATE_NORMAL = 0, + GEDIT_TAB_STATE_LOADING, + GEDIT_TAB_STATE_REVERTING, + GEDIT_TAB_STATE_SAVING, + GEDIT_TAB_STATE_PRINTING, + GEDIT_TAB_STATE_PRINT_PREVIEWING, + GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW, + GEDIT_TAB_STATE_GENERIC_NOT_EDITABLE, + GEDIT_TAB_STATE_LOADING_ERROR, + GEDIT_TAB_STATE_REVERTING_ERROR, + GEDIT_TAB_STATE_SAVING_ERROR, + GEDIT_TAB_STATE_GENERIC_ERROR, + GEDIT_TAB_STATE_CLOSING, + GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION, + GEDIT_TAB_NUM_OF_STATES /* This is not a valid state */ +} GeditTabState; + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_TAB (gedit_tab_get_type()) +#define GEDIT_TAB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_TAB, GeditTab)) +#define GEDIT_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_TAB, GeditTabClass)) +#define GEDIT_IS_TAB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_TAB)) +#define GEDIT_IS_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_TAB)) +#define GEDIT_TAB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_TAB, GeditTabClass)) + +/* Private structure type */ +typedef struct _GeditTabPrivate GeditTabPrivate; + +/* + * Main object structure + */ +typedef struct _GeditTab GeditTab; + +struct _GeditTab +{ + GtkVBox vbox; + + /*< private > */ + GeditTabPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditTabClass GeditTabClass; + +struct _GeditTabClass +{ + GtkVBoxClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_tab_get_type (void) G_GNUC_CONST; + +GeditView *gedit_tab_get_view (GeditTab *tab); + +/* This is only an helper function */ +GeditDocument *gedit_tab_get_document (GeditTab *tab); + +GeditTab *gedit_tab_get_from_document (GeditDocument *doc); + +GeditTabState gedit_tab_get_state (GeditTab *tab); + +gboolean gedit_tab_get_auto_save_enabled + (GeditTab *tab); + +void gedit_tab_set_auto_save_enabled + (GeditTab *tab, + gboolean enable); + +gint gedit_tab_get_auto_save_interval + (GeditTab *tab); + +void gedit_tab_set_auto_save_interval + (GeditTab *tab, + gint interval); + +void gedit_tab_set_info_bar (GeditTab *tab, + GtkWidget *info_bar); +/* + * Non exported methods + */ +GtkWidget *_gedit_tab_new (void); + +/* Whether create is TRUE, creates a new empty document if location does + not refer to an existing file */ +GtkWidget *_gedit_tab_new_from_uri (const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create); +gchar *_gedit_tab_get_name (GeditTab *tab); +gchar *_gedit_tab_get_tooltips (GeditTab *tab); +GdkPixbuf *_gedit_tab_get_icon (GeditTab *tab); +void _gedit_tab_load (GeditTab *tab, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create); +void _gedit_tab_revert (GeditTab *tab); +void _gedit_tab_save (GeditTab *tab); +void _gedit_tab_save_as (GeditTab *tab, + const gchar *uri, + const GeditEncoding *encoding, + GeditDocumentNewlineType newline_type); + +void _gedit_tab_print (GeditTab *tab); +void _gedit_tab_print_preview (GeditTab *tab); + +void _gedit_tab_mark_for_closing (GeditTab *tab); + +gboolean _gedit_tab_can_close (GeditTab *tab); + +#if !GTK_CHECK_VERSION (2, 17, 4) +void _gedit_tab_page_setup (GeditTab *tab); +#endif + +G_END_DECLS + +#endif /* __GEDIT_TAB_H__ */ diff --git a/gedit/gedit-ui.h b/gedit/gedit-ui.h new file mode 100755 index 00000000..8a536251 --- /dev/null +++ b/gedit/gedit-ui.h @@ -0,0 +1,188 @@ +/* + * gedit-ui.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_UI_H__ +#define __GEDIT_UI_H__ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> + +#include "gedit-commands.h" + +G_BEGIN_DECLS + +static const GtkActionEntry gedit_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 (_gedit_cmd_file_new) }, + { "FileOpen", GTK_STOCK_OPEN, N_("_Open..."), "<control>O", + N_("Open a file"), G_CALLBACK (_gedit_cmd_file_open) }, + + /* Edit menu */ + { "EditPreferences", GTK_STOCK_PREFERENCES, N_("Pr_eferences"), NULL, + N_("Configure the application"), G_CALLBACK (_gedit_cmd_edit_preferences) }, + + /* Help menu */ + {"HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1", + N_("Open the gedit manual"), G_CALLBACK (_gedit_cmd_help_contents) }, + { "HelpAbout", GTK_STOCK_ABOUT, NULL, NULL, + N_("About this application"), G_CALLBACK (_gedit_cmd_help_about) }, + + /* Fullscreen toolbar */ + { "LeaveFullscreen", GTK_STOCK_LEAVE_FULLSCREEN, NULL, + NULL, N_("Leave fullscreen mode"), + G_CALLBACK (_gedit_cmd_view_leave_fullscreen_mode) } +}; + +static const GtkActionEntry gedit_menu_entries[] = +{ + /* File menu */ + { "FileSave", GTK_STOCK_SAVE, NULL, "<control>S", + N_("Save the current file"), G_CALLBACK (_gedit_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 (_gedit_cmd_file_save_as) }, + { "FileRevert", GTK_STOCK_REVERT_TO_SAVED, NULL, NULL, + N_("Revert to a saved version of the file"), G_CALLBACK (_gedit_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 (_gedit_cmd_file_page_setup) }, +#endif + { "FilePrintPreview", GTK_STOCK_PRINT_PREVIEW, N_("Print Previe_w"),"<control><shift>P", + N_("Print preview"), G_CALLBACK (_gedit_cmd_file_print_preview) }, + { "FilePrint", GTK_STOCK_PRINT, N_("_Print..."), "<control>P", + N_("Print the current page"), G_CALLBACK (_gedit_cmd_file_print) }, + + /* Edit menu */ + { "EditUndo", GTK_STOCK_UNDO, NULL, "<control>Z", + N_("Undo the last action"), G_CALLBACK (_gedit_cmd_edit_undo) }, + { "EditRedo", GTK_STOCK_REDO, NULL, "<shift><control>Z", + N_("Redo the last undone action"), G_CALLBACK (_gedit_cmd_edit_redo) }, + { "EditCut", GTK_STOCK_CUT, NULL, "<control>X", + N_("Cut the selection"), G_CALLBACK (_gedit_cmd_edit_cut) }, + { "EditCopy", GTK_STOCK_COPY, NULL, "<control>C", + N_("Copy the selection"), G_CALLBACK (_gedit_cmd_edit_copy) }, + { "EditPaste", GTK_STOCK_PASTE, NULL, "<control>V", + N_("Paste the clipboard"), G_CALLBACK (_gedit_cmd_edit_paste) }, + { "EditDelete", GTK_STOCK_DELETE, NULL, NULL, + N_("Delete the selected text"), G_CALLBACK (_gedit_cmd_edit_delete) }, + { "EditSelectAll", GTK_STOCK_SELECT_ALL, N_("Select _All"), "<control>A", + N_("Select the entire document"), G_CALLBACK (_gedit_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 (_gedit_cmd_search_find) }, + { "SearchFindNext", NULL, N_("Find Ne_xt"), "<control>G", + N_("Search forwards for the same text"), G_CALLBACK (_gedit_cmd_search_find_next) }, + { "SearchFindPrevious", NULL, N_("Find Pre_vious"), "<shift><control>G", + N_("Search backwards for the same text"), G_CALLBACK (_gedit_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 (_gedit_cmd_search_replace) }, +#else + { "SearchReplace", GTK_STOCK_FIND_AND_REPLACE, N_("_Replace..."), "<control><alt>F", + N_("Search for and replace text"), G_CALLBACK (_gedit_cmd_search_replace) }, +#endif + { "SearchClearHighlight", NULL, N_("_Clear Highlight"), "<shift><control>K", + N_("Clear highlighting of search matches"), G_CALLBACK (_gedit_cmd_search_clear_highlight) }, + { "SearchGoToLine", GTK_STOCK_JUMP_TO, N_("Go to _Line..."), "<control>I", + N_("Go to a specific line"), G_CALLBACK (_gedit_cmd_search_goto_line) }, + { "SearchIncrementalSearch", GTK_STOCK_FIND, N_("_Incremental Search..."), "<control>K", + N_("Incrementally search for text"), G_CALLBACK (_gedit_cmd_search_incremental_search) }, + + /* Documents menu */ + { "FileSaveAll", GTK_STOCK_SAVE, N_("_Save All"), "<shift><control>L", + N_("Save all open files"), G_CALLBACK (_gedit_cmd_file_save_all) }, + { "FileCloseAll", GTK_STOCK_CLOSE, N_("_Close All"), "<shift><control>W", + N_("Close all open files"), G_CALLBACK (_gedit_cmd_file_close_all) }, + { "DocumentsPreviousDocument", NULL, N_("_Previous Document"), "<alt><control>Page_Up", + N_("Activate previous document"), G_CALLBACK (_gedit_cmd_documents_previous_document) }, + { "DocumentsNextDocument", NULL, N_("_Next Document"), "<alt><control>Page_Down", + N_("Activate next document"), G_CALLBACK (_gedit_cmd_documents_next_document) }, + { "DocumentsMoveToNewWindow", NULL, N_("_Move to New Window"), NULL, + N_("Move the current document to a new window"), G_CALLBACK (_gedit_cmd_documents_move_to_new_window) } +}; + +/* separate group, needs to be sensitive on OS X even when there are no tabs */ +static const GtkActionEntry gedit_close_menu_entries[] = +{ + { "FileClose", GTK_STOCK_CLOSE, NULL, "<control>W", + N_("Close the current file"), G_CALLBACK (_gedit_cmd_file_close) } +}; + +/* separate group, should be sensitive even when there are no tabs */ +static const GtkActionEntry gedit_quit_menu_entries[] = +{ + { "FileQuit", GTK_STOCK_QUIT, NULL, "<control>Q", + N_("Quit the program"), G_CALLBACK (_gedit_cmd_file_quit) } +}; + +static const GtkToggleActionEntry gedit_always_sensitive_toggle_menu_entries[] = +{ + { "ViewToolbar", NULL, N_("_Toolbar"), NULL, + N_("Show or hide the toolbar in the current window"), + G_CALLBACK (_gedit_cmd_view_show_toolbar), TRUE }, + { "ViewStatusbar", NULL, N_("_Statusbar"), NULL, + N_("Show or hide the statusbar in the current window"), + G_CALLBACK (_gedit_cmd_view_show_statusbar), TRUE }, + { "ViewFullscreen", GTK_STOCK_FULLSCREEN, NULL, "F11", + N_("Edit text in fullscreen"), + G_CALLBACK (_gedit_cmd_view_toggle_fullscreen_mode), FALSE } +}; + +/* separate group, should be always sensitive except when there are no panes */ +static const GtkToggleActionEntry gedit_panes_toggle_menu_entries[] = +{ + { "ViewSidePane", NULL, N_("Side _Pane"), "F9", + N_("Show or hide the side pane in the current window"), + G_CALLBACK (_gedit_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 (_gedit_cmd_view_show_bottom_pane), FALSE } +}; + +G_END_DECLS + +#endif /* __GEDIT_UI_H__ */ diff --git a/gedit/gedit-ui.xml b/gedit/gedit-ui.xml new file mode 100755 index 00000000..21c31a72 --- /dev/null +++ b/gedit/gedit-ui.xml @@ -0,0 +1,203 @@ +<!-- + * gedit-ui.xml + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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/gedit/gedit-utils.c b/gedit/gedit-utils.c new file mode 100755 index 00000000..c34b746f --- /dev/null +++ b/gedit/gedit-utils.c @@ -0,0 +1,1546 @@ +/* + * gedit-utils.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-utils.h" + +#include "gedit-document.h" +#include "gedit-prefs-manager.h" +#include "gedit-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 +gedit_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 +gedit_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 = gedit_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 +gedit_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 +gedit_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 */ + gedit_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 * +gedit_gdk_color_to_string (GdkColor color) +{ + return g_strdup_printf ("#%04x%04x%04x", + color.red, + color.green, + color.blue); +} + +GtkWidget * +gedit_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 * +gedit_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 = gedit_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; +} + +/** + * gedit_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 +gedit_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); +} + +/** + * gedit_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 +gedit_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 +gedit_utils_uri_exists (const gchar* text_uri) +{ + GFile *gfile; + gboolean res; + + g_return_val_if_fail (text_uri != NULL, FALSE); + + gedit_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); + + gedit_debug_message (DEBUG_UTILS, res ? "TRUE" : "FALSE"); + + return res; +} + +gchar * +gedit_utils_escape_search_text (const gchar* text) +{ + GString *str; + gint length; + const gchar *p; + const gchar *end; + + if (text == NULL) + return NULL; + + gedit_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 * +gedit_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 +gedit_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 * +gedit_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 * +gedit_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 * +gedit_utils_str_middle_truncate (const gchar *string, + guint truncate_length) +{ + return gedit_utils_str_truncate (string, truncate_length, TRUE); +} + +gchar * +gedit_utils_str_end_truncate (const gchar *string, + guint truncate_length) +{ + return gedit_utils_str_truncate (string, truncate_length, FALSE); +} + +gchar * +gedit_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 * +gedit_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 = gedit_utils_replace_home_dir_with_tilde (str); + + g_free (str); + + return res; +} + +/** + * gedit_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 * +gedit_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 (gedit_utils_decode_uri (uri, + NULL, NULL, + NULL, NULL, + &path)) + { + gchar *dirname; + + dirname = gedit_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 = gedit_utils_uri_get_dirname (uri); + } + + g_free (uri); + + return res; +} + +gchar * +gedit_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 */ + +/** + * gedit_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 +gedit_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) ¤t_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 +} + +/** + * gedit_utils_get_window_workspace: Get the workspace the window is on + * + * This function gets the workspace that the #GtkWindow is visible on, + * it returns GEDIT_ALL_WORKSPACES if the window is sticky, or if + * the window manager doesn support this function + */ +guint +gedit_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 = GEDIT_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 +} + +/** + * gedit_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 +gedit_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 +gedit_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 +gedit_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; +} + +/** + * gedit_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 +gedit_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 * +gedit_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 gedit_utils_is_valid_uri () + * + */ + + gfile = g_file_new_for_commandline_arg (str); + uri = g_file_get_uri (gfile); + g_object_unref (gfile); + + if (gedit_utils_is_valid_uri (uri)) + return uri; + + g_free (uri); + return NULL; +} + +/** + * gedit_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 +gedit_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; +} + +/** + * gedit_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 * +gedit_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 (gedit_utils_file_has_parent (gfile) || !gedit_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 = gedit_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; +} + +/** + * gedit_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 * +gedit_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; +} + +/** + * gedit_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 ** +gedit_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 = gedit_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; +} + +/** + * gedit_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 +gedit_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/gedit/gedit-utils.h b/gedit/gedit-utils.h new file mode 100755 index 00000000..ddf1d9c5 --- /dev/null +++ b/gedit/gedit-utils.h @@ -0,0 +1,162 @@ +/* + * gedit-utils.h + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_UTILS_H__ +#define __GEDIT_UTILS_H__ + +#include <glib.h> +#include <gtk/gtk.h> +#include <atk/atk.h> +#include <gedit/gedit-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 { GEDIT_ALL_WORKSPACES = 0xffffffff }; + +gboolean gedit_utils_uri_has_writable_scheme (const gchar *uri); +gboolean gedit_utils_uri_has_file_scheme (const gchar *uri); + +void gedit_utils_menu_position_under_widget (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data); + +void gedit_utils_menu_position_under_tree_view + (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data); + +gchar *gedit_gdk_color_to_string (GdkColor color); + +GtkWidget *gedit_gtk_button_new_with_stock_icon (const gchar *label, + const gchar *stock_id); + +GtkWidget *gedit_dialog_add_button (GtkDialog *dialog, + const gchar *text, + const gchar *stock_id, + gint response_id); + +gchar *gedit_utils_escape_underscores (const gchar *text, + gssize length); + +gchar *gedit_utils_str_middle_truncate (const gchar *string, + guint truncate_length); + +gchar *gedit_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 gedit_utils_set_atk_name_description (GtkWidget *widget, + const gchar *name, + const gchar *description); + +void gedit_utils_set_atk_relation (GtkWidget *obj1, + GtkWidget *obj2, + AtkRelationType rel_type); + +gboolean gedit_utils_uri_exists (const gchar* text_uri); + +gchar *gedit_utils_escape_search_text (const gchar *text); + +gchar *gedit_utils_unescape_search_text (const gchar *text); + +void gedit_warning (GtkWindow *parent, + const gchar *format, + ...) G_GNUC_PRINTF(2, 3); + +gchar *gedit_utils_make_valid_utf8 (const char *name); + +/* Note that this function replace home dir with ~ */ +gchar *gedit_utils_uri_get_dirname (const char *uri); + +gchar *gedit_utils_location_get_dirname_for_display + (GFile *location); + +gchar *gedit_utils_replace_home_dir_with_tilde (const gchar *uri); + +guint gedit_utils_get_current_workspace (GdkScreen *screen); + +guint gedit_utils_get_window_workspace (GtkWindow *gtkwindow); + +void gedit_utils_get_current_viewport (GdkScreen *screen, + gint *x, + gint *y); + +void gedit_utils_activate_url (GtkAboutDialog *about, + const gchar *url, + gpointer data); + +gboolean gedit_utils_is_valid_uri (const gchar *uri); + +gboolean gedit_utils_get_ui_objects (const gchar *filename, + gchar **root_objects, + GtkWidget **error_widget, + const gchar *object_name, + ...) G_GNUC_NULL_TERMINATED; + +gboolean gedit_utils_file_has_parent (GFile *gfile); + +/* Return NULL if str is not a valid URI and/or filename */ +gchar *gedit_utils_make_canonical_uri_from_shell_arg + (const gchar *str); + +gchar *gedit_utils_uri_for_display (const gchar *uri); +gchar *gedit_utils_basename_for_display (const gchar *uri); +gboolean gedit_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 **gedit_utils_drop_get_uris (GtkSelectionData *selection_data); + +G_END_DECLS + +#endif /* __GEDIT_UTILS_H__ */ + + diff --git a/gedit/gedit-view.c b/gedit/gedit-view.c new file mode 100755 index 00000000..b999d298 --- /dev/null +++ b/gedit/gedit-view.c @@ -0,0 +1,2181 @@ +/* + * gedit-view.c + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-view.h" +#include "gedit-debug.h" +#include "gedit-prefs-manager.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-marshal.h" +#include "gedit-utils.h" + + +#define GEDIT_VIEW_SCROLL_MARGIN 0.02 +#define GEDIT_VIEW_SEARCH_DIALOG_TIMEOUT (30*1000) /* 30 seconds */ + +#define MIN_SEARCH_COMPLETION_KEY_LEN 3 + +#define GEDIT_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_VIEW, GeditViewPrivate)) + +typedef enum +{ + GOTO_LINE, + SEARCH +} SearchMode; + +enum +{ + TARGET_URI_LIST = 100 +}; + +struct _GeditViewPrivate +{ + 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 gedit_view_destroy (GtkObject *object); +static void gedit_view_finalize (GObject *object); +static gint gedit_view_focus_out (GtkWidget *widget, + GdkEventFocus *event); +static gboolean gedit_view_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint timestamp); +static void gedit_view_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint timestamp); +static gboolean gedit_view_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint timestamp); +static gboolean gedit_view_button_press_event (GtkWidget *widget, + GdkEventButton *event); + +static gboolean start_interactive_search (GeditView *view); +static gboolean start_interactive_goto_line (GeditView *view); +static gboolean reset_searched_text (GeditView *view); + +static void hide_search_window (GeditView *view, + gboolean cancel); + + +static gint gedit_view_expose (GtkWidget *widget, + GdkEventExpose *event); +static void search_highlight_updated_cb (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end, + GeditView *view); + +static void gedit_view_delete_from_cursor (GtkTextView *text_view, + GtkDeleteType type, + gint count); + +G_DEFINE_TYPE(GeditView, gedit_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 +{ + GEDIT_SEARCH_ENTRY_NORMAL, + GEDIT_SEARCH_ENTRY_NOT_FOUND +} GeditSearchEntryBgColor; + +static void +document_read_only_notify_handler (GeditDocument *document, + GParamSpec *pspec, + GeditView *view) +{ + gedit_debug (DEBUG_VIEW); + + gtk_text_view_set_editable (GTK_TEXT_VIEW (view), + !gedit_document_get_readonly (document)); +} + +static void +gedit_view_class_init (GeditViewClass *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 = gedit_view_destroy; + object_class->finalize = gedit_view_finalize; + + widget_class->focus_out_event = gedit_view_focus_out; + widget_class->expose_event = gedit_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 = gedit_view_drag_motion; + widget_class->drag_data_received = gedit_view_drag_data_received; + widget_class->drag_drop = gedit_view_drag_drop; + widget_class->button_press_event = gedit_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 = gedit_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 (GeditViewClass, start_interactive_search), + NULL, NULL, + gedit_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 (GeditViewClass, start_interactive_goto_line), + NULL, NULL, + gedit_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 (GeditViewClass, reset_searched_text), + NULL, NULL, + gedit_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'. GeditView 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 gedit 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 (GeditViewClass, drop_uris), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, G_TYPE_STRV); + + g_type_class_add_private (klass, sizeof (GeditViewPrivate)); + + 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 (GeditView *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 (GeditView *view, + GParamSpec *arg1, + gpointer userdata) +{ + GtkTextBuffer *buffer; + + current_buffer_removed (view); + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + if (buffer == NULL || !GEDIT_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), + !gedit_document_get_readonly (GEDIT_DOCUMENT (buffer))); + + g_signal_connect (buffer, + "search_highlight_updated", + G_CALLBACK (search_highlight_updated_cb), + view); +} + +static void +gedit_view_init (GeditView *view) +{ + GtkTargetList *tl; + + gedit_debug (DEBUG_VIEW); + + view->priv = GEDIT_VIEW_GET_PRIVATE (view); + + /* + * Set tab, fonts, wrap mode, colors, etc. according + * to preferences + */ + if (!gedit_prefs_manager_get_use_default_font ()) + { + gchar *editor_font; + + editor_font = gedit_prefs_manager_get_editor_font (); + + gedit_view_set_font (view, FALSE, editor_font); + + g_free (editor_font); + } + else + { + gedit_view_set_font (view, TRUE, NULL); + } + + g_object_set (G_OBJECT (view), + "wrap_mode", gedit_prefs_manager_get_wrap_mode (), + "show_line_numbers", gedit_prefs_manager_get_display_line_numbers (), + "auto_indent", gedit_prefs_manager_get_auto_indent (), + "tab_width", gedit_prefs_manager_get_tabs_size (), + "insert_spaces_instead_of_tabs", gedit_prefs_manager_get_insert_spaces (), + "show_right_margin", gedit_prefs_manager_get_display_right_margin (), + "right_margin_position", gedit_prefs_manager_get_right_margin_position (), + "highlight_current_line", gedit_prefs_manager_get_highlight_current_line (), + "smart_home_end", gedit_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 +gedit_view_destroy (GtkObject *object) +{ + GeditView *view; + + view = GEDIT_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 (gedit_view_parent_class)->destroy) (object); +} + +static void +gedit_view_finalize (GObject *object) +{ + GeditView *view; + + view = GEDIT_VIEW (object); + + current_buffer_removed (view); + + g_free (view->priv->old_search_text); + + (* G_OBJECT_CLASS (gedit_view_parent_class)->finalize) (object); +} + +static gint +gedit_view_focus_out (GtkWidget *widget, GdkEventFocus *event) +{ + GeditView *view = GEDIT_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 (gedit_view_parent_class)->focus_out_event) (widget, event); + + return FALSE; +} + +/** + * gedit_view_new: + * @doc: a #GeditDocument + * + * Creates a new #GeditView object displaying the @doc document. + * @doc cannot be NULL. + * + * Return value: a new #GeditView + **/ +GtkWidget * +gedit_view_new (GeditDocument *doc) +{ + GtkWidget *view; + + gedit_debug_message (DEBUG_VIEW, "START"); + + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + view = GTK_WIDGET (g_object_new (GEDIT_TYPE_VIEW, "buffer", doc, NULL)); + + gedit_debug_message (DEBUG_VIEW, "END: %d", G_OBJECT (view)->ref_count); + + gtk_widget_show_all (view); + + return view; +} + +void +gedit_view_cut_clipboard (GeditView *view) +{ + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + gedit_debug (DEBUG_VIEW); + + g_return_if_fail (GEDIT_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, + !gedit_document_get_readonly ( + GEDIT_DOCUMENT (buffer))); + + gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view), + gtk_text_buffer_get_insert (buffer), + GEDIT_VIEW_SCROLL_MARGIN, + FALSE, + 0.0, + 0.0); +} + +void +gedit_view_copy_clipboard (GeditView *view) +{ + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + gedit_debug (DEBUG_VIEW); + + g_return_if_fail (GEDIT_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 +gedit_view_paste_clipboard (GeditView *view) +{ + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + gedit_debug (DEBUG_VIEW); + + g_return_if_fail (GEDIT_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, + !gedit_document_get_readonly ( + GEDIT_DOCUMENT (buffer))); + + gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view), + gtk_text_buffer_get_insert (buffer), + GEDIT_VIEW_SCROLL_MARGIN, + FALSE, + 0.0, + 0.0); +} + +/** + * gedit_view_delete_selection: + * @view: a #GeditView + * + * Deletes the text currently selected in the #GtkTextBuffer associated + * to the view and scroll to the cursor position. + **/ +void +gedit_view_delete_selection (GeditView *view) +{ + GtkTextBuffer *buffer = NULL; + + gedit_debug (DEBUG_VIEW); + + g_return_if_fail (GEDIT_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, + !gedit_document_get_readonly ( + GEDIT_DOCUMENT (buffer))); + + gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view), + gtk_text_buffer_get_insert (buffer), + GEDIT_VIEW_SCROLL_MARGIN, + FALSE, + 0.0, + 0.0); +} + +/** + * gedit_view_select_all: + * @view: a #GeditView + * + * Selects all the text displayed in the @view. + **/ +void +gedit_view_select_all (GeditView *view) +{ + GtkTextBuffer *buffer = NULL; + GtkTextIter start, end; + + gedit_debug (DEBUG_VIEW); + + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_view_scroll_to_cursor: + * @view: a #GeditView + * + * Scrolls the @view to the cursor position. + **/ +void +gedit_view_scroll_to_cursor (GeditView *view) +{ + GtkTextBuffer* buffer = NULL; + + gedit_debug (DEBUG_VIEW); + + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_view_set_font: + * @view: a #GeditView + * @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 +gedit_view_set_font (GeditView *view, + gboolean def, + const gchar *font_name) +{ + PangoFontDescription *font_desc = NULL; + + gedit_debug (DEBUG_VIEW); + + g_return_if_fail (GEDIT_IS_VIEW (view)); + + if (def) + { + gchar *font; + + font = gedit_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 = gedit_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, + GeditSearchEntryBgColor col) +{ + if (col == GEDIT_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 (GeditView *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; + GeditDocument *doc; + + g_return_val_if_fail (view->priv->search_mode == SEARCH, FALSE); + + doc = GEDIT_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 = gedit_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 = gedit_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 = gedit_document_search_forward (doc, + NULL, + NULL, /* FIXME: set the end_inter */ + &match_start, + &match_end); + else + found = gedit_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')) + { + gedit_view_scroll_to_cursor (view); + + set_entry_background (view->priv->search_entry, + GEDIT_SEARCH_ENTRY_NORMAL); + } + else + { + set_entry_background (view->priv->search_entry, + GEDIT_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 (GeditView *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); + + gedit_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 (GeditView *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 (GeditView *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, + GeditView *view) +{ + hide_search_window (view, FALSE); + + return TRUE; +} + +static gboolean +search_window_button_press_event (GtkWidget *widget, + GdkEventButton *event, + GeditView *view) +{ + hide_search_window (view, FALSE); + + gtk_propagate_event (GTK_WIDGET (view), (GdkEvent *)event); + + return FALSE; +} + +static void +search_again (GeditView *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 (GEDIT_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, + GeditView *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, + GeditView *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) + { + GeditDocument *doc; + + /* restore document search so that Find Next does the right thing */ + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + gedit_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, + GeditView *view) +{ + hide_search_window (view, FALSE); +} + +static void +wrap_around_menu_item_toggled (GtkCheckMenuItem *checkmenuitem, + GeditView *view) +{ + view->priv->wrap_around = gtk_check_menu_item_get_active (checkmenuitem); +} + +static void +match_entire_word_menu_item_toggled (GtkCheckMenuItem *checkmenuitem, + GeditView *view) +{ + GEDIT_SEARCH_SET_ENTIRE_WORD (view->priv->search_flags, + gtk_check_menu_item_get_active (checkmenuitem)); +} + +static void +match_case_menu_item_toggled (GtkCheckMenuItem *checkmenuitem, + GeditView *view) +{ + GEDIT_SEARCH_SET_CASE_SENSITIVE (view->priv->search_flags, + gtk_check_menu_item_get_active (checkmenuitem)); +} + +static gboolean +real_search_enable_popdown (gpointer data) +{ + GeditView *view = (GeditView *)data; + + GDK_THREADS_ENTER (); + + view->priv->disable_popdown = FALSE; + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +search_enable_popdown (GtkWidget *widget, + GeditView *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 (GEDIT_VIEW_SEARCH_DIALOG_TIMEOUT, + (GSourceFunc)search_entry_flush_timeout, + view); +} + +static void +search_entry_populate_popup (GtkEntry *entry, + GtkMenu *menu, + GeditView *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), + GEDIT_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), + GEDIT_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, + GeditView *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; + + gedit_debug_message (DEBUG_SEARCH, "Text: %s", text); + + /* To avoid recursive behavior */ + if (insert_text) + return; + + escaped_text = gedit_utils_escape_search_text (text); + + gedit_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 (GeditView *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; + GeditViewPrivate *priv = (GeditViewPrivate *)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 (GEDIT_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 (GeditView *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 (GeditView *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 = gedit_document_get_search_text (GEDIT_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, + GeditView *view) +{ + GeditDocument *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 (GEDIT_VIEW_SEARCH_DIALOG_TIMEOUT, + (GSourceFunc)search_entry_flush_timeout, + view); + } + + doc = GEDIT_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 = gedit_document_get_search_text (doc, &search_flags); + + if ((search_text == NULL) || + (strcmp (search_text, entry_text) != 0) || + search_flags != view->priv->search_flags) + { + gedit_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 = gedit_document_goto_line (doc, line); + moved_offset = gedit_document_goto_line_offset (doc, line, + line_offset); + + gedit_view_scroll_to_cursor (view); + + if (!moved || !moved_offset) + set_entry_background (view->priv->search_entry, + GEDIT_SEARCH_ENTRY_NOT_FOUND); + else + set_entry_background (view->priv->search_entry, + GEDIT_SEARCH_ENTRY_NORMAL); + } + } +} + +static gboolean +start_interactive_search_real (GeditView *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 (GEDIT_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 (GeditView *view) +{ + GeditDocument *doc; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + + gedit_document_set_search_text (doc, "", GEDIT_SEARCH_DONT_SET_FLAGS); + + return TRUE; +} + +static gboolean +start_interactive_search (GeditView *view) +{ + view->priv->search_mode = SEARCH; + + return start_interactive_search_real (view); +} + +static gboolean +start_interactive_goto_line (GeditView *view) +{ + view->priv->search_mode = GOTO_LINE; + + return start_interactive_search_real (view); +} + +static gint +gedit_view_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkTextView *text_view; + GeditDocument *doc; + + text_view = GTK_TEXT_VIEW (widget); + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (text_view)); + + if ((event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)) && + gedit_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); + + _gedit_document_search_region (doc, + &iter1, + &iter2); + } + + return (* GTK_WIDGET_CLASS (gedit_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 +gedit_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 (gedit_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 +gedit_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 = gedit_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 (gedit_view_parent_class)->drag_data_received (widget, context, x, y, selection_data, info, timestamp); + } +} + +static gboolean +gedit_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 (gedit_view_parent_class)->drag_drop (widget, context, x, y, timestamp); + } + + return result; +} + +static void +show_line_numbers_toggled (GtkMenu *menu, + GeditView *view) +{ + gboolean show; + + show = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu)); + + gedit_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 +gedit_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 (gedit_view_parent_class)->button_press_event (widget, event); +} + +static void +search_highlight_updated_cb (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end, + GeditView *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 (gedit_document_get_enable_search_highlighting ( + GEDIT_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 +gedit_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 (gedit_view_parent_class)->delete_from_cursor(text_view, type, count); + break; + } +} diff --git a/gedit/gedit-view.h b/gedit/gedit-view.h new file mode 100755 index 00000000..dc0096bc --- /dev/null +++ b/gedit/gedit-view.h @@ -0,0 +1,108 @@ +/* + * gedit-view.h + * This file is part of gedit + * + * 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 gedit Team, 1998-2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef __GEDIT_VIEW_H__ +#define __GEDIT_VIEW_H__ + +#include <gtk/gtk.h> + +#include <gedit/gedit-document.h> +#include <gtksourceview/gtksourceview.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_VIEW (gedit_view_get_type ()) +#define GEDIT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_VIEW, GeditView)) +#define GEDIT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_VIEW, GeditViewClass)) +#define GEDIT_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_VIEW)) +#define GEDIT_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_VIEW)) +#define GEDIT_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_VIEW, GeditViewClass)) + +/* Private structure type */ +typedef struct _GeditViewPrivate GeditViewPrivate; + +/* + * Main object structure + */ +typedef struct _GeditView GeditView; + +struct _GeditView +{ + GtkSourceView view; + + /*< private > */ + GeditViewPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditViewClass GeditViewClass; + +struct _GeditViewClass +{ + GtkSourceViewClass parent_class; + + /* FIXME: Do we need placeholders ? */ + + /* Key bindings */ + gboolean (* start_interactive_search) (GeditView *view); + gboolean (* start_interactive_goto_line)(GeditView *view); + gboolean (* reset_searched_text) (GeditView *view); + + void (* drop_uris) (GeditView *view, + gchar **uri_list); +}; + +/* + * Public methods + */ +GType gedit_view_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_view_new (GeditDocument *doc); + +void gedit_view_cut_clipboard (GeditView *view); +void gedit_view_copy_clipboard (GeditView *view); +void gedit_view_paste_clipboard (GeditView *view); +void gedit_view_delete_selection (GeditView *view); +void gedit_view_select_all (GeditView *view); + +void gedit_view_scroll_to_cursor (GeditView *view); + +void gedit_view_set_font (GeditView *view, + gboolean def, + const gchar *font_name); + +G_END_DECLS + +#endif /* __GEDIT_VIEW_H__ */ diff --git a/gedit/gedit-window-private.h b/gedit/gedit-window-private.h new file mode 100755 index 00000000..399433af --- /dev/null +++ b/gedit/gedit-window-private.h @@ -0,0 +1,124 @@ +/* + * gedit-window-private.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_WINDOW_PRIVATE_H__ +#define __GEDIT_WINDOW_PRIVATE_H__ + +#include "gedit/gedit-window.h" +#include "gedit-prefs-manager.h" +#include "gedit-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 gedit-commands */ + +struct _GeditWindowPrivate +{ + GtkWidget *notebook; + + GtkWidget *side_panel; + GtkWidget *bottom_panel; + + GtkWidget *hpaned; + GtkWidget *vpaned; + + GtkWidget *tab_width_combo; + GtkWidget *language_combo; + + GeditMessageBus *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; + GeditToolbarSetting toolbar_style; + + /* recent files */ + GtkActionGroup *recents_action_group; + guint recents_menu_ui_id; + gulong recents_handler_id; + + GeditTab *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; + + GeditWindowState 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 /* __GEDIT_WINDOW_PRIVATE_H__ */ diff --git a/gedit/gedit-window.c b/gedit/gedit-window.c new file mode 100755 index 00000000..c4bf2403 --- /dev/null +++ b/gedit/gedit-window.c @@ -0,0 +1,4798 @@ +/* + * gedit-window.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-ui.h" +#include "gedit-window.h" +#include "gedit-window-private.h" +#include "gedit-app.h" +#include "gedit-notebook.h" +#include "gedit-statusbar.h" +#include "gedit-utils.h" +#include "gedit-commands.h" +#include "gedit-debug.h" +#include "gedit-language-manager.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-panel.h" +#include "gedit-documents-panel.h" +#include "gedit-plugins-engine.h" +#include "gedit-enum-types.h" +#include "gedit-dirs.h" +#include "gedit-status-combo-box.h" + +#ifdef OS_OSX +#include "osx/gedit-osx.h" +#endif + +#define LANGUAGE_NONE (const gchar *)"LangNone" +#define GEDIT_UIFILE "gedit-ui.xml" +#define TAB_WIDTH_DATA "GeditWindowTabWidthData" +#define LANGUAGE_DATA "GeditWindowLanguageData" +#define FULLSCREEN_ANIMATION_SPEED 4 + +#define GEDIT_WINDOW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object),\ + GEDIT_TYPE_WINDOW, \ + GeditWindowPrivate)) + +/* 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(GeditWindow, gedit_window, GTK_TYPE_WINDOW) + +static void recent_manager_changed (GtkRecentManager *manager, + GeditWindow *window); + +static void +gedit_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditWindow *window = GEDIT_WINDOW (object); + + switch (prop_id) + { + case PROP_STATE: + g_value_set_enum (value, + gedit_window_get_state (window)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +save_panes_state (GeditWindow *window) +{ + gint pane_page; + + gedit_debug (DEBUG_WINDOW); + + if (gedit_prefs_manager_window_size_can_set ()) + gedit_prefs_manager_set_window_size (window->priv->width, + window->priv->height); + + if (gedit_prefs_manager_window_state_can_set ()) + gedit_prefs_manager_set_window_state (window->priv->window_state); + + if ((window->priv->side_panel_size > 0) && + gedit_prefs_manager_side_panel_size_can_set ()) + gedit_prefs_manager_set_side_panel_size ( + window->priv->side_panel_size); + + pane_page = _gedit_panel_get_active_item_id (GEDIT_PANEL (window->priv->side_panel)); + if (pane_page != 0 && + gedit_prefs_manager_side_panel_active_page_can_set ()) + gedit_prefs_manager_set_side_panel_active_page (pane_page); + + if ((window->priv->bottom_panel_size > 0) && + gedit_prefs_manager_bottom_panel_size_can_set ()) + gedit_prefs_manager_set_bottom_panel_size ( + window->priv->bottom_panel_size); + + pane_page = _gedit_panel_get_active_item_id (GEDIT_PANEL (window->priv->bottom_panel)); + if (pane_page != 0 && + gedit_prefs_manager_bottom_panel_active_page_can_set ()) + gedit_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 (GeditWindow *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 (GeditWindow *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 +gedit_window_focus_in_event (GtkWidget *widget, + GdkEventFocus *event) +{ + add_mac_root_menu (GEDIT_WINDOW (widget)); + return GTK_WIDGET_CLASS (gedit_window_parent_class)->focus_in_event (widget, event); +} + +static gboolean +gedit_window_focus_out_event (GtkWidget *widget, + GdkEventFocus *event) +{ + remove_mac_root_menu (GEDIT_WINDOW (widget)); + return GTK_WIDGET_CLASS (gedit_window_parent_class)->focus_out_event (widget, event); +} +#endif + +static void +gedit_window_dispose (GObject *object) +{ + GeditWindow *window; + + gedit_debug (DEBUG_WINDOW); + + window = GEDIT_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. + */ + gedit_plugins_engine_garbage_collect (gedit_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); + + gedit_plugins_engine_deactivate_plugins (gedit_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. + */ + gedit_plugins_engine_garbage_collect (gedit_plugins_engine_get_default ()); + +#ifdef OS_OSX + remove_mac_root_menu (window); +#endif + + G_OBJECT_CLASS (gedit_window_parent_class)->dispose (object); +} + +static void +gedit_window_finalize (GObject *object) +{ + GeditWindow *window; + + gedit_debug (DEBUG_WINDOW); + + window = GEDIT_WINDOW (object); + + if (window->priv->default_location != NULL) + g_object_unref (window->priv->default_location); + + G_OBJECT_CLASS (gedit_window_parent_class)->finalize (object); +} + +static gboolean +gedit_window_window_state_event (GtkWidget *widget, + GdkEventWindowState *event) +{ + GeditWindow *window = GEDIT_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)); + + _gedit_statusbar_set_has_resize_grip (GEDIT_STATUSBAR (window->priv->statusbar), + show); + } + + return FALSE; +} + +static gboolean +gedit_window_configure_event (GtkWidget *widget, + GdkEventConfigure *event) +{ + GeditWindow *window = GEDIT_WINDOW (widget); + + window->priv->width = event->width; + window->priv->height = event->height; + + return GTK_WIDGET_CLASS (gedit_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 +gedit_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 (gedit_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 +gedit_window_tab_removed (GeditWindow *window, + GeditTab *tab) +{ + gedit_plugins_engine_garbage_collect (gedit_plugins_engine_get_default ()); +} + +static void +gedit_window_class_init (GeditWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + klass->tab_removed = gedit_window_tab_removed; + + object_class->dispose = gedit_window_dispose; + object_class->finalize = gedit_window_finalize; + object_class->get_property = gedit_window_get_property; + + widget_class->window_state_event = gedit_window_window_state_event; + widget_class->configure_event = gedit_window_configure_event; + widget_class->key_press_event = gedit_window_key_press_event; + +#ifdef OS_OSX + widget_class->focus_in_event = gedit_window_focus_in_event; + widget_class->focus_out_event = gedit_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 (GeditWindowClass, tab_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[TAB_REMOVED] = + g_signal_new ("tab_removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditWindowClass, tab_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_TYPE_TAB); + signals[TABS_REORDERED] = + g_signal_new ("tabs_reordered", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GeditWindowClass, 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 (GeditWindowClass, active_tab_changed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + GEDIT_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 (GeditWindowClass, 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", + GEDIT_TYPE_WINDOW_STATE, + GEDIT_WINDOW_STATE_NORMAL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (object_class, sizeof(GeditWindowPrivate)); +} + +static void +menu_item_select_cb (GtkMenuItem *proxy, + GeditWindow *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, + GeditWindow *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, + GeditWindow *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, + GeditWindow *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 (GeditWindow *window, + GtkWidget *toolbar) +{ + switch (window->priv->toolbar_style) + { + case GEDIT_TOOLBAR_SYSTEM: + gedit_debug_message (DEBUG_WINDOW, "GEDIT: SYSTEM"); + gtk_toolbar_unset_style ( + GTK_TOOLBAR (toolbar)); + break; + + case GEDIT_TOOLBAR_ICONS: + gedit_debug_message (DEBUG_WINDOW, "GEDIT: ICONS"); + gtk_toolbar_set_style ( + GTK_TOOLBAR (toolbar), + GTK_TOOLBAR_ICONS); + break; + + case GEDIT_TOOLBAR_ICONS_AND_TEXT: + gedit_debug_message (DEBUG_WINDOW, "GEDIT: ICONS_AND_TEXT"); + gtk_toolbar_set_style ( + GTK_TOOLBAR (toolbar), + GTK_TOOLBAR_BOTH); + break; + + case GEDIT_TOOLBAR_ICONS_BOTH_HORIZ: + gedit_debug_message (DEBUG_WINDOW, "GEDIT: 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 (GeditWindow *window, + GeditWindow *origin) +{ + gboolean visible; + GeditToolbarSetting style; + GtkAction *action; + + if (origin == NULL) + visible = gedit_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 = gedit_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 (GeditWindow *window, + GeditTab *tab) +{ + gint tab_number; + GtkNotebook *notebook; + GtkAction *action; + + gedit_debug (DEBUG_WINDOW); + + notebook = GTK_NOTEBOOK (_gedit_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 (GeditWindow *window) +{ + GeditTab *tab; + GtkAction *action; + + gedit_debug (DEBUG_WINDOW); + + tab = gedit_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, + GeditWindow *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) + { + GeditTabState state; + gboolean state_normal; + + state = gedit_tab_get_state (window->priv->active_tab); + state_normal = (state == GEDIT_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 (GeditWindow *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 (GeditWindow *window, + GeditTab *tab) +{ + GeditDocument *doc; + GeditView *view; + GtkAction *action; + gboolean b; + gboolean state_normal; + gboolean editable; + GeditTabState state; + GtkClipboard *clipboard; + GeditLockdownMask lockdown; + + g_return_if_fail (GEDIT_TAB (tab)); + + gedit_debug (DEBUG_WINDOW); + + lockdown = gedit_app_get_lockdown (gedit_app_get_default ()); + + state = gedit_tab_get_state (tab); + state_normal = (state == GEDIT_TAB_STATE_NORMAL); + + view = gedit_tab_get_view (tab); + editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (view)); + + doc = GEDIT_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 == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) || + (state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)) && + !gedit_document_get_readonly (doc) && + !(lockdown & GEDIT_LOCKDOWN_SAVE_TO_DISK)); + + action = gtk_action_group_get_action (window->priv->action_group, + "FileSaveAs"); + gtk_action_set_sensitive (action, + (state_normal || + (state == GEDIT_TAB_STATE_SAVING_ERROR) || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION) || + (state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)) && + !(lockdown & GEDIT_LOCKDOWN_SAVE_TO_DISK)); + + action = gtk_action_group_get_action (window->priv->action_group, + "FileRevert"); + gtk_action_set_sensitive (action, + (state_normal || + (state == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)) && + !gedit_document_is_untitled (doc)); + + action = gtk_action_group_get_action (window->priv->action_group, + "FilePrintPreview"); + gtk_action_set_sensitive (action, + state_normal && + !(lockdown & GEDIT_LOCKDOWN_PRINTING)); + + action = gtk_action_group_get_action (window->priv->action_group, + "FilePrint"); + gtk_action_set_sensitive (action, + (state_normal || + (state == GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)) && + !(lockdown & GEDIT_LOCKDOWN_PRINTING)); + + action = gtk_action_group_get_action (window->priv->close_action_group, + "FileClose"); + + gtk_action_set_sensitive (action, + (state != GEDIT_TAB_STATE_CLOSING) && + (state != GEDIT_TAB_STATE_SAVING) && + (state != GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW) && + (state != GEDIT_TAB_STATE_PRINTING) && + (state != GEDIT_TAB_STATE_PRINT_PREVIEWING) && + (state != GEDIT_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 == GEDIT_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 == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)); + + action = gtk_action_group_get_action (window->priv->action_group, + "SearchIncrementalSearch"); + gtk_action_set_sensitive (action, + (state_normal || + state == GEDIT_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 = gedit_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 == GEDIT_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 == GEDIT_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 == GEDIT_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 == GEDIT_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)); + + action = gtk_action_group_get_action (window->priv->action_group, + "ViewHighlightMode"); + gtk_action_set_sensitive (action, + (state != GEDIT_TAB_STATE_CLOSING) && + gedit_prefs_manager_get_enable_syntax_highlighting ()); + + update_next_prev_doc_sensitivity (window, tab); + + gedit_plugins_engine_update_plugins_ui (gedit_plugins_engine_get_default (), + window); +} + +static void +language_toggled (GtkToggleAction *action, + GeditWindow *window) +{ + GeditDocument *doc; + GtkSourceLanguage *lang; + const gchar *lang_id; + + if (gtk_toggle_action_get_active (action) == FALSE) + return; + + doc = gedit_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 ( + gedit_get_language_manager (), + lang_id); + if (lang == NULL) + { + g_warning ("Could not get language %s\n", lang_id); + } + } + + gedit_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, + GeditWindow *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 = gedit_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 = gedit_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 (GeditWindow *window) +{ + GtkRadioAction *action_none; + GSList *languages; + GSList *l; + guint id; + gint i; + + gedit_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 = gedit_language_manager_list_languages_sorted ( + gedit_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 (GeditWindow *window) +{ + GeditDocument *doc; + GList *actions; + GList *l; + GtkAction *action; + GtkSourceLanguage *lang; + const gchar *lang_id; + + doc = gedit_window_get_active_document (window); + if (doc == NULL) + return; + + lang = gedit_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 +_gedit_recent_add (GeditWindow *window, + const gchar *uri, + const gchar *mime) +{ + GtkRecentManager *recent_manager; + GtkRecentData *recent_data; + + static gchar *groups[2] = { + "gedit", + 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 +_gedit_recent_remove (GeditWindow *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, + GeditWindow *window) +{ + GSList *uris = NULL; + + uris = g_slist_prepend (uris, (gpointer) uri); + + if (gedit_commands_load_uris (window, uris, NULL, 0) != 1) + { + _gedit_recent_remove (window, uri); + } + + g_slist_free (uris); +} + +static void +recent_chooser_item_activated (GtkRecentChooser *chooser, + GeditWindow *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, + GeditWindow *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 (GeditWindow *window); + +static void +recent_manager_changed (GtkRecentManager *manager, + GeditWindow *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 (GeditWindow *window) +{ + GeditWindowPrivate *p = window->priv; + GtkRecentManager *recent_manager; + gint max_recents; + GList *actions, *l, *items; + GList *filtered_items = NULL; + gint i; + + gedit_debug (DEBUG_WINDOW); + + max_recents = gedit_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, "gedit")) + 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 = gedit_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 = gedit_utils_uri_for_display (gtk_recent_info_get_uri (info)); + ruri = gedit_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, + GeditWindow *window) +{ + gboolean visible; + GtkAction *action; + + visible = GTK_WIDGET_VISIBLE (toolbar); + + if (gedit_prefs_manager_toolbar_visible_can_set ()) + gedit_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 (GeditWindow *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), + gedit_prefs_manager_get_max_recents ()); + + filter = gtk_recent_filter_new (); + gtk_recent_filter_add_group (filter, "gedit"); + 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 (GeditWindow *window, + GtkWidget *main_box) +{ + GtkActionGroup *action_group; + GtkAction *action; + GtkUIManager *manager; + GtkRecentManager *recent_manager; + GError *error = NULL; + gchar *ui_file; + + gedit_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 ("GeditWindowAlwaysSensitiveActions"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + gedit_always_sensitive_menu_entries, + G_N_ELEMENTS (gedit_always_sensitive_menu_entries), + window); + gtk_action_group_add_toggle_actions (action_group, + gedit_always_sensitive_toggle_menu_entries, + G_N_ELEMENTS (gedit_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 ("GeditWindowActions"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + gedit_menu_entries, + G_N_ELEMENTS (gedit_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 ("GeditQuitWindowActions"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + gedit_quit_menu_entries, + G_N_ELEMENTS (gedit_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 ("GeditCloseWindowActions"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + gedit_close_menu_entries, + G_N_ELEMENTS (gedit_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 ("GeditWindowPanesActions"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_toggle_actions (action_group, + gedit_panes_toggle_menu_entries, + G_N_ELEMENTS (gedit_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 = gedit_dirs_get_ui_file (GEDIT_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 gedit-ui.xml */ + { + guint merge_id; + GeditLockdownMask 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 = gedit_app_get_lockdown (gedit_app_get_default ()); + action = gtk_action_group_get_action (window->priv->action_group, + "FilePageSetup"); + gtk_action_set_sensitive (action, + !(lockdown & GEDIT_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, + GeditWindow *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 (GeditTab *tab) +{ + GeditDocument *doc; + gchar *uri; + gchar *ruri; + gchar *tip; + + doc = gedit_tab_get_document (tab); + + uri = gedit_document_get_uri_for_display (doc); + ruri = gedit_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 (GeditWindow *window) +{ + GeditWindowPrivate *p = window->priv; + GList *actions, *l; + gint n, i; + guint id; + GSList *group = NULL; + + gedit_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 = _gedit_tab_get_name (GEDIT_TAB (tab)); + name = gedit_utils_escape_underscores (tab_name, -1); + tip = get_menu_tip_for_tab (GEDIT_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 (GEDIT_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 (GeditWindow *window, + GeditWindow *origin) +{ + GtkAction *action; + + gboolean visible; + + if (origin == NULL) + visible = gedit_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, + GeditWindow *window) +{ + gboolean visible; + GtkAction *action; + + visible = GTK_WIDGET_VISIBLE (statusbar); + + if (gedit_prefs_manager_statusbar_visible_can_set ()) + gedit_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 (GeditStatusComboBox *combo, + GtkMenuItem *item, + GeditWindow *window) +{ + GeditView *view; + guint width_data = 0; + + view = gedit_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, + GeditWindow *window) +{ + GeditView *view; + + view = gedit_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 (GeditStatusComboBox *combo, + GtkMenuItem *item, + GeditWindow *window) +{ + GeditDocument *doc; + GtkSourceLanguage *language; + + doc = gedit_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); + gedit_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 (GeditWindow *window) +{ + static TabWidthDefinition defs[] = { + {"2", 2}, + {"4", 4}, + {"8", 8}, + {"", 0}, /* custom size */ + {NULL, 0} + }; + + GeditStatusComboBox *combo = GEDIT_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)); + + gedit_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 (); + gedit_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")); + gedit_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 (GeditWindow *window) +{ + GtkSourceLanguageManager *manager; + GSList *languages; + GSList *item; + GtkWidget *menu_item; + const gchar *name; + + manager = gedit_get_language_manager (); + languages = gedit_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); + gedit_status_combo_box_add_item (GEDIT_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); + + gedit_status_combo_box_add_item (GEDIT_STATUS_COMBO_BOX (window->priv->language_combo), + GTK_MENU_ITEM (menu_item), + name); + } + + g_slist_free (languages); +} + +static void +create_statusbar (GeditWindow *window, + GtkWidget *main_box) +{ + gedit_debug (DEBUG_WINDOW); + + window->priv->statusbar = gedit_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 = gedit_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 = gedit_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 GeditWindow * +clone_window (GeditWindow *origin) +{ + GeditWindow *window; + GdkScreen *screen; + GeditApp *app; + gint panel_page; + + gedit_debug (DEBUG_WINDOW); + + app = gedit_app_get_default (); + + screen = gtk_window_get_screen (GTK_WINDOW (origin)); + window = gedit_app_create_window (app, screen); + + if ((origin->priv->window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0) + { + gint w, h; + + gedit_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 = _gedit_panel_get_active_item_id (GEDIT_PANEL (origin->priv->side_panel)); + _gedit_panel_set_active_item_by_id (GEDIT_PANEL (window->priv->side_panel), + panel_page); + + panel_page = _gedit_panel_get_active_item_id (GEDIT_PANEL (origin->priv->bottom_panel)); + _gedit_panel_set_active_item_by_id (GEDIT_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, + GeditWindow *window) +{ + gint row, col; + GtkTextIter iter; + GtkTextIter start; + guint tab_size; + GeditView *view; + + gedit_debug (DEBUG_WINDOW); + + if (buffer != GTK_TEXT_BUFFER (gedit_window_get_active_document (window))) + return; + + view = gedit_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); + } + + gedit_statusbar_set_cursor_position ( + GEDIT_STATUSBAR (window->priv->statusbar), + row + 1, + col + 1); +} + +static void +update_overwrite_mode_statusbar (GtkTextView *view, + GeditWindow *window) +{ + if (view != GTK_TEXT_VIEW (gedit_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 + */ + gedit_statusbar_set_overwrite ( + GEDIT_STATUSBAR (window->priv->statusbar), + !gtk_text_view_get_overwrite (view)); +} + +#define MAX_TITLE_LENGTH 100 + +static void +set_title (GeditWindow *window) +{ + GeditDocument *doc = NULL; + gchar *name; + gchar *dirname = NULL; + gchar *title = NULL; + gint len; + + if (window->priv->active_tab == NULL) + { +#ifdef OS_OSX + gedit_osx_set_window_title (window, "gedit", NULL); +#else + gtk_window_set_title (GTK_WINDOW (window), "gedit"); +#endif + return; + } + + doc = gedit_tab_get_document (window->priv->active_tab); + g_return_if_fail (doc != NULL); + + name = gedit_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 = gedit_utils_str_middle_truncate (name, + MAX_TITLE_LENGTH); + g_free (name); + name = tmp; + } + else + { + GFile *file; + + file = gedit_document_get_location (doc); + if (file != NULL) + { + gchar *str; + + str = gedit_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 = gedit_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 (gedit_document_get_readonly (doc)) + { + if (dirname != NULL) + title = g_strdup_printf ("%s [%s] (%s) - gedit", + name, + _("Read-Only"), + dirname); + else + title = g_strdup_printf ("%s [%s] - gedit", + name, + _("Read-Only")); + } + else + { + if (dirname != NULL) + title = g_strdup_printf ("%s (%s) - gedit", + name, + dirname); + else + title = g_strdup_printf ("%s - gedit", + name); + } + +#ifdef OS_OSX + gedit_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 (GeditWindow *window, + GtkMenuItem *item) +{ + g_signal_handlers_block_by_func (window->priv->tab_width_combo, + tab_width_combo_changed, + window); + + gedit_status_combo_box_set_item (GEDIT_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, + GeditWindow *window) +{ + GeditView *view = GEDIT_VIEW (object); + gboolean active = gtk_source_view_get_insert_spaces_instead_of_tabs ( + GTK_SOURCE_VIEW (view)); + GList *children = gedit_status_combo_box_get_items ( + GEDIT_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, + GeditWindow *window) +{ + GList *items; + GList *item; + GeditStatusComboBox *combo = GEDIT_STATUS_COMBO_BOX (window->priv->tab_width_combo); + guint new_tab_width; + gboolean found = FALSE; + + items = gedit_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); + gedit_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, + GeditWindow *window) +{ + GList *items; + GList *item; + GeditStatusComboBox *combo = GEDIT_STATUS_COMBO_BOX (window->priv->language_combo); + GtkSourceLanguage *new_language; + const gchar *new_id; + + items = gedit_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); + + gedit_status_combo_box_set_item (GEDIT_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, + GeditWindow *window) +{ + GeditView *view; + GeditTab *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 = GEDIT_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 (gedit_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 (gedit_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 = gedit_tab_get_view (tab); + + /* sync the statusbar */ + update_cursor_position_statusbar (GTK_TEXT_BUFFER (gedit_tab_get_document (tab)), + window); + gedit_statusbar_set_overwrite (GEDIT_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 (gedit_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 (gedit_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 (GeditWindow *window) +{ + GtkAction *action; + GeditLockdownMask lockdown; + + lockdown = gedit_app_get_lockdown (gedit_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 & GEDIT_WINDOW_STATE_SAVING) && + !(window->priv->state & GEDIT_WINDOW_STATE_PRINTING)); + + action = gtk_action_group_get_action (window->priv->action_group, + "FileCloseAll"); + gtk_action_set_sensitive (action, + !(window->priv->state & GEDIT_WINDOW_STATE_SAVING) && + !(window->priv->state & GEDIT_WINDOW_STATE_PRINTING)); + + action = gtk_action_group_get_action (window->priv->action_group, + "FileSaveAll"); + gtk_action_set_sensitive (action, + !(window->priv->state & GEDIT_WINDOW_STATE_PRINTING) && + !(lockdown & GEDIT_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 & GEDIT_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 & GEDIT_WINDOW_STATE_SAVING_SESSION)); + + gtk_action_group_set_sensitive (window->priv->recents_action_group, + !(window->priv->state & GEDIT_WINDOW_STATE_SAVING_SESSION)); + + gedit_notebook_set_close_buttons_sensitive (GEDIT_NOTEBOOK (window->priv->notebook), + !(window->priv->state & GEDIT_WINDOW_STATE_SAVING_SESSION)); + + gedit_notebook_set_tab_drag_and_drop_enabled (GEDIT_NOTEBOOK (window->priv->notebook), + !(window->priv->state & GEDIT_WINDOW_STATE_SAVING_SESSION)); + + if ((window->priv->state & GEDIT_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) +{ + GeditTab *tab = GEDIT_TAB (widget); + gboolean *enabled = (gboolean *) data; + + gedit_tab_set_auto_save_enabled (tab, *enabled); +} + +void +_gedit_window_set_lockdown (GeditWindow *window, + GeditLockdownMask lockdown) +{ + GeditTab *tab; + GtkAction *action; + gboolean autosave; + + /* start/stop autosave in each existing tab */ + autosave = gedit_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 = gedit_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 & GEDIT_WINDOW_STATE_PRINTING) && + !(lockdown & GEDIT_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 & GEDIT_LOCKDOWN_PRINT_SETUP)); +#endif +} + +static void +analyze_tab_state (GeditTab *tab, + GeditWindow *window) +{ + GeditTabState ts; + + ts = gedit_tab_get_state (tab); + + switch (ts) + { + case GEDIT_TAB_STATE_LOADING: + case GEDIT_TAB_STATE_REVERTING: + window->priv->state |= GEDIT_WINDOW_STATE_LOADING; + break; + + case GEDIT_TAB_STATE_SAVING: + window->priv->state |= GEDIT_WINDOW_STATE_SAVING; + break; + + case GEDIT_TAB_STATE_PRINTING: + case GEDIT_TAB_STATE_PRINT_PREVIEWING: + window->priv->state |= GEDIT_WINDOW_STATE_PRINTING; + break; + + case GEDIT_TAB_STATE_LOADING_ERROR: + case GEDIT_TAB_STATE_REVERTING_ERROR: + case GEDIT_TAB_STATE_SAVING_ERROR: + case GEDIT_TAB_STATE_GENERIC_ERROR: + window->priv->state |= GEDIT_WINDOW_STATE_ERROR; + ++window->priv->num_tabs_with_error; + default: + /* NOP */ + break; + } +} + +static void +update_window_state (GeditWindow *window) +{ + GeditWindowState old_ws; + gint old_num_of_errors; + + gedit_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 & GEDIT_WINDOW_STATE_SAVING_SESSION; + + window->priv->num_tabs_with_error = 0; + + gtk_container_foreach (GTK_CONTAINER (window->priv->notebook), + (GtkCallback)analyze_tab_state, + window); + + gedit_debug_message (DEBUG_WINDOW, "New state: %x", window->priv->state); + + if (old_ws != window->priv->state) + { + set_sensitivity_according_to_window_state (window); + + gedit_statusbar_set_window_state (GEDIT_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) + { + gedit_statusbar_set_window_state (GEDIT_STATUSBAR (window->priv->statusbar), + window->priv->state, + window->priv->num_tabs_with_error); + } +} + +static void +sync_state (GeditTab *tab, + GParamSpec *pspec, + GeditWindow *window) +{ + gedit_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 (GeditTab *tab, + GParamSpec *pspec, + GeditWindow *window) +{ + GtkAction *action; + gchar *action_name; + gchar *tab_name; + gchar *escaped_name; + gchar *tip; + gint n; + GeditDocument *doc; + + if (tab == window->priv->active_tab) + { + set_title (window); + + doc = gedit_tab_get_document (tab); + action = gtk_action_group_get_action (window->priv->action_group, + "FileRevert"); + gtk_action_set_sensitive (action, + !gedit_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 = _gedit_tab_get_name (tab); + escaped_name = gedit_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); + + gedit_plugins_engine_update_plugins_ui (gedit_plugins_engine_get_default (), + window); +} + +static GeditWindow * +get_drop_window (GtkWidget *widget) +{ + GtkWidget *target_window; + + target_window = gtk_widget_get_toplevel (widget); + g_return_val_if_fail (GEDIT_IS_WINDOW (target_window), NULL); + + if ((GEDIT_WINDOW(target_window)->priv->state & GEDIT_WINDOW_STATE_SAVING_SESSION) != 0) + return NULL; + + return GEDIT_WINDOW (target_window); +} + +static void +load_uris_from_drop (GeditWindow *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); + gedit_commands_load_uris (window, + uris, + NULL, + 0); + + g_slist_free (uris); +} + +/* Handle drops on the GeditWindow */ +static void +drag_data_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint timestamp, + gpointer data) +{ + GeditWindow *window; + gchar **uri_list; + + window = get_drop_window (widget); + + if (window == NULL) + return; + + if (info == TARGET_URI_LIST) + { + uri_list = gedit_utils_drop_get_uris(selection_data); + load_uris_from_drop (window, uri_list); + g_strfreev (uri_list); + } +} + +/* Handle drops on the GeditView */ +static void +drop_uris_cb (GtkWidget *widget, + gchar **uri_list) +{ + GeditWindow *window; + + window = get_drop_window (widget); + + if (window == NULL) + return; + + load_uris_from_drop (window, uri_list); +} + +static void +fullscreen_controls_show (GeditWindow *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) +{ + GeditWindow *window = GEDIT_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 (GeditWindow *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, + GeditWindow *window) +{ + show_hide_fullscreen_toolbar (window, TRUE, 0); + + return FALSE; +} + +static gboolean +on_fullscreen_controls_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event, + GeditWindow *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 (GeditWindow *window) +{ + GeditWindowPrivate *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 (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + gboolean sensitive; + GtkAction *action; + + if (doc != gedit_window_get_active_document (window)) + return; + + sensitive = gedit_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 (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + GtkAction *action; + gboolean sensitive; + + sensitive = gtk_source_buffer_can_undo (GTK_SOURCE_BUFFER (doc)); + + if (doc != gedit_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 (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + GtkAction *action; + gboolean sensitive; + + sensitive = gtk_source_buffer_can_redo (GTK_SOURCE_BUFFER (doc)); + + if (doc != gedit_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 (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + GeditTab *tab; + GeditView *view; + GtkAction *action; + GeditTabState state; + gboolean state_normal; + gboolean editable; + + gedit_debug (DEBUG_WINDOW); + + if (doc != gedit_window_get_active_document (window)) + return; + + tab = gedit_tab_get_from_document (doc); + state = gedit_tab_get_state (tab); + state_normal = (state == GEDIT_TAB_STATE_NORMAL); + + view = gedit_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 == GEDIT_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))); + + gedit_plugins_engine_update_plugins_ui (gedit_plugins_engine_get_default (), + window); +} + +static void +sync_languages_menu (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + update_languages_menu (window); + gedit_plugins_engine_update_plugins_ui (gedit_plugins_engine_get_default (), + window); +} + +static void +readonly_changed (GeditDocument *doc, + GParamSpec *pspec, + GeditWindow *window) +{ + set_sensitivity_according_to_tab (window, window->priv->active_tab); + + sync_name (window->priv->active_tab, NULL, window); + + gedit_plugins_engine_update_plugins_ui (gedit_plugins_engine_get_default (), + window); +} + +static void +editable_changed (GeditView *view, + GParamSpec *arg1, + GeditWindow *window) +{ + gedit_plugins_engine_update_plugins_ui (gedit_plugins_engine_get_default (), + window); +} + +static void +update_sensitivity_according_to_open_tabs (GeditWindow *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 (GeditNotebook *notebook, + GeditTab *tab, + GeditWindow *window) +{ + GeditView *view; + GeditDocument *doc; + + gedit_debug (DEBUG_WINDOW); + + g_return_if_fail ((window->priv->state & GEDIT_WINDOW_STATE_SAVING_SESSION) == 0); + + ++window->priv->num_tabs; + + update_sensitivity_according_to_open_tabs (window); + + view = gedit_tab_get_view (tab); + doc = gedit_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 (GeditNotebook *notebook, + GeditTab *tab, + GeditWindow *window) +{ + GeditView *view; + GeditDocument *doc; + + gedit_debug (DEBUG_WINDOW); + + g_return_if_fail ((window->priv->state & GEDIT_WINDOW_STATE_SAVING_SESSION) == 0); + + --window->priv->num_tabs; + + view = gedit_tab_get_view (tab); + doc = gedit_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 == gedit_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 == gedit_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 == gedit_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 */ + gedit_statusbar_set_cursor_position ( + GEDIT_STATUSBAR (window->priv->statusbar), + -1, + -1); + + gedit_statusbar_clear_overwrite ( + GEDIT_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) + { + gedit_plugins_engine_update_plugins_ui (gedit_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 (GeditNotebook *notebook, + GeditWindow *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 (GeditNotebook *notebook, + GeditTab *tab, + GeditWindow *window) +{ + GeditWindow *new_window; + + new_window = clone_window (window); + + gedit_notebook_move_tab (notebook, + GEDIT_NOTEBOOK (_gedit_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 (GeditNotebook *notebook, + GeditTab *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. */ + _gedit_cmd_file_close_tab (tab, GEDIT_WINDOW (window)); +} + +static gboolean +show_notebook_popup_menu (GtkNotebook *notebook, + GeditWindow *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 (gedit_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, + gedit_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, + GeditWindow *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, + GeditWindow *window) +{ + /* Only respond if the notebook is the actual focus */ + if (GEDIT_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, + GeditWindow *window) +{ + window->priv->side_panel_size = allocation->width; +} + +static void +bottom_panel_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GeditWindow *window) +{ + window->priv->bottom_panel_size = allocation->height; +} + +static void +hpaned_restore_position (GtkWidget *widget, + GeditWindow *window) +{ + gint pos; + + gedit_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, + GeditWindow *window) +{ + gint pos; + + gedit_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, + GeditWindow *window) +{ + gboolean visible; + GtkAction *action; + + visible = GTK_WIDGET_VISIBLE (side_panel); + + if (gedit_prefs_manager_side_pane_visible_can_set ()) + gedit_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 ( + gedit_tab_get_view (GEDIT_TAB (window->priv->active_tab)))); +} + +static void +create_side_panel (GeditWindow *window) +{ + GtkWidget *documents_panel; + + gedit_debug (DEBUG_WINDOW); + + window->priv->side_panel = gedit_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 = gedit_documents_panel_new (window); + gedit_panel_add_item_with_stock_icon (GEDIT_PANEL (window->priv->side_panel), + documents_panel, + _("Documents"), + GTK_STOCK_FILE); +} + +static void +bottom_panel_visibility_changed (GeditPanel *bottom_panel, + GeditWindow *window) +{ + gboolean visible; + GtkAction *action; + + visible = GTK_WIDGET_VISIBLE (bottom_panel); + + if (gedit_prefs_manager_bottom_panel_visible_can_set ()) + gedit_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 ( + gedit_tab_get_view (GEDIT_TAB (window->priv->active_tab)))); +} + +static void +bottom_panel_item_removed (GeditPanel *panel, + GtkWidget *item, + GeditWindow *window) +{ + if (gedit_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 (GeditPanel *panel, + GtkWidget *item, + GeditWindow *window) +{ + /* if it's the first item added, set the menu item + * sensitive and if needed show the panel */ + if (gedit_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 (GeditWindow *window) +{ + gedit_debug (DEBUG_WINDOW); + + window->priv->bottom_panel = gedit_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 (GeditWindow *window) +{ + gint active_page; + + gedit_debug (DEBUG_WINDOW); + + /* side pane */ + active_page = gedit_prefs_manager_get_side_panel_active_page (); + _gedit_panel_set_active_item_by_id (GEDIT_PANEL (window->priv->side_panel), + active_page); + + if (gedit_prefs_manager_get_side_pane_visible ()) + { + gtk_widget_show (window->priv->side_panel); + } + + /* bottom pane, it can be empty */ + if (gedit_panel_get_n_items (GEDIT_PANEL (window->priv->bottom_panel)) > 0) + { + active_page = gedit_prefs_manager_get_bottom_panel_active_page (); + _gedit_panel_set_active_item_by_id (GEDIT_PANEL (window->priv->bottom_panel), + active_page); + + if (gedit_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, + GeditWindow *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 (GeditWindow *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 (GeditWindow *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 gedit")); + + 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 (GeditWindow *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 (GeditWindow *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 +gedit_window_init (GeditWindow *window) +{ + GtkWidget *main_box; + GtkTargetList *tl; + + gedit_debug (DEBUG_WINDOW); + + window->priv = GEDIT_WINDOW_GET_PRIVATE (window); + window->priv->active_tab = NULL; + window->priv->num_tabs = 0; + window->priv->removing_tabs = FALSE; + window->priv->state = GEDIT_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 = gedit_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 */ + gedit_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); + + gedit_debug_message (DEBUG_WINDOW, "Create gedit notebook"); + window->priv->notebook = gedit_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 = gedit_prefs_manager_get_side_panel_size (); + window->priv->bottom_panel_size = gedit_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); + + gedit_debug_message (DEBUG_WINDOW, "Update plugins ui"); + + gedit_plugins_engine_activate_plugins (gedit_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 + + gedit_debug_message (DEBUG_WINDOW, "END"); +} + +/** + * gedit_window_get_active_view: + * @window: a #GeditWindow + * + * Gets the active #GeditView. + * + * Returns: the active #GeditView + */ +GeditView * +gedit_window_get_active_view (GeditWindow *window) +{ + GeditView *view; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + if (window->priv->active_tab == NULL) + return NULL; + + view = gedit_tab_get_view (GEDIT_TAB (window->priv->active_tab)); + + return view; +} + +/** + * gedit_window_get_active_document: + * @window: a #GeditWindow + * + * Gets the active #GeditDocument. + * + * Returns: the active #GeditDocument + */ +GeditDocument * +gedit_window_get_active_document (GeditWindow *window) +{ + GeditView *view; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + view = gedit_window_get_active_view (window); + if (view == NULL) + return NULL; + + return GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); +} + +GtkWidget * +_gedit_window_get_notebook (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->notebook; +} + +/** + * gedit_window_create_tab: + * @window: a #GeditWindow + * @jump_to: %TRUE to set the new #GeditTab as active + * + * Creates a new #GeditTab and adds the new tab to the #GeditNotebook. + * In case @jump_to is %TRUE the #GeditNotebook switches to that new #GeditTab. + * + * Returns: a new #GeditTab + */ +GeditTab * +gedit_window_create_tab (GeditWindow *window, + gboolean jump_to) +{ + GeditTab *tab; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + tab = GEDIT_TAB (_gedit_tab_new ()); + gtk_widget_show (GTK_WIDGET (tab)); + + gedit_notebook_add_tab (GEDIT_NOTEBOOK (window->priv->notebook), + tab, + -1, + jump_to); + + if (!GTK_WIDGET_VISIBLE (window)) + { + gtk_window_present (GTK_WINDOW (window)); + } + + return tab; +} + +/** + * gedit_window_create_tab_from_uri: + * @window: a #GeditWindow + * @uri: the uri of the document + * @encoding: a #GeditEncoding + * @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 #GeditTab as active + * + * Creates a new #GeditTab loading the document specified by @uri. + * In case @jump_to is %TRUE the #GeditNotebook swithes to that new #GeditTab. + * Whether @create is %TRUE, creates a new empty document if location does + * not refer to an existing file + * + * Returns: a new #GeditTab + */ +GeditTab * +gedit_window_create_tab_from_uri (GeditWindow *window, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create, + gboolean jump_to) +{ + GtkWidget *tab; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail (uri != NULL, NULL); + + tab = _gedit_tab_new_from_uri (uri, + encoding, + line_pos, + create); + if (tab == NULL) + return NULL; + + gtk_widget_show (tab); + + gedit_notebook_add_tab (GEDIT_NOTEBOOK (window->priv->notebook), + GEDIT_TAB (tab), + -1, + jump_to); + + + if (!GTK_WIDGET_VISIBLE (window)) + { + gtk_window_present (GTK_WINDOW (window)); + } + + return GEDIT_TAB (tab); +} + +/** + * gedit_window_get_active_tab: + * @window: a GeditWindow + * + * Gets the active #GeditTab in the @window. + * + * Returns: the active #GeditTab in the @window. + */ +GeditTab * +gedit_window_get_active_tab (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return (window->priv->active_tab == NULL) ? + NULL : GEDIT_TAB (window->priv->active_tab); +} + +static void +add_document (GeditTab *tab, GList **res) +{ + GeditDocument *doc; + + doc = gedit_tab_get_document (tab); + + *res = g_list_prepend (*res, doc); +} + +/** + * gedit_window_get_documents: + * @window: a #GeditWindow + * + * 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 * +gedit_window_get_documents (GeditWindow *window) +{ + GList *res = NULL; + + g_return_val_if_fail (GEDIT_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 (GeditTab *tab, GList **res) +{ + GeditView *view; + + view = gedit_tab_get_view (tab); + + *res = g_list_prepend (*res, view); +} + +/** + * gedit_window_get_views: + * @window: a #GeditWindow + * + * 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 * +gedit_window_get_views (GeditWindow *window) +{ + GList *res = NULL; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + gtk_container_foreach (GTK_CONTAINER (window->priv->notebook), + (GtkCallback)add_view, + &res); + + res = g_list_reverse (res); + + return res; +} + +/** + * gedit_window_close_tab: + * @window: a #GeditWindow + * @tab: the #GeditTab to close + * + * Closes the @tab. + */ +void +gedit_window_close_tab (GeditWindow *window, + GeditTab *tab) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (GEDIT_IS_TAB (tab)); + g_return_if_fail ((gedit_tab_get_state (tab) != GEDIT_TAB_STATE_SAVING) && + (gedit_tab_get_state (tab) != GEDIT_TAB_STATE_SHOWING_PRINT_PREVIEW)); + + gedit_notebook_remove_tab (GEDIT_NOTEBOOK (window->priv->notebook), + tab); +} + +/** + * gedit_window_close_all_tabs: + * @window: a #GeditWindow + * + * Closes all opened tabs. + */ +void +gedit_window_close_all_tabs (GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (!(window->priv->state & GEDIT_WINDOW_STATE_SAVING) && + !(window->priv->state & GEDIT_WINDOW_STATE_SAVING_SESSION)); + + window->priv->removing_tabs = TRUE; + + gedit_notebook_remove_all_tabs (GEDIT_NOTEBOOK (window->priv->notebook)); + + window->priv->removing_tabs = FALSE; +} + +/** + * gedit_window_close_tabs: + * @window: a #GeditWindow + * @tabs: a list of #GeditTab + * + * Closes all tabs specified by @tabs. + */ +void +gedit_window_close_tabs (GeditWindow *window, + const GList *tabs) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (!(window->priv->state & GEDIT_WINDOW_STATE_SAVING) && + !(window->priv->state & GEDIT_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; + + gedit_notebook_remove_tab (GEDIT_NOTEBOOK (window->priv->notebook), + GEDIT_TAB (tabs->data)); + + tabs = g_list_next (tabs); + } + + g_return_if_fail (window->priv->removing_tabs == FALSE); +} + +GeditWindow * +_gedit_window_move_tab_to_new_window (GeditWindow *window, + GeditTab *tab) +{ + GeditWindow *new_window; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail (GEDIT_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); + + gedit_notebook_move_tab (GEDIT_NOTEBOOK (window->priv->notebook), + GEDIT_NOTEBOOK (new_window->priv->notebook), + tab, + -1); + + gtk_widget_show (GTK_WIDGET (new_window)); + + return new_window; +} + +/** + * gedit_window_set_active_tab: + * @window: a #GeditWindow + * @tab: a #GeditTab + * + * Switches to the tab that matches with @tab. + */ +void +gedit_window_set_active_tab (GeditWindow *window, + GeditTab *tab) +{ + gint page_num; + + g_return_if_fail (GEDIT_IS_WINDOW (window)); + g_return_if_fail (GEDIT_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); +} + +/** + * gedit_window_get_group: + * @window: a #GeditWindow + * + * Gets the #GtkWindowGroup in which @window resides. + * + * Returns: the #GtkWindowGroup + */ +GtkWindowGroup * +gedit_window_get_group (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->window_group; +} + +gboolean +_gedit_window_is_removing_tabs (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), FALSE); + + return window->priv->removing_tabs; +} + +/** + * gedit_window_get_ui_manager: + * @window: a #GeditWindow + * + * Gets the #GtkUIManager associated with the @window. + * + * Returns: the #GtkUIManager of the @window. + */ +GtkUIManager * +gedit_window_get_ui_manager (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->manager; +} + +/** + * gedit_window_get_side_panel: + * @window: a #GeditWindow + * + * Gets the side #GeditPanel of the @window. + * + * Returns: the side #GeditPanel. + */ +GeditPanel * +gedit_window_get_side_panel (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return GEDIT_PANEL (window->priv->side_panel); +} + +/** + * gedit_window_get_bottom_panel: + * @window: a #GeditWindow + * + * Gets the bottom #GeditPanel of the @window. + * + * Returns: the bottom #GeditPanel. + */ +GeditPanel * +gedit_window_get_bottom_panel (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return GEDIT_PANEL (window->priv->bottom_panel); +} + +/** + * gedit_window_get_statusbar: + * @window: a #GeditWindow + * + * Gets the #GeditStatusbar of the @window. + * + * Returns: the #GeditStatusbar of the @window. + */ +GtkWidget * +gedit_window_get_statusbar (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), 0); + + return window->priv->statusbar; +} + +/** + * gedit_window_get_state: + * @window: a #GeditWindow + * + * Retrieves the state of the @window. + * + * Returns: the current #GeditWindowState of the @window. + */ +GeditWindowState +gedit_window_get_state (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), GEDIT_WINDOW_STATE_NORMAL); + + return window->priv->state; +} + +GFile * +_gedit_window_get_default_location (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->default_location != NULL ? + g_object_ref (window->priv->default_location) : NULL; +} + +void +_gedit_window_set_default_location (GeditWindow *window, + GFile *location) +{ + GFile *dir; + + g_return_if_fail (GEDIT_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; +} + +/** + * gedit_window_get_unsaved_documents: + * @window: a #GeditWindow + * + * Gets the list of documents that need to be saved before closing the window. + * + * Returns: a list of #GeditDocument that need to be saved before closing the window + */ +GList * +gedit_window_get_unsaved_documents (GeditWindow *window) +{ + GList *unsaved_docs = NULL; + GList *tabs; + GList *l; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + tabs = gtk_container_get_children (GTK_CONTAINER (window->priv->notebook)); + + l = tabs; + while (l != NULL) + { + GeditTab *tab; + + tab = GEDIT_TAB (l->data); + + if (!_gedit_tab_can_close (tab)) + { + GeditDocument *doc; + + doc = gedit_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 +_gedit_window_set_saving_session_state (GeditWindow *window, + gboolean saving_session) +{ + GeditWindowState old_state; + + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + old_state = window->priv->state; + + if (saving_session) + window->priv->state |= GEDIT_WINDOW_STATE_SAVING_SESSION; + else + window->priv->state &= ~GEDIT_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, + GeditWindow *window) +{ + gtk_notebook_set_show_tabs (notebook, FALSE); +} + +void +_gedit_window_fullscreen (GeditWindow *window) +{ + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + if (_gedit_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 +_gedit_window_unfullscreen (GeditWindow *window) +{ + gboolean visible; + GtkAction *action; + + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + if (!_gedit_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 +_gedit_window_is_fullscreen (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), FALSE); + + return window->priv->window_state & GDK_WINDOW_STATE_FULLSCREEN; +} + +/** + * gedit_window_get_tab_from_location: + * @window: a #GeditWindow + * @location: a #GFile + * + * Gets the #GeditTab that matches with the given @location. + * + * Returns: the #GeditTab that matches with the given @location. + */ +GeditTab * +gedit_window_get_tab_from_location (GeditWindow *window, + GFile *location) +{ + GList *tabs; + GList *l; + GeditTab *ret = NULL; + + g_return_val_if_fail (GEDIT_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)) + { + GeditDocument *d; + GeditTab *t; + GFile *f; + + t = GEDIT_TAB (l->data); + d = gedit_tab_get_document (t); + + f = gedit_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; +} + +/** + * gedit_window_get_message_bus: + * @window: a #GeditWindow + * + * Gets the #GeditMessageBus associated with @window. The returned reference + * is owned by the window and should not be unreffed. + * + * Return value: the #GeditMessageBus associated with @window + */ +GeditMessageBus * +gedit_window_get_message_bus (GeditWindow *window) +{ + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + return window->priv->message_bus; +} + +/** + * gedit_window_get_tab_from_uri: + * @window: a #GeditWindow + * @uri: the uri to get the #GeditTab + * + * Gets the #GeditTab that matches @uri. + * + * Returns: the #GeditTab associated with @uri. + * + * Deprecated: 2.24: Use gedit_window_get_tab_from_location() instead. + */ +GeditTab * +gedit_window_get_tab_from_uri (GeditWindow *window, + const gchar *uri) +{ + GFile *f; + GeditTab *tab; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + g_return_val_if_fail (uri != NULL, NULL); + + f = g_file_new_for_uri (uri); + tab = gedit_window_get_tab_from_location (window, f); + g_object_unref (f); + + return tab; +} diff --git a/gedit/gedit-window.h b/gedit/gedit-window.h new file mode 100755 index 00000000..e8c7eef3 --- /dev/null +++ b/gedit/gedit-window.h @@ -0,0 +1,195 @@ +/* + * gedit-window.h + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + * + * $Id$ + */ + +#ifndef __GEDIT_WINDOW_H__ +#define __GEDIT_WINDOW_H__ + +#include <gio/gio.h> +#include <gtk/gtk.h> + +#include <gedit/gedit-tab.h> +#include <gedit/gedit-panel.h> +#include <gedit/gedit-message-bus.h> + +G_BEGIN_DECLS + +typedef enum +{ + GEDIT_WINDOW_STATE_NORMAL = 0, + GEDIT_WINDOW_STATE_SAVING = 1 << 1, + GEDIT_WINDOW_STATE_PRINTING = 1 << 2, + GEDIT_WINDOW_STATE_LOADING = 1 << 3, + GEDIT_WINDOW_STATE_ERROR = 1 << 4, + GEDIT_WINDOW_STATE_SAVING_SESSION = 1 << 5 +} GeditWindowState; + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_WINDOW (gedit_window_get_type()) +#define GEDIT_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_WINDOW, GeditWindow)) +#define GEDIT_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_WINDOW, GeditWindowClass)) +#define GEDIT_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_WINDOW)) +#define GEDIT_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_WINDOW)) +#define GEDIT_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_WINDOW, GeditWindowClass)) + +/* Private structure type */ +typedef struct _GeditWindowPrivate GeditWindowPrivate; + +/* + * Main object structure + */ +typedef struct _GeditWindow GeditWindow; + +struct _GeditWindow +{ + GtkWindow window; + + /*< private > */ + GeditWindowPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditWindowClass GeditWindowClass; + +struct _GeditWindowClass +{ + GtkWindowClass parent_class; + + /* Signals */ + void (* tab_added) (GeditWindow *window, + GeditTab *tab); + void (* tab_removed) (GeditWindow *window, + GeditTab *tab); + void (* tabs_reordered) (GeditWindow *window); + void (* active_tab_changed) (GeditWindow *window, + GeditTab *tab); + void (* active_tab_state_changed) + (GeditWindow *window); +}; + +/* + * Public methods + */ +GType gedit_window_get_type (void) G_GNUC_CONST; + +GeditTab *gedit_window_create_tab (GeditWindow *window, + gboolean jump_to); + +GeditTab *gedit_window_create_tab_from_uri (GeditWindow *window, + const gchar *uri, + const GeditEncoding *encoding, + gint line_pos, + gboolean create, + gboolean jump_to); + +void gedit_window_close_tab (GeditWindow *window, + GeditTab *tab); + +void gedit_window_close_all_tabs (GeditWindow *window); + +void gedit_window_close_tabs (GeditWindow *window, + const GList *tabs); + +GeditTab *gedit_window_get_active_tab (GeditWindow *window); + +void gedit_window_set_active_tab (GeditWindow *window, + GeditTab *tab); + +/* Helper functions */ +GeditView *gedit_window_get_active_view (GeditWindow *window); +GeditDocument *gedit_window_get_active_document (GeditWindow *window); + +/* Returns a newly allocated list with all the documents in the window */ +GList *gedit_window_get_documents (GeditWindow *window); + +/* Returns a newly allocated list with all the documents that need to be + saved before closing the window */ +GList *gedit_window_get_unsaved_documents (GeditWindow *window); + +/* Returns a newly allocated list with all the views in the window */ +GList *gedit_window_get_views (GeditWindow *window); + +GtkWindowGroup *gedit_window_get_group (GeditWindow *window); + +GeditPanel *gedit_window_get_side_panel (GeditWindow *window); + +GeditPanel *gedit_window_get_bottom_panel (GeditWindow *window); + +GtkWidget *gedit_window_get_statusbar (GeditWindow *window); + +GtkUIManager *gedit_window_get_ui_manager (GeditWindow *window); + +GeditWindowState gedit_window_get_state (GeditWindow *window); + +GeditTab *gedit_window_get_tab_from_location (GeditWindow *window, + GFile *location); + +GeditTab *gedit_window_get_tab_from_uri (GeditWindow *window, + const gchar *uri); + +/* Message bus */ +GeditMessageBus *gedit_window_get_message_bus (GeditWindow *window); + +/* + * Non exported functions + */ +GtkWidget *_gedit_window_get_notebook (GeditWindow *window); + +GeditWindow *_gedit_window_move_tab_to_new_window (GeditWindow *window, + GeditTab *tab); +gboolean _gedit_window_is_removing_tabs (GeditWindow *window); + +GFile *_gedit_window_get_default_location (GeditWindow *window); + +void _gedit_window_set_default_location (GeditWindow *window, + GFile *location); + +void _gedit_window_set_saving_session_state (GeditWindow *window, + gboolean saving_session); + +void _gedit_window_fullscreen (GeditWindow *window); + +void _gedit_window_unfullscreen (GeditWindow *window); + +gboolean _gedit_window_is_fullscreen (GeditWindow *window); + +/* these are in gedit-window because of screen safety */ +void _gedit_recent_add (GeditWindow *window, + const gchar *uri, + const gchar *mime); +void _gedit_recent_remove (GeditWindow *window, + const gchar *uri); + +G_END_DECLS + +#endif /* __GEDIT_WINDOW_H__ */ diff --git a/gedit/gedit.c b/gedit/gedit.c new file mode 100755 index 00000000..fa53b407 --- /dev/null +++ b/gedit/gedit.c @@ -0,0 +1,766 @@ +/* + * gedit.c + * This file is part of gedit + * + * 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 gedit Team, 2005. See the AUTHORS file for a + * list of people on the gedit 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 "gedit-app.h" +#include "gedit-commands.h" +#include "gedit-debug.h" +#include "gedit-dirs.h" +#include "gedit-encodings.h" +#include "gedit-plugins-engine.h" +#include "gedit-prefs-manager-app.h" +#include "gedit-session.h" +#include "gedit-utils.h" +#include "gedit-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/gedit-osx.h" +#endif + +#ifndef ENABLE_GVFS_METADATA +#include "gedit-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 GeditEncoding *enc; + + while ((enc = gedit_encoding_get_from_index (i)) != NULL) + { + g_print ("%s\n", gedit_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 gedit"), NULL }, + + { "new-document", '\0', 0, G_OPTION_ARG_NONE, &new_document_option, + N_("Create a new document in an existing instance of gedit"), 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 +gedit_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 && + (gedit_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 GeditEncoding *encoding = NULL; + gchar **commands; + gchar **params; + gint workspace; + gint viewport_x; + gint viewport_y; + gchar *display_name; + gint screen_number; + gint i; + GeditApp *app; + GeditWindow *window; + GdkDisplay *display; + GdkScreen *screen; + + g_return_if_fail (message != NULL); + + gedit_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 = gedit_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 = gedit_app_get_default (); + + if (new_window_option) + { + window = gedit_app_create_window (app, screen); + } + else + { + /* get a window in the current workspace (if exists) and raise it */ + window = _gedit_app_get_window_in_viewport (app, + screen, + workspace, + viewport_x, + viewport_y); + } + + if (file_list != NULL) + { + _gedit_cmd_load_files_from_prompt (window, + file_list, + encoding, + line_position); + + if (new_document_option) + gedit_window_create_tab (window, TRUE); + } + else + { + GeditDocument *doc; + doc = gedit_window_get_active_document (window); + + if (doc == NULL || + !gedit_document_is_untouched (doc) || + new_document_option) + gedit_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. + */ + + gedit_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); + + gedit_debug_message (DEBUG_APP, "Display: %s", display_name); + gedit_debug_message (DEBUG_APP, "Screen: %d", screen_number); + + ws = gedit_utils_get_current_workspace (screen); + gedit_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); + } + } + + gedit_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 gedit 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 gedit"); + + g_free (path); +} +#endif + +int +main (int argc, char *argv[]) +{ + GOptionContext *context; + GeditPluginsEngine *engine; + GeditWindow *window; + GeditApp *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 */ + gedit_debug_init (); + gedit_debug_message (DEBUG_APP, "Startup"); + + setlocale (LC_ALL, ""); + + dir = gedit_dirs_get_gedit_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 gedit 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 + gedit_debug_message (DEBUG_APP, "Create bacon connection"); + + connection = bacon_message_connection_new ("gedit"); + + if (connection != NULL) + { + if (!bacon_message_connection_get_is_server (connection)) + { + gedit_debug_message (DEBUG_APP, "I'm a client"); + + gedit_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 + { + gedit_debug_message (DEBUG_APP, "I'm a server"); + + bacon_message_connection_set_callback (connection, + on_message_received, + NULL); + } + } + else + { + g_warning ("Cannot create the 'gedit' connection."); + } +#endif + + gedit_debug_message (DEBUG_APP, "Set icon"); + + dir = gedit_dirs_get_gedit_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/gedit.desktop"); +#else + /* manually set name and icon */ + g_set_application_name("gedit"); + gtk_window_set_default_icon_name ("accessories-text-editor"); +#endif + + /* Load user preferences */ + gedit_debug_message (DEBUG_APP, "Init prefs manager"); + gedit_prefs_manager_app_init (); + + /* Init plugins engine */ + gedit_debug_message (DEBUG_APP, "Init plugins"); + engine = gedit_plugins_engine_get_default (); + + #if !GTK_CHECK_VERSION(3, 0, 0) + gtk_about_dialog_set_url_hook(gedit_utils_activate_url, NULL, NULL); + #endif + /* Initialize session management */ + gedit_debug_message (DEBUG_APP, "Init session manager"); + gedit_session_init (); + +#ifdef OS_OSX + ige_mac_menu_set_global_key_handler_enabled (FALSE); +#endif + + if (gedit_session_is_restored ()) + restored = gedit_session_load (); + + if (!restored) + { + gedit_debug_message (DEBUG_APP, "Analyze command line data"); + gedit_get_command_line_data (); + + gedit_debug_message (DEBUG_APP, "Get default app"); + app = gedit_app_get_default (); + + gedit_debug_message (DEBUG_APP, "Create main window"); + window = gedit_app_create_window (app, NULL); + + if (file_list != NULL) + { + const GeditEncoding *encoding = NULL; + + if (encoding_charset) + encoding = gedit_encoding_get_from_charset (encoding_charset); + + gedit_debug_message (DEBUG_APP, "Load files"); + _gedit_cmd_load_files_from_prompt (window, + file_list, + encoding, + line_position); + } + else + { + gedit_debug_message (DEBUG_APP, "Create tab"); + gedit_window_create_tab (window, TRUE); + } + + gedit_debug_message (DEBUG_APP, "Show window"); + gtk_widget_show (GTK_WIDGET (window)); + + free_command_line_data (); + } + + gedit_debug_message (DEBUG_APP, "Start gtk-main"); + +#ifdef OS_OSX + gedit_osx_init(gedit_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); + gedit_prefs_manager_app_shutdown (); + +#ifndef ENABLE_GVFS_METADATA + gedit_metadata_manager_shutdown (); +#endif + + return 0; +} + diff --git a/gedit/gedit.rc b/gedit/gedit.rc new file mode 100755 index 00000000..17d8eb13 --- /dev/null +++ b/gedit/gedit.rc @@ -0,0 +1 @@ +A ICON MOVEABLE PURE LOADONCALL DISCARDABLE "../pixmaps/gedit.ico"
diff --git a/gedit/gedittextregion.c b/gedit/gedittextregion.c new file mode 100755 index 00000000..f6790489 --- /dev/null +++ b/gedit/gedittextregion.c @@ -0,0 +1,647 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * gedittextregion.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 "gedittextregion.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 _GeditTextRegion { + GtkTextBuffer *buffer; + GList *subregions; + guint32 time_stamp; +}; + +typedef struct _GeditTextRegionIteratorReal GeditTextRegionIteratorReal; + +struct _GeditTextRegionIteratorReal { + GeditTextRegion *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 (GeditTextRegion *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 + ---------------------------------------------------------------------- */ + +GeditTextRegion * +gedit_text_region_new (GtkTextBuffer *buffer) +{ + GeditTextRegion *region; + + g_return_val_if_fail (buffer != NULL, NULL); + + region = g_new (GeditTextRegion, 1); + region->buffer = buffer; + region->subregions = NULL; + region->time_stamp = 0; + + return region; +} + +void +gedit_text_region_destroy (GeditTextRegion *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 * +gedit_text_region_get_buffer (GeditTextRegion *region) +{ + g_return_val_if_fail (region != NULL, NULL); + + return region->buffer; +} + +static void +gedit_text_region_clear_zero_length_subregions (GeditTextRegion *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 +gedit_text_region_add (GeditTextRegion *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 (gedit_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 (gedit_text_region_debug_print (region)); +} + +void +gedit_text_region_subtract (GeditTextRegion *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 (gedit_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 (gedit_text_region_debug_print (region)); + + /* now get rid of empty subregions */ + gedit_text_region_clear_zero_length_subregions (region); + + DEBUG (gedit_text_region_debug_print (region)); +} + +gint +gedit_text_region_subregions (GeditTextRegion *region) +{ + g_return_val_if_fail (region != NULL, 0); + + return g_list_length (region->subregions); +} + +gboolean +gedit_text_region_nth_subregion (GeditTextRegion *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; +} + +GeditTextRegion * +gedit_text_region_intersect (GeditTextRegion *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; + GeditTextRegion *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 = gedit_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 (GeditTextRegionIteratorReal *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 +gedit_text_region_get_iterator (GeditTextRegion *region, + GeditTextRegionIterator *iter, + guint start) +{ + GeditTextRegionIteratorReal *real; + + g_return_if_fail (region != NULL); + g_return_if_fail (iter != NULL); + + real = (GeditTextRegionIteratorReal *)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 +gedit_text_region_iterator_is_end (GeditTextRegionIterator *iter) +{ + GeditTextRegionIteratorReal *real; + + g_return_val_if_fail (iter != NULL, FALSE); + + real = (GeditTextRegionIteratorReal *)iter; + g_return_val_if_fail (check_iterator (real), FALSE); + + return (real->subregions == NULL); +} + +gboolean +gedit_text_region_iterator_next (GeditTextRegionIterator *iter) +{ + GeditTextRegionIteratorReal *real; + + g_return_val_if_fail (iter != NULL, FALSE); + + real = (GeditTextRegionIteratorReal *)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 +gedit_text_region_iterator_get_subregion (GeditTextRegionIterator *iter, + GtkTextIter *start, + GtkTextIter *end) +{ + GeditTextRegionIteratorReal *real; + Subregion *sr; + + g_return_if_fail (iter != NULL); + + real = (GeditTextRegionIteratorReal *)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 +gedit_text_region_debug_print (GeditTextRegion *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/gedit/gedittextregion.h b/gedit/gedittextregion.h new file mode 100755 index 00000000..594a4950 --- /dev/null +++ b/gedit/gedittextregion.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * gedittextregion.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 __GEDIT_TEXT_REGION_H__ +#define __GEDIT_TEXT_REGION_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct _GeditTextRegion GeditTextRegion; +typedef struct _GeditTextRegionIterator GeditTextRegionIterator; + +struct _GeditTextRegionIterator { + /* GeditTextRegionIterator is an opaque datatype; ignore all these fields. + * Initialize the iter with gedit_text_region_get_iterator + * function + */ + /*< private >*/ + gpointer dummy1; + guint32 dummy2; + gpointer dummy3; +}; + +GeditTextRegion *gedit_text_region_new (GtkTextBuffer *buffer); +void gedit_text_region_destroy (GeditTextRegion *region, + gboolean delete_marks); + +GtkTextBuffer *gedit_text_region_get_buffer (GeditTextRegion *region); + +void gedit_text_region_add (GeditTextRegion *region, + const GtkTextIter *_start, + const GtkTextIter *_end); + +void gedit_text_region_subtract (GeditTextRegion *region, + const GtkTextIter *_start, + const GtkTextIter *_end); + +gint gedit_text_region_subregions (GeditTextRegion *region); + +gboolean gedit_text_region_nth_subregion (GeditTextRegion *region, + guint subregion, + GtkTextIter *start, + GtkTextIter *end); + +GeditTextRegion *gedit_text_region_intersect (GeditTextRegion *region, + const GtkTextIter *_start, + const GtkTextIter *_end); + +void gedit_text_region_get_iterator (GeditTextRegion *region, + GeditTextRegionIterator *iter, + guint start); + +gboolean gedit_text_region_iterator_is_end (GeditTextRegionIterator *iter); + +/* Returns FALSE if iterator is the end iterator */ +gboolean gedit_text_region_iterator_next (GeditTextRegionIterator *iter); + +void gedit_text_region_iterator_get_subregion (GeditTextRegionIterator *iter, + GtkTextIter *start, + GtkTextIter *end); + +void gedit_text_region_debug_print (GeditTextRegion *region); + +G_END_DECLS + +#endif /* __GEDIT_TEXT_REGION_H__ */ diff --git a/gedit/osx/Makefile.am b/gedit/osx/Makefile.am new file mode 100755 index 00000000..4d734169 --- /dev/null +++ b/gedit/osx/Makefile.am @@ -0,0 +1,23 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -I$(top_srcdir)/gedit \ + -I$(top_builddir)/gedit \ + $(GEDIT_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 = \ + gedit-osx.c \ + gedit-osx.h \ + gedit-osx-delegate.m \ + gedit-osx-delegate.h + +-include $(top_srcdir)/git.mk diff --git a/gedit/osx/gedit-osx-delegate.h b/gedit/osx/gedit-osx-delegate.h new file mode 100755 index 00000000..0b4411e8 --- /dev/null +++ b/gedit/osx/gedit-osx-delegate.h @@ -0,0 +1,16 @@ +#ifndef GEDIT_OSX_DELEGATE_H_ +#define GEDIT_OSX_DELEGATE_H_ + +#import <Foundation/NSAppleEventManager.h> + +@interface GeditOSXDelegate : NSObject +{ +} + +-(id) init; +-(void) openFiles:(NSAppleEventDescriptor*)event + withReply:(NSAppleEventDescriptor*)reply; + +@end + +#endif /* GEDIT_OSX_DELEGATE_H_ */ diff --git a/gedit/osx/gedit-osx-delegate.m b/gedit/osx/gedit-osx-delegate.m new file mode 100755 index 00000000..41b0b262 --- /dev/null +++ b/gedit/osx/gedit-osx-delegate.m @@ -0,0 +1,84 @@ +#import "gedit-osx-delegate.h" +#import <Foundation/NSAppleEventManager.h> +#import <Foundation/NSAppleEventDescriptor.h> +#import <Foundation/NSData.h> +#include <glib.h> +#include <gedit/gedit-app.h> +#include <gedit/gedit-commands.h> + +@implementation GeditOSXDelegate +-(id)init +{ + if ((self = [super init])) + { + NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager]; + + [em setEventHandler:self + andSelector:@selector(openFiles:withReply:) + forEventClass:kCoreEventClass + andEventID:kAEOpenDocuments]; + } + + return self; +} + +static GeditWindow * +get_window(NSAppleEventDescriptor *event) +{ + GeditApp *app = gedit_app_get_default (); + return gedit_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) + { + GeditWindow *window = get_window (event); + gedit_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/gedit/osx/gedit-osx.c b/gedit/osx/gedit-osx.c new file mode 100755 index 00000000..e7a18d42 --- /dev/null +++ b/gedit/osx/gedit-osx.c @@ -0,0 +1,94 @@ +#include "gedit-osx.h" +#include <gdk/gdkquartz.h> +#include <Carbon/Carbon.h> + +#import "gedit-osx-delegate.h" + +void +gedit_osx_set_window_title (GeditWindow *window, + gchar const *title, + GeditDocument *document) +{ + NSWindow *native; + + g_return_if_fail (GEDIT_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 (gedit_document_is_untitled (document)) + { + [native setRepresentedURL:nil]; + } + else + { + const gchar *uri = gedit_document_get_uri (document); + NSURL *nsurl = [NSURL URLWithString:[NSString stringWithUTF8String:uri]]; + + [native setRepresentedURL:nsurl]; + } + + ismodified = !gedit_document_is_untouched (document); + [native setDocumentEdited:ismodified]; + } + else + { + [native setRepresentedURL:nil]; + [native setDocumentEdited:false]; + } + + gtk_window_set_title (GTK_WINDOW (window), title); +} + +gboolean +gedit_osx_show_url (const gchar *url) +{ + return [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]]; +} + +gboolean +gedit_osx_show_help (const gchar *link_id) +{ + gchar *link; + gboolean ret; + + if (link_id) + { + link = g_strdup_printf ("http://library.mate.org/users/gedit/stable/%s", + link_id); + } + else + { + link = g_strdup ("http://library.mate.org/users/gedit/stable/"); + } + + ret = gedit_osx_show_url (link); + g_free (link); + + return ret; +} + +static void +destroy_delegate (GeditOSXDelegate *delegate) +{ + [delegate dealloc]; +} + +void +gedit_osx_init(GeditApp *app) +{ + GeditOSXDelegate *delegate = [[GeditOSXDelegate alloc] init]; + + g_object_set_data_full (G_OBJECT (app), + "GeditOSXDelegate", + delegate, + (GDestroyNotify)destroy_delegate); +}
\ No newline at end of file diff --git a/gedit/osx/gedit-osx.h b/gedit/osx/gedit-osx.h new file mode 100755 index 00000000..82f0120b --- /dev/null +++ b/gedit/osx/gedit-osx.h @@ -0,0 +1,17 @@ +#ifndef __GEDIT_OSX_H__ +#define __GEDIT_OSX_H__ + +#include <gtk/gtk.h> +#include <gedit/gedit-window.h> +#include <gedit/gedit-app.h> + +void gedit_osx_init (GeditApp *app); + +void gedit_osx_set_window_title (GeditWindow *window, + gchar const *title, + GeditDocument *document); + +gboolean gedit_osx_show_url (const gchar *url); +gboolean gedit_osx_show_help (const gchar *link_id); + +#endif /* __GEDIT_OSX_H__ */ diff --git a/gedit/smclient/Makefile.am b/gedit/smclient/Makefile.am new file mode 100755 index 00000000..8ec0ef71 --- /dev/null +++ b/gedit/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"\" \ + $(GEDIT_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/gedit/smclient/eggdesktopfile.c b/gedit/smclient/eggdesktopfile.c new file mode 100755 index 00000000..5ac79507 --- /dev/null +++ b/gedit/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/gedit/smclient/eggdesktopfile.h b/gedit/smclient/eggdesktopfile.h new file mode 100755 index 00000000..18fe4631 --- /dev/null +++ b/gedit/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/gedit/smclient/eggsmclient-osx.c b/gedit/smclient/eggsmclient-osx.c new file mode 100755 index 00000000..7d3ff4b6 --- /dev/null +++ b/gedit/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/gedit/smclient/eggsmclient-private.h b/gedit/smclient/eggsmclient-private.h new file mode 100755 index 00000000..ccb10bfc --- /dev/null +++ b/gedit/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/gedit/smclient/eggsmclient-win32.c b/gedit/smclient/eggsmclient-win32.c new file mode 100755 index 00000000..91a25715 --- /dev/null +++ b/gedit/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/gedit/smclient/eggsmclient-xsmp.c b/gedit/smclient/eggsmclient-xsmp.c new file mode 100755 index 00000000..a6d3f11f --- /dev/null +++ b/gedit/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/gedit/smclient/eggsmclient.c b/gedit/smclient/eggsmclient.c new file mode 100755 index 00000000..4b65f283 --- /dev/null +++ b/gedit/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/gedit/smclient/eggsmclient.h b/gedit/smclient/eggsmclient.h new file mode 100755 index 00000000..e620b754 --- /dev/null +++ b/gedit/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__ */ |