diff options
Diffstat (limited to 'plugins')
171 files changed, 45829 insertions, 0 deletions
diff --git a/plugins/Makefile.am b/plugins/Makefile.am new file mode 100755 index 00000000..ba80e090 --- /dev/null +++ b/plugins/Makefile.am @@ -0,0 +1,42 @@ +DIST_SUBDIRS = \ + changecase \ + checkupdate \ + docinfo \ + externaltools \ + filebrowser \ + modelines \ + pythonconsole \ + quickopen \ + snippets \ + sort \ + spell \ + taglist \ + time + +SUBDIRS = \ + changecase \ + docinfo \ + filebrowser \ + modelines \ + sort \ + taglist \ + time + +if ENABLE_PYTHON +SUBDIRS += pythonconsole snippets quickopen + +if !OS_WIN32 +SUBDIRS += externaltools +endif + +endif + +if ENABLE_ENCHANT +SUBDIRS += spell +endif + +if ENABLE_UPDATER +SUBDIRS += checkupdate +endif + +-include $(top_srcdir)/git.mk diff --git a/plugins/changecase/Makefile.am b/plugins/changecase/Makefile.am new file mode 100755 index 00000000..1f165f9e --- /dev/null +++ b/plugins/changecase/Makefile.am @@ -0,0 +1,34 @@ +# changecase plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libchangecase.la + +libchangecase_la_SOURCES = \ + gedit-changecase-plugin.h \ + gedit-changecase-plugin.c + +libchangecase_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libchangecase_la_LIBADD = $(GEDIT_LIBS) + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/changecase +ui_DATA = + +plugin_in_files = changecase.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(ui_DATA) $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/changecase/changecase.gedit-plugin.desktop.in b/plugins/changecase/changecase.gedit-plugin.desktop.in new file mode 100755 index 00000000..52a226f4 --- /dev/null +++ b/plugins/changecase/changecase.gedit-plugin.desktop.in @@ -0,0 +1,8 @@ +[Gedit Plugin] +Module=changecase +IAge=2 +_Name=Change Case +_Description=Changes the case of selected text. +Authors=Paolo Borelli <[email protected]> +Copyright=Copyright © 2004-2005 Paolo Borelli +Website=http://www.gedit.org diff --git a/plugins/changecase/gedit-changecase-plugin.c b/plugins/changecase/gedit-changecase-plugin.c new file mode 100755 index 00000000..8544aeb0 --- /dev/null +++ b/plugins/changecase/gedit-changecase-plugin.c @@ -0,0 +1,395 @@ +/* + * gedit-changecase-plugin.c + * + * Copyright (C) 2004-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, 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$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-changecase-plugin.h" + +#include <glib/gi18n-lib.h> +#include <gmodule.h> + +#include <gedit/gedit-debug.h> + +#define WINDOW_DATA_KEY "GeditChangecasePluginWindowData" + +GEDIT_PLUGIN_REGISTER_TYPE(GeditChangecasePlugin, gedit_changecase_plugin) + +typedef enum { + TO_UPPER_CASE, + TO_LOWER_CASE, + INVERT_CASE, + TO_TITLE_CASE, +} ChangeCaseChoice; + +static void +do_upper_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + nc = g_unichar_toupper (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +do_lower_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + nc = g_unichar_tolower (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +do_invert_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + if (g_unichar_islower (c)) + nc = g_unichar_toupper (c); + else + nc = g_unichar_tolower (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +do_title_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + if (gtk_text_iter_starts_word (start)) + nc = g_unichar_totitle (c); + else + nc = g_unichar_tolower (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +change_case (GeditWindow *window, + ChangeCaseChoice choice) +{ + GeditDocument *doc; + GtkTextIter start, end; + + gedit_debug (DEBUG_PLUGINS); + + doc = gedit_window_get_active_document (window); + g_return_if_fail (doc != NULL); + + if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), + &start, &end)) + { + return; + } + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (doc)); + + switch (choice) + { + case TO_UPPER_CASE: + do_upper_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + case TO_LOWER_CASE: + do_lower_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + case INVERT_CASE: + do_invert_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + case TO_TITLE_CASE: + do_title_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + default: + g_return_if_reached (); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (doc)); +} + +static void +upper_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, TO_UPPER_CASE); +} + +static void +lower_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, TO_LOWER_CASE); +} + +static void +invert_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, INVERT_CASE); +} + +static void +title_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, TO_TITLE_CASE); +} + +static const GtkActionEntry action_entries[] = +{ + { "ChangeCase", NULL, N_("C_hange Case") }, + { "UpperCase", NULL, N_("All _Upper Case"), NULL, + N_("Change selected text to upper case"), + G_CALLBACK (upper_case_cb) }, + { "LowerCase", NULL, N_("All _Lower Case"), NULL, + N_("Change selected text to lower case"), + G_CALLBACK (lower_case_cb) }, + { "InvertCase", NULL, N_("_Invert Case"), NULL, + N_("Invert the case of selected text"), + G_CALLBACK (invert_case_cb) }, + { "TitleCase", NULL, N_("_Title Case"), NULL, + N_("Capitalize the first letter of each selected word"), + G_CALLBACK (title_case_cb) } +}; + +const gchar submenu[] = +"<ui>" +" <menubar name='MenuBar'>" +" <menu name='EditMenu' action='Edit'>" +" <placeholder name='EditOps_6'>" +" <menu action='ChangeCase'>" +" <menuitem action='UpperCase'/>" +" <menuitem action='LowerCase'/>" +" <menuitem action='InvertCase'/>" +" <menuitem action='TitleCase'/>" +" </menu>" +" </placeholder>" +" </menu>" +" </menubar>" +"</ui>"; + +static void +gedit_changecase_plugin_init (GeditChangecasePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditChangecasePlugin initializing"); +} + +static void +gedit_changecase_plugin_finalize (GObject *object) +{ + G_OBJECT_CLASS (gedit_changecase_plugin_parent_class)->finalize (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditChangecasePlugin finalizing"); +} + +typedef struct +{ + GtkActionGroup *action_group; + guint ui_id; +} WindowData; + +static void +free_window_data (WindowData *data) +{ + g_return_if_fail (data != NULL); + + g_slice_free (WindowData, data); +} + +static void +update_ui_real (GeditWindow *window, + WindowData *data) +{ + GtkTextView *view; + GtkAction *action; + gboolean sensitive = FALSE; + + gedit_debug (DEBUG_PLUGINS); + + view = GTK_TEXT_VIEW (gedit_window_get_active_view (window)); + + if (view != NULL) + { + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer (view); + sensitive = (gtk_text_view_get_editable (view) && + gtk_text_buffer_get_has_selection (buffer)); + } + + action = gtk_action_group_get_action (data->action_group, + "ChangeCase"); + gtk_action_set_sensitive (action, sensitive); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + GError *error = NULL; + + gedit_debug (DEBUG_PLUGINS); + + data = g_slice_new (WindowData); + + manager = gedit_window_get_ui_manager (window); + + data->action_group = gtk_action_group_new ("GeditChangecasePluginActions"); + gtk_action_group_set_translation_domain (data->action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions (data->action_group, + action_entries, + G_N_ELEMENTS (action_entries), + window); + + gtk_ui_manager_insert_action_group (manager, data->action_group, -1); + + data->ui_id = gtk_ui_manager_add_ui_from_string (manager, + submenu, + -1, + &error); + if (data->ui_id == 0) + { + g_warning ("%s", error->message); + free_window_data (data); + return; + } + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + (GDestroyNotify) free_window_data); + + update_ui_real (window, data); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + manager = gedit_window_get_ui_manager (window); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + gtk_ui_manager_remove_ui (manager, data->ui_id); + gtk_ui_manager_remove_action_group (manager, data->action_group); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + update_ui_real (window, data); +} + +static void +gedit_changecase_plugin_class_init (GeditChangecasePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_changecase_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; +} diff --git a/plugins/changecase/gedit-changecase-plugin.h b/plugins/changecase/gedit-changecase-plugin.h new file mode 100755 index 00000000..9587928c --- /dev/null +++ b/plugins/changecase/gedit-changecase-plugin.h @@ -0,0 +1,72 @@ +/* + * gedit-changecase-plugin.h + * + * Copyright (C) 2004-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, 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$ + */ + +#ifndef __GEDIT_CHANGECASE_PLUGIN_H__ +#define __GEDIT_CHANGECASE_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_CHANGECASE_PLUGIN (gedit_changecase_plugin_get_type ()) +#define GEDIT_CHANGECASE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePlugin)) +#define GEDIT_CHANGECASE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePluginClass)) +#define GEDIT_IS_CHANGECASE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_CHANGECASE_PLUGIN)) +#define GEDIT_IS_CHANGECASE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_CHANGECASE_PLUGIN)) +#define GEDIT_CHANGECASE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePluginClass)) + +/* + * Main object structure + */ +typedef struct _GeditChangecasePlugin GeditChangecasePlugin; + +struct _GeditChangecasePlugin +{ + GeditPlugin parent_instance; +}; + +/* + * Class definition + */ +typedef struct _GeditChangecasePluginClass GeditChangecasePluginClass; + +struct _GeditChangecasePluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_changecase_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_CHANGECASE_PLUGIN_H__ */ diff --git a/plugins/checkupdate/Makefile.am b/plugins/checkupdate/Makefile.am new file mode 100755 index 00000000..551b7eee --- /dev/null +++ b/plugins/checkupdate/Makefile.am @@ -0,0 +1,49 @@ +# gedit win32 updater + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(LIBSOUP_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libcheckupdate.la + +libcheckupdate_la_SOURCES = \ + gedit-check-update-plugin.h \ + gedit-check-update-plugin.c + +libcheckupdate_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libcheckupdate_la_LIBADD = $(GEDIT_LIBS) $(LIBSOUP_LIBS) + +plugin_in_files = checkupdate.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +schemasdir = $(MATECONF_SCHEMA_FILE_DIR) +schemas_in_files = gedit-check-update.schemas.in +schemas_DATA = $(schemas_in_files:.schemas.in=.schemas) +@INTLTOOL_SCHEMAS_RULE@ + +if MATECONF_SCHEMAS_INSTALL +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schemas_DATA) ; do \ + MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $(top_builddir)/plugins/checkupdate/$$p ; \ + done \ + fi +else +install-data-local: +endif + +EXTRA_DIST = $(plugin_in_files) $(schemas_in_files) + +CLEANFILES = $(plugin_DATA) $(schemas_DATA) + +DISTCLEANFILES = $(plugin_DATA) $(schemas_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in b/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in new file mode 100755 index 00000000..4699b6a2 --- /dev/null +++ b/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in @@ -0,0 +1,9 @@ +[Gedit Plugin] +Module=checkupdate +IAge=2 +_Name=Check update +_Description=Check for latest version of gedit +Icon=gedit-plugin +Authors=Ignacio Casal Quinteiro <[email protected]> +Copyright=Copyright © 2009 Ignacio Casal Quinteiro +Website=http://www.gedit.org diff --git a/plugins/checkupdate/gedit-check-update-plugin.c b/plugins/checkupdate/gedit-check-update-plugin.c new file mode 100755 index 00000000..aa9f7a5e --- /dev/null +++ b/plugins/checkupdate/gedit-check-update-plugin.c @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2009 - Ignacio Casal Quinteiro <[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, 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-check-update-plugin.h" + +#include <glib/gi18n-lib.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> +#include <libsoup/soup.h> +#include <gtk/gtk.h> +#include <stdlib.h> + +#include <mateconf/mateconf-client.h> + +#if !GTK_CHECK_VERSION(2, 17, 1) +#include <gedit/gedit-message-area.h> +#endif + +#define MATECONF_KEY_BASE "/apps/gedit-2/plugins/checkupdate" +#define MATECONF_KEY_IGNORE_VERSION MATECONF_KEY_BASE "/ignore_version" + +#define WINDOW_DATA_KEY "GeditCheckUpdatePluginWindowData" + +#define VERSION_PLACE "<a href=\"[0-9]\\.[0-9]+/\">" + +#ifdef G_OS_WIN32 +#define GEDIT_URL "http://ftp.acc.umu.se/pub/mate/binaries/win32/gedit/" +#define FILE_REGEX "gedit\\-setup\\-[0-9]+\\.[0-9]+\\.[0-9]+(\\-[0-9]+)?\\.exe" +#else +#define GEDIT_URL "http://ftp.acc.umu.se/pub/mate/binaries/mac/gedit/" +#define FILE_REGEX "gedit\\-[0-9]+\\.[0-9]+\\.[0-9]+(\\-[0-9]+)?\\.dmg" +#endif + +#ifdef OS_OSX +#include "gedit/osx/gedit-osx.h" +#endif + +#define GEDIT_CHECK_UPDATE_PLUGIN_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_CHECK_UPDATE_PLUGIN, \ + GeditCheckUpdatePluginPrivate)) + +GEDIT_PLUGIN_REGISTER_TYPE (GeditCheckUpdatePlugin, gedit_check_update_plugin) + +struct _GeditCheckUpdatePluginPrivate +{ + SoupSession *session; + + MateConfClient *mateconf_client; +}; + +typedef struct +{ + GeditCheckUpdatePlugin *plugin; + + gchar *url; + gchar *version; +} WindowData; + +static void +free_window_data (gpointer data) +{ + WindowData *window_data; + + if (data == NULL) + return; + + window_data = (WindowData *)data; + + g_free (window_data->url); + g_free (window_data->version); + g_slice_free (WindowData, data); +} + +static void +gedit_check_update_plugin_init (GeditCheckUpdatePlugin *plugin) +{ + plugin->priv = GEDIT_CHECK_UPDATE_PLUGIN_GET_PRIVATE (plugin); + + gedit_debug_message (DEBUG_PLUGINS, + "GeditCheckUpdatePlugin initializing"); + + plugin->priv->session = soup_session_async_new (); + + plugin->priv->mateconf_client = mateconf_client_get_default (); + + mateconf_client_add_dir (plugin->priv->mateconf_client, + MATECONF_KEY_BASE, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); +} + +static void +gedit_check_update_plugin_dispose (GObject *object) +{ + GeditCheckUpdatePlugin *plugin = GEDIT_CHECK_UPDATE_PLUGIN (object); + + if (plugin->priv->session != NULL) + { + g_object_unref (plugin->priv->session); + plugin->priv->session = NULL; + } + + if (plugin->priv->mateconf_client != NULL) + { + mateconf_client_suggest_sync (plugin->priv->mateconf_client, NULL); + + g_object_unref (G_OBJECT (plugin->priv->mateconf_client)); + + plugin->priv->mateconf_client = NULL; + } + + gedit_debug_message (DEBUG_PLUGINS, + "GeditCheckUpdatePlugin disposing"); + + G_OBJECT_CLASS (gedit_check_update_plugin_parent_class)->dispose (object); +} + +static void +gedit_check_update_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, + "GeditCheckUpdatePlugin finalizing"); + + G_OBJECT_CLASS (gedit_check_update_plugin_parent_class)->finalize (object); +} + +static void +set_contents (GtkWidget *infobar, + GtkWidget *contents) +{ +#if !GTK_CHECK_VERSION (2, 17, 1) + gedit_message_area_set_contents (GEDIT_MESSAGE_AREA (infobar), + contents); +#else + GtkWidget *content_area; + + content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar)); + gtk_container_add (GTK_CONTAINER (content_area), contents); +#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); + gtk_widget_show (hbox_content); + + image = gtk_image_new_from_stock (icon_stock_id, GTK_ICON_SIZE_DIALOG); + gtk_widget_show (image); + 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_widget_show (vbox); + 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_widget_show (primary_label); + 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_widget_show (secondary_label); + 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); + } + + set_contents (message_area, hbox_content); +} + +static void +on_response_cb (GtkWidget *infobar, + gint response_id, + GeditWindow *window) +{ + if (response_id == GTK_RESPONSE_YES) + { + GError *error = NULL; + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + +#ifdef OS_OSX + gedit_osx_show_url (data->url); +#else + gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (window)), + data->url, + GDK_CURRENT_TIME, + &error); +#endif + if (error != NULL) + { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("There was an error displaying the URI.")); + + 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); + } + } + else if (response_id == GTK_RESPONSE_NO) + { + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + + mateconf_client_set_string (data->plugin->priv->mateconf_client, + MATECONF_KEY_IGNORE_VERSION, + data->version, + NULL); + } + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); + + gtk_widget_destroy (infobar); +} + +static GtkWidget * +create_infobar (GeditWindow *window, + const gchar *version) +{ + GtkWidget *infobar; + gchar *message; + +#if !GTK_CHECK_VERSION (2, 17, 1) + infobar = gedit_message_area_new (); + + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (infobar), + _("_Download"), + GTK_STOCK_SAVE, + GTK_RESPONSE_YES); + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (infobar), + _("_Ignore Version"), + GTK_STOCK_DISCARD, + GTK_RESPONSE_NO); + gedit_message_area_add_button (GEDIT_MESSAGE_AREA (infobar), + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); +#else + GtkWidget *button; + + infobar = gtk_info_bar_new (); + + button = gedit_gtk_button_new_with_stock_icon (_("_Download"), + GTK_STOCK_SAVE); + gtk_widget_show (button); + + gtk_info_bar_add_action_widget (GTK_INFO_BAR (infobar), + button, + GTK_RESPONSE_YES); + + button = gedit_gtk_button_new_with_stock_icon (_("_Ignore Version"), + GTK_STOCK_DISCARD); + gtk_widget_show (button); + + gtk_info_bar_add_action_widget (GTK_INFO_BAR (infobar), + button, + GTK_RESPONSE_NO); + + gtk_info_bar_add_button (GTK_INFO_BAR (infobar), + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); + + gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), + GTK_MESSAGE_INFO); +#endif + + message = g_strdup_printf ("%s (%s)", _("There is a new version of gedit"), version); + set_message_area_text_and_icon (infobar, + "gtk-dialog-info", + message, + _("You can download the new version of gedit" + " by clicking on the download button or" + " ignore that version and wait for a new one")); + + g_free (message); + + g_signal_connect (infobar, "response", + G_CALLBACK (on_response_cb), + window); + + return infobar; +} + +static void +pack_infobar (GtkWidget *window, + GtkWidget *infobar) +{ + GtkWidget *vbox; + + vbox = gtk_bin_get_child (GTK_BIN (window)); + + gtk_box_pack_start (GTK_BOX (vbox), infobar, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (vbox), infobar, 2); +} + +static gchar * +get_file (const gchar *text, + const gchar *regex_place) +{ + GRegex *regex; + GMatchInfo *match_info; + gchar *word = NULL; + + regex = g_regex_new (regex_place, 0, 0, NULL); + g_regex_match (regex, text, 0, &match_info); + while (g_match_info_matches (match_info)) + { + g_free (word); + + word = g_match_info_fetch (match_info, 0); + + g_match_info_next (match_info, NULL); + } + g_match_info_free (match_info); + g_regex_unref (regex); + + return word; +} + +static void +get_numbers (const gchar *version, + gint *major, + gint *minor, + gint *micro) +{ + gchar **split; + gint num = 2; + + if (micro != NULL) + num = 3; + + split = g_strsplit (version, ".", num); + *major = atoi (split[0]); + *minor = atoi (split[1]); + if (micro != NULL) + *micro = atoi (split[2]); + + g_strfreev (split); +} + +static gboolean +newer_version (const gchar *v1, + const gchar *v2, + gboolean with_micro) +{ + gboolean newer = FALSE; + gint major1, minor1, micro1; + gint major2, minor2, micro2; + + if (v1 == NULL || v2 == NULL) + return FALSE; + + if (with_micro) + { + get_numbers (v1, &major1, &minor1, µ1); + get_numbers (v2, &major2, &minor2, µ2); + } + else + { + get_numbers (v1, &major1, &minor1, NULL); + get_numbers (v2, &major2, &minor2, NULL); + } + + if (major1 > major2) + { + newer = TRUE; + } + else if (minor1 > minor2 && major1 == major2) + { + newer = TRUE; + } + else if (with_micro && micro1 > micro2 && minor1 == minor2) + { + newer = TRUE; + } + + return newer; +} + +static gchar * +parse_file_version (const gchar *file) +{ + gchar *p, *aux; + + p = (gchar *)file; + + while (*p != '\0' && !g_ascii_isdigit (*p)) + { + p++; + } + + if (*p == '\0') + return NULL; + + aux = g_strrstr (p, "-"); + if (aux == NULL) + aux = g_strrstr (p, "."); + + return g_strndup (p, aux - p); +} + +static gchar * +get_ignore_version (GeditCheckUpdatePlugin *plugin) +{ + return mateconf_client_get_string (plugin->priv->mateconf_client, + MATECONF_KEY_IGNORE_VERSION, + NULL); +} + +static void +parse_page_file (SoupSession *session, + SoupMessage *msg, + GeditWindow *window) +{ + if (msg->status_code == SOUP_STATUS_OK) + { + gchar *file; + gchar *file_version; + gchar *ignore_version; + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + + file = get_file (msg->response_body->data, FILE_REGEX); + file_version = parse_file_version (file); + ignore_version = get_ignore_version (data->plugin); + + if (newer_version (file_version, VERSION, TRUE) && + (ignore_version == NULL || *ignore_version == '\0' || + newer_version (file_version, ignore_version, TRUE))) + { + GtkWidget *infobar; + WindowData *data; + gchar *file_url; + + data = g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + + file_url = g_strconcat (data->url, file, NULL); + + g_free (data->url); + data->url = file_url; + data->version = g_strdup (file_version); + + infobar = create_infobar (window, file_version); + pack_infobar (GTK_WIDGET (window), infobar); + gtk_widget_show (infobar); + } + + g_free (ignore_version); + g_free (file_version); + g_free (file); + } + else + { + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); + } +} + +static gboolean +is_unstable (const gchar *version) +{ + gchar **split; + gint minor; + gboolean unstable = TRUE;; + + split = g_strsplit (version, ".", 2); + minor = atoi (split[1]); + g_strfreev (split); + + if ((minor % 2) == 0) + unstable = FALSE; + + return unstable; +} + +static gchar * +get_file_page_version (const gchar *text, + const gchar *regex_place) +{ + GRegex *regex; + GMatchInfo *match_info; + GString *string = NULL; + gchar *unstable = NULL; + gchar *stable = NULL; + + regex = g_regex_new (regex_place, 0, 0, NULL); + g_regex_match (regex, text, 0, &match_info); + while (g_match_info_matches (match_info)) + { + gint end; + gint i; + + g_match_info_fetch_pos (match_info, 0, NULL, &end); + + string = g_string_new (""); + + i = end; + while (text[i] != '/') + { + string = g_string_append_c (string, text[i]); + i++; + } + + if (is_unstable (string->str)) + { + g_free (unstable); + unstable = g_string_free (string, FALSE); + } + else + { + g_free (stable); + stable = g_string_free (string, FALSE); + } + + g_match_info_next (match_info, NULL); + } + g_match_info_free (match_info); + g_regex_unref (regex); + + if ((GEDIT_MINOR_VERSION % 2) == 0) + { + g_free (unstable); + + return stable; + } + else + { + /* We need to check that stable isn't newer than unstable */ + if (newer_version (stable, unstable, FALSE)) + { + g_free (unstable); + + return stable; + } + else + { + g_free (stable); + + return unstable; + } + } +} + +static void +parse_page_version (SoupSession *session, + SoupMessage *msg, + GeditWindow *window) +{ + if (msg->status_code == SOUP_STATUS_OK) + { + gchar *version; + SoupMessage *msg2; + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + + version = get_file_page_version (msg->response_body->data, + VERSION_PLACE); + + data->url = g_strconcat (GEDIT_URL, version, "/", NULL); + g_free (version); + msg2 = soup_message_new ("GET", data->url); + + soup_session_queue_message (session, msg2, + (SoupSessionCallback)parse_page_file, + window); + } + else + { + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); + } +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + SoupMessage *msg; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = g_slice_new (WindowData); + data->plugin = GEDIT_CHECK_UPDATE_PLUGIN (plugin); + data->url = NULL; + data->version = NULL; + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + free_window_data); + + msg = soup_message_new ("GET", GEDIT_URL); + + soup_session_queue_message (GEDIT_CHECK_UPDATE_PLUGIN (plugin)->priv->session, msg, + (SoupSessionCallback)parse_page_version, + window); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + + gedit_debug (DEBUG_PLUGINS); + + soup_session_abort (GEDIT_CHECK_UPDATE_PLUGIN (plugin)->priv->session); + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); +} + +static void +gedit_check_update_plugin_class_init (GeditCheckUpdatePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (GeditCheckUpdatePluginPrivate)); + + object_class->finalize = gedit_check_update_plugin_finalize; + object_class->dispose = gedit_check_update_plugin_dispose; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; +} diff --git a/plugins/checkupdate/gedit-check-update-plugin.h b/plugins/checkupdate/gedit-check-update-plugin.h new file mode 100755 index 00000000..68dc7f97 --- /dev/null +++ b/plugins/checkupdate/gedit-check-update-plugin.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 - Ignacio Casal Quinteiro <[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, 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_CHECK_UPDATE_PLUGIN_H__ +#define __GEDIT_CHECK_UPDATE_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_CHECK_UPDATE_PLUGIN (gedit_check_update_plugin_get_type ()) +#define GEDIT_CHECK_UPDATE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePlugin)) +#define GEDIT_CHECK_UPDATE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePluginClass)) +#define IS_GEDIT_CHECK_UPDATE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN)) +#define IS_GEDIT_CHECK_UPDATE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_CHECK_UPDATE_PLUGIN)) +#define GEDIT_CHECK_UPDATE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePluginClass)) + +/* Private structure type */ +typedef struct _GeditCheckUpdatePluginPrivate GeditCheckUpdatePluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditCheckUpdatePlugin GeditCheckUpdatePlugin; + +struct _GeditCheckUpdatePlugin +{ + GeditPlugin parent_instance; + + /*< private >*/ + GeditCheckUpdatePluginPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditCheckUpdatePluginClass GeditCheckUpdatePluginClass; + +struct _GeditCheckUpdatePluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_check_update_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_CHECK_UPDATE_PLUGIN_H__ */ diff --git a/plugins/checkupdate/gedit-check-update.schemas.in b/plugins/checkupdate/gedit-check-update.schemas.in new file mode 100755 index 00000000..67bc892b --- /dev/null +++ b/plugins/checkupdate/gedit-check-update.schemas.in @@ -0,0 +1,13 @@ +<mateconfschemafile> + <schemalist> + <schema> + <key>/schemas/apps/gedit-2/plugins/checkupdate/ignore_version</key> + <applyto>/apps/gedit-2/plugins/checkupdate/ignore_version</applyto> + <owner>gedit</owner> + <type>string</type> + <locale name="C"> + <short>Version to ignore until the next version is released</short> + </locale> + </schema> + </schemalist> +</mateconfschemafile> diff --git a/plugins/docinfo/Makefile.am b/plugins/docinfo/Makefile.am new file mode 100755 index 00000000..edf2909c --- /dev/null +++ b/plugins/docinfo/Makefile.am @@ -0,0 +1,34 @@ +# docinfo plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libdocinfo.la + +libdocinfo_la_SOURCES = \ + gedit-docinfo-plugin.h \ + gedit-docinfo-plugin.c + +libdocinfo_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libdocinfo_la_LIBADD = $(GEDIT_LIBS) + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/docinfo +ui_DATA = docinfo.ui + +plugin_in_files = docinfo.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(ui_DATA) $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/docinfo/docinfo.gedit-plugin.desktop.in b/plugins/docinfo/docinfo.gedit-plugin.desktop.in new file mode 100755 index 00000000..77f2793d --- /dev/null +++ b/plugins/docinfo/docinfo.gedit-plugin.desktop.in @@ -0,0 +1,7 @@ +[Gedit Plugin] +Module=docinfo +IAge=2 +_Name=Document Statistics +_Description=Analyzes the current document and reports the number of words, lines, characters and non-space characters in it. +Authors=Paolo Maggi <[email protected]>;Jorge Alberto Torres <[email protected]> +Copyright=Copyright © 2002-2005 Paolo Maggi diff --git a/plugins/docinfo/docinfo.ui b/plugins/docinfo/docinfo.ui new file mode 100755 index 00000000..bb73d40f --- /dev/null +++ b/plugins/docinfo/docinfo.ui @@ -0,0 +1,621 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <object class="GtkDialog" id="dialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Document Statistics</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">True</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_END</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="update_button"> + <property name="label" translatable="yes">_Update</property> + <property name="use_underline">True</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="image">update_image</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </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="GtkVBox" id="docinfo_dialog_content"> + <property name="border_width">5</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="file_name_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">File Name</property> + <property name="use_underline">False</property> + <property name="use_markup">True</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> + <attributes> + <attribute name="weight" value="PANGO_WEIGHT_BOLD"/> + </attributes> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <child> + <object class="GtkLabel" id="label28"> + <property name="visible">True</property> + <property name="label" translatable="yes"> </property> + <property name="use_underline">False</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.5</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table1"> + <property name="border_width">6</property> + <property name="visible">True</property> + <property name="n_rows">6</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">18</property> + <child> + <object class="GtkLabel" id="label26"> + <property name="visible">True</property> + <property name="label" translatable="yes">Bytes</property> + <property name="use_underline">False</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">5</property> + <property name="bottom_attach">6</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="bytes_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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">1</property> + <property name="right_attach">2</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="label" translatable="yes">Characters (no spaces)</property> + <property name="use_underline">False</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">4</property> + <property name="bottom_attach">5</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="chars_ns_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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">1</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes">Characters (with spaces)</property> + <property name="use_underline">False</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">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="chars_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="words_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes">Words</property> + <property name="use_underline">False</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">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="label" translatable="yes">Lines</property> + <property name="use_underline">False</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> + <child> + <object class="GtkLabel" id="lines_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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">1</property> + <property name="right_attach">2</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> + <child> + <object class="GtkLabel" id="label30"> + <property name="visible">True</property> + <property name="label" translatable="yes">Document</property> + <property name="use_underline">True</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_CENTER</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">1</property> + <property name="right_attach">2</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> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="selection_vbox"> + <property name="border_width">6</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="selection_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Selection</property> + <property name="use_underline">False</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_lines_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_words_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_chars_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_chars_ns_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_bytes_label"> + <property name="visible">True</property> + <property name="label">0</property> + <property name="use_underline">False</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">1</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="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> + <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="-7">close_button</action-widget> + <action-widget response="-5">update_button</action-widget> + </action-widgets> + </object> + <object class="GtkImage" id="update_image"> + <property name="visible">True</property> + <property name="stock">gtk-refresh</property> + </object> +</interface> diff --git a/plugins/docinfo/gedit-docinfo-plugin.c b/plugins/docinfo/gedit-docinfo-plugin.c new file mode 100755 index 00000000..a143a5a6 --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.c @@ -0,0 +1,580 @@ +/* + * gedit-docinfo-plugin.c + * + * 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, 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$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-docinfo-plugin.h" + +#include <string.h> /* For strlen (...) */ + +#include <glib/gi18n-lib.h> +#include <pango/pango-break.h> +#include <gmodule.h> + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> + +#define WINDOW_DATA_KEY "GeditDocInfoWindowData" +#define MENU_PATH "/MenuBar/ToolsMenu/ToolsOps_2" + +GEDIT_PLUGIN_REGISTER_TYPE(GeditDocInfoPlugin, gedit_docinfo_plugin) + +typedef struct +{ + GtkWidget *dialog; + GtkWidget *file_name_label; + GtkWidget *lines_label; + GtkWidget *words_label; + GtkWidget *chars_label; + GtkWidget *chars_ns_label; + GtkWidget *bytes_label; + GtkWidget *selection_vbox; + GtkWidget *selected_lines_label; + GtkWidget *selected_words_label; + GtkWidget *selected_chars_label; + GtkWidget *selected_chars_ns_label; + GtkWidget *selected_bytes_label; +} DocInfoDialog; + +typedef struct +{ + GeditPlugin *plugin; + + GtkActionGroup *ui_action_group; + guint ui_id; + + DocInfoDialog *dialog; +} WindowData; + +static void docinfo_dialog_response_cb (GtkDialog *widget, + gint res_id, + GeditWindow *window); + +static void +docinfo_dialog_destroy_cb (GtkObject *obj, + WindowData *data) +{ + gedit_debug (DEBUG_PLUGINS); + + if (data != NULL) + { + g_free (data->dialog); + data->dialog = NULL; + } +} + +static DocInfoDialog * +get_docinfo_dialog (GeditWindow *window, + WindowData *data) +{ + DocInfoDialog *dialog; + gchar *data_dir; + gchar *ui_file; + GtkWidget *content; + GtkWidget *error_widget; + gboolean ret; + + gedit_debug (DEBUG_PLUGINS); + + dialog = g_new (DocInfoDialog, 1); + + data_dir = gedit_plugin_get_data_dir (data->plugin); + ui_file = g_build_filename (data_dir, "docinfo.ui", NULL); + ret = gedit_utils_get_ui_objects (ui_file, + NULL, + &error_widget, + "dialog", &dialog->dialog, + "docinfo_dialog_content", &content, + "file_name_label", &dialog->file_name_label, + "words_label", &dialog->words_label, + "bytes_label", &dialog->bytes_label, + "lines_label", &dialog->lines_label, + "chars_label", &dialog->chars_label, + "chars_ns_label", &dialog->chars_ns_label, + "selection_vbox", &dialog->selection_vbox, + "selected_words_label", &dialog->selected_words_label, + "selected_bytes_label", &dialog->selected_bytes_label, + "selected_lines_label", &dialog->selected_lines_label, + "selected_chars_label", &dialog->selected_chars_label, + "selected_chars_ns_label", &dialog->selected_chars_ns_label, + NULL); + + g_free (data_dir); + g_free (ui_file); + + if (!ret) + { + const gchar *err_message; + + err_message = gtk_label_get_label (GTK_LABEL (error_widget)); + gedit_warning (GTK_WINDOW (window), "%s", err_message); + + g_free (dialog); + gtk_widget_destroy (error_widget); + + return NULL; + } + + gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog), + GTK_RESPONSE_OK); + gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), + GTK_WINDOW (window)); + + g_signal_connect (dialog->dialog, + "destroy", + G_CALLBACK (docinfo_dialog_destroy_cb), + data); + g_signal_connect (dialog->dialog, + "response", + G_CALLBACK (docinfo_dialog_response_cb), + window); + + return dialog; +} + +static void +calculate_info (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end, + gint *chars, + gint *words, + gint *white_chars, + gint *bytes) +{ + gchar *text; + + gedit_debug (DEBUG_PLUGINS); + + text = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), + start, + end, + TRUE); + + *chars = g_utf8_strlen (text, -1); + *bytes = strlen (text); + + if (*chars > 0) + { + PangoLogAttr *attrs; + gint i; + + attrs = g_new0 (PangoLogAttr, *chars + 1); + + pango_get_log_attrs (text, + -1, + 0, + pango_language_from_string ("C"), + attrs, + *chars + 1); + + for (i = 0; i < (*chars); i++) + { + if (attrs[i].is_white) + ++(*white_chars); + + if (attrs[i].is_word_start) + ++(*words); + } + + g_free (attrs); + } + else + { + *white_chars = 0; + *words = 0; + } + + g_free (text); +} + +static void +docinfo_real (GeditDocument *doc, + DocInfoDialog *dialog) +{ + GtkTextIter start, end; + gint words = 0; + gint chars = 0; + gint white_chars = 0; + gint lines = 0; + gint bytes = 0; + gchar *tmp_str; + gchar *doc_name; + + gedit_debug (DEBUG_PLUGINS); + + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), + &start, + &end); + + lines = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (doc)); + + calculate_info (doc, + &start, &end, + &chars, &words, &white_chars, &bytes); + + if (chars == 0) + lines = 0; + + gedit_debug_message (DEBUG_PLUGINS, "Chars: %d", chars); + gedit_debug_message (DEBUG_PLUGINS, "Lines: %d", lines); + gedit_debug_message (DEBUG_PLUGINS, "Words: %d", words); + gedit_debug_message (DEBUG_PLUGINS, "Chars non-space: %d", chars - white_chars); + gedit_debug_message (DEBUG_PLUGINS, "Bytes: %d", bytes); + + doc_name = gedit_document_get_short_name_for_display (doc); + tmp_str = g_strdup_printf ("<span weight=\"bold\">%s</span>", doc_name); + gtk_label_set_markup (GTK_LABEL (dialog->file_name_label), tmp_str); + g_free (doc_name); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (dialog->lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (dialog->words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (dialog->chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (dialog->chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (dialog->bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +selectioninfo_real (GeditDocument *doc, + DocInfoDialog *dialog) +{ + gboolean sel; + GtkTextIter start, end; + gint words = 0; + gint chars = 0; + gint white_chars = 0; + gint lines = 0; + gint bytes = 0; + gchar *tmp_str; + + gedit_debug (DEBUG_PLUGINS); + + sel = gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), + &start, + &end); + + if (sel) + { + lines = gtk_text_iter_get_line (&end) - gtk_text_iter_get_line (&start) + 1; + + calculate_info (doc, + &start, &end, + &chars, &words, &white_chars, &bytes); + + gedit_debug_message (DEBUG_PLUGINS, "Selected chars: %d", chars); + gedit_debug_message (DEBUG_PLUGINS, "Selected lines: %d", lines); + gedit_debug_message (DEBUG_PLUGINS, "Selected words: %d", words); + gedit_debug_message (DEBUG_PLUGINS, "Selected chars non-space: %d", chars - white_chars); + gedit_debug_message (DEBUG_PLUGINS, "Selected bytes: %d", bytes); + + gtk_widget_set_sensitive (dialog->selection_vbox, TRUE); + } + else + { + gtk_widget_set_sensitive (dialog->selection_vbox, FALSE); + + gedit_debug_message (DEBUG_PLUGINS, "Selection empty"); + } + + if (chars == 0) + lines = 0; + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (dialog->selected_lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (dialog->selected_words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (dialog->selected_chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (dialog->selected_chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (dialog->selected_bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +docinfo_cb (GtkAction *action, + GeditWindow *window) +{ + GeditDocument *doc; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + + doc = gedit_window_get_active_document (window); + g_return_if_fail (doc != NULL); + + if (data->dialog != NULL) + { + gtk_window_present (GTK_WINDOW (data->dialog->dialog)); + gtk_widget_grab_focus (GTK_WIDGET (data->dialog->dialog)); + } + else + { + DocInfoDialog *dialog; + + dialog = get_docinfo_dialog (window, data); + g_return_if_fail (dialog != NULL); + + data->dialog = dialog; + + gtk_widget_show (GTK_WIDGET (dialog->dialog)); + } + + docinfo_real (doc, + data->dialog); + selectioninfo_real (doc, + data->dialog); +} + +static void +docinfo_dialog_response_cb (GtkDialog *widget, + gint res_id, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + + switch (res_id) + { + case GTK_RESPONSE_CLOSE: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CLOSE"); + gtk_widget_destroy (data->dialog->dialog); + + break; + } + + case GTK_RESPONSE_OK: + { + GeditDocument *doc; + + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK"); + + doc = gedit_window_get_active_document (window); + g_return_if_fail (doc != NULL); + + docinfo_real (doc, + data->dialog); + + selectioninfo_real (doc, + data->dialog); + + break; + } + } +} + +static const GtkActionEntry action_entries[] = +{ + { "DocumentStatistics", + NULL, + N_("_Document Statistics"), + NULL, + N_("Get statistical information on the current document"), + G_CALLBACK (docinfo_cb) } +}; + +static void +free_window_data (WindowData *data) +{ + g_return_if_fail (data != NULL); + + gedit_debug (DEBUG_PLUGINS); + + g_object_unref (data->plugin); + + g_object_unref (data->ui_action_group); + + if (data->dialog != NULL) + { + gtk_widget_destroy (data->dialog->dialog); + } + + g_free (data); +} + +static void +update_ui_real (GeditWindow *window, + WindowData *data) +{ + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (window); + + gtk_action_group_set_sensitive (data->ui_action_group, + (view != NULL)); + + if (data->dialog != NULL) + { + gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog->dialog), + GTK_RESPONSE_OK, + (view != NULL)); + } +} + +static void +gedit_docinfo_plugin_init (GeditDocInfoPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditDocInfoPlugin initializing"); +} + +static void +gedit_docinfo_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditDocInfoPlugin finalizing"); + + G_OBJECT_CLASS (gedit_docinfo_plugin_parent_class)->finalize (object); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = g_new (WindowData, 1); + + data->plugin = g_object_ref (plugin); + data->dialog = NULL; + data->ui_action_group = gtk_action_group_new ("GeditDocInfoPluginActions"); + + gtk_action_group_set_translation_domain (data->ui_action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions (data->ui_action_group, + action_entries, + G_N_ELEMENTS (action_entries), + window); + + manager = gedit_window_get_ui_manager (window); + gtk_ui_manager_insert_action_group (manager, + data->ui_action_group, + -1); + + data->ui_id = gtk_ui_manager_new_merge_id (manager); + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + (GDestroyNotify) free_window_data); + + gtk_ui_manager_add_ui (manager, + data->ui_id, + MENU_PATH, + "DocumentStatistics", + "DocumentStatistics", + GTK_UI_MANAGER_MENUITEM, + FALSE); + + update_ui_real (window, + data); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + manager = gedit_window_get_ui_manager (window); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + gtk_ui_manager_remove_ui (manager, + data->ui_id); + gtk_ui_manager_remove_action_group (manager, + data->ui_action_group); + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + update_ui_real (window, + data); +} + +static void +gedit_docinfo_plugin_class_init (GeditDocInfoPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_docinfo_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; +} diff --git a/plugins/docinfo/gedit-docinfo-plugin.h b/plugins/docinfo/gedit-docinfo-plugin.h new file mode 100755 index 00000000..36d6bddc --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.h @@ -0,0 +1,75 @@ +/* + * gedit-docinfo-plugin.h + * + * 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, 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$ + */ + +#ifndef __GEDIT_DOCINFO_PLUGIN_H__ +#define __GEDIT_DOCINFO_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_DOCINFO_PLUGIN (gedit_docinfo_plugin_get_type ()) +#define GEDIT_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPlugin)) +#define GEDIT_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPluginClass)) +#define GEDIT_IS_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_DOCINFO_PLUGIN)) +#define GEDIT_IS_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_DOCINFO_PLUGIN)) +#define GEDIT_DOCINFO_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPluginClass)) + +/* Private structure type */ +typedef struct _GeditDocInfoPluginPrivate GeditDocInfoPluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditDocInfoPlugin GeditDocInfoPlugin; + +struct _GeditDocInfoPlugin +{ + GeditPlugin parent_instance; +}; + +/* + * Class definition + */ +typedef struct _GeditDocInfoPluginClass GeditDocInfoPluginClass; + +struct _GeditDocInfoPluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_docinfo_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_DOCINFO_PLUGIN_H__ */ diff --git a/plugins/externaltools/Makefile.am b/plugins/externaltools/Makefile.am new file mode 100755 index 00000000..f529640c --- /dev/null +++ b/plugins/externaltools/Makefile.am @@ -0,0 +1,15 @@ +# External Tools plugin +SUBDIRS = tools data scripts +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = externaltools.gedit-plugin.desktop.in +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/data/Makefile.am b/plugins/externaltools/data/Makefile.am new file mode 100755 index 00000000..ae3a1c66 --- /dev/null +++ b/plugins/externaltools/data/Makefile.am @@ -0,0 +1,65 @@ +TOOL_MERGE=$(top_srcdir)/plugins/externaltools/scripts/gedit-tool-merge.pl + +tools_in_files = \ + build.tool.in \ + remove-trailing-spaces.tool.in + +tools_in_linux = \ + open-terminal-here.tool.in \ + run-command.tool.in + +tools_in_osx = \ + open-terminal-here-osx.tool.in + +tools_in_win32 = + +install_tools_in_files = $(tools_in_files) + +if PLATFORM_OSX +install_tools_in_files += $(tools_in_osx) +else +if PLATFORM_WIN32 +install_tools_in_files += $(tools_in_win32) +else +install_tools_in_files += $(tools_in_linux) +endif +endif + +desktop_in_files = $(install_tools_in_files:.tool.in=.desktop.in) +desktop_files = $(install_tools_in_files:.tool.in=.desktop) + +tools_SCRIPTS = $(install_tools_in_files:.tool.in=) +toolsdir = $(GEDIT_PLUGINS_DATA_DIR)/externaltools/tools + +all_tools_in_files = \ + $(tools_in_files) \ + $(tools_in_linux) \ + $(tools_in_osx) \ + $(tools_in_win32) + +all_desktop_in_files = $(all_tools_in_files:.tool.in=.desktop.in) +all_desktop_files = $(all_tools_in_files:.tool.in=.desktop) +all_tools_files = $(all_tools_in_files:.tool.in=) + +@INTLTOOL_DESKTOP_RULE@ + +# Tools are generated by merging a script file (.tool.in) with a data file +# (.desktop), which happens to be translated using intltool. +$(tools_SCRIPTS): %: %.tool.in %.desktop $(TOOL_MERGE) + perl $(TOOL_MERGE) -o $@ $< $(word 2,$^) + chmod 755 $@ + +EXTRA_DIST = \ + $(all_desktop_in_files) \ + $(all_tools_in_files) + +CLEANFILES = \ + $(all_desktop_files) \ + $(all_tools_files) + +DISTCLEANFILES = \ + $(all_desktop_files) \ + $(all_tools_files) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/data/build.desktop.in b/plugins/externaltools/data/build.desktop.in new file mode 100755 index 00000000..13767ee7 --- /dev/null +++ b/plugins/externaltools/data/build.desktop.in @@ -0,0 +1,9 @@ +[Gedit Tool] +_Name=Build +_Comment=Run "make" in the document directory +Input=nothing +Output=output-panel +Shortcut=<Control>F8 +Applicability=local +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/build.tool.in b/plugins/externaltools/data/build.tool.in new file mode 100755 index 00000000..0b81d5b6 --- /dev/null +++ b/plugins/externaltools/data/build.tool.in @@ -0,0 +1,15 @@ +#!/bin/sh + +EHOME=`echo $HOME | sed "s/#/\#/"` +DIR=$GEDIT_CURRENT_DOCUMENT_DIR +while test "$DIR" != "/"; do + for m in GNUmakefile makefile Makefile; do + if [ -f "${DIR}/${m}" ]; then + echo "Using ${m} from ${DIR}" | sed "s#$EHOME#~#" > /dev/stderr + make -C "${DIR}" + exit + fi + done + DIR=`dirname "${DIR}"` +done +echo "No Makefile found!" > /dev/stderr diff --git a/plugins/externaltools/data/open-terminal-here-osx.desktop.in b/plugins/externaltools/data/open-terminal-here-osx.desktop.in new file mode 100755 index 00000000..801b003c --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here-osx.desktop.in @@ -0,0 +1,8 @@ +[Gedit Tool] +_Name=Open terminal here +_Comment=Open a terminal in the document location +Input=nothing +Output=output-panel +Applicability=local +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/open-terminal-here-osx.tool.in b/plugins/externaltools/data/open-terminal-here-osx.tool.in new file mode 100755 index 00000000..c3360064 --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here-osx.tool.in @@ -0,0 +1,16 @@ +#!/usr/bin/osascript + +set the_path to system attribute "GEDIT_CURRENT_DOCUMENT_DIR" +set cmd to "cd " & quoted form of the_path + +tell application "System Events" to set terminalIsRunning to exists application process "Terminal" + +tell application "Terminal" + activate + + if terminalIsRunning is true then + do script with command cmd + else + do script with command cmd in window 1 + end if +end tell diff --git a/plugins/externaltools/data/open-terminal-here.desktop.in b/plugins/externaltools/data/open-terminal-here.desktop.in new file mode 100755 index 00000000..801b003c --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.desktop.in @@ -0,0 +1,8 @@ +[Gedit Tool] +_Name=Open terminal here +_Comment=Open a terminal in the document location +Input=nothing +Output=output-panel +Applicability=local +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/open-terminal-here.tool.in b/plugins/externaltools/data/open-terminal-here.tool.in new file mode 100755 index 00000000..d2dda8db --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "mateconftool-2 -g /desktop/mate/applications/terminal/exec" +mate-terminal --working-directory=$GEDIT_CURRENT_DOCUMENT_DIR & diff --git a/plugins/externaltools/data/remove-trailing-spaces.desktop.in b/plugins/externaltools/data/remove-trailing-spaces.desktop.in new file mode 100755 index 00000000..99b8b703 --- /dev/null +++ b/plugins/externaltools/data/remove-trailing-spaces.desktop.in @@ -0,0 +1,9 @@ +[Gedit Tool] +_Name=Remove trailing spaces +_Comment=Remove useless trailing spaces in your file +Input=document +Output=replace-document +Shortcut=<Alt>F12 +Applicability=all +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/remove-trailing-spaces.tool.in b/plugins/externaltools/data/remove-trailing-spaces.tool.in new file mode 100755 index 00000000..83e4c19b --- /dev/null +++ b/plugins/externaltools/data/remove-trailing-spaces.tool.in @@ -0,0 +1,3 @@ +#!/bin/sh + +sed 's/[[:blank:]]*$//' diff --git a/plugins/externaltools/data/run-command.desktop.in b/plugins/externaltools/data/run-command.desktop.in new file mode 100755 index 00000000..b58294b0 --- /dev/null +++ b/plugins/externaltools/data/run-command.desktop.in @@ -0,0 +1,8 @@ +[Gedit Tool] +_Name=Run command +_Comment=Execute a custom command and put its output in a new document +Input=nothing +Output=new-document +Applicability=all +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/run-command.tool.in b/plugins/externaltools/data/run-command.tool.in new file mode 100755 index 00000000..ee611bbb --- /dev/null +++ b/plugins/externaltools/data/run-command.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "mateconftool-2 -g /desktop/mate/applications/terminal/exec" +exec `matedialog --entry --title="Run command - gedit" --text="Command to run"` diff --git a/plugins/externaltools/externaltools.gedit-plugin.desktop.in b/plugins/externaltools/externaltools.gedit-plugin.desktop.in new file mode 100755 index 00000000..5212c49b --- /dev/null +++ b/plugins/externaltools/externaltools.gedit-plugin.desktop.in @@ -0,0 +1,9 @@ +[Gedit Plugin] +Loader=python +Module=externaltools +IAge=2 +_Name=External Tools +_Description=Execute external commands and shell scripts. +Authors=Steve Frécinaux <[email protected]> +Copyright=Copyright © 2005 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/externaltools/scripts/Makefile.am b/plugins/externaltools/scripts/Makefile.am new file mode 100755 index 00000000..4ff8060b --- /dev/null +++ b/plugins/externaltools/scripts/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = gedit-tool-merge.pl + + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/scripts/gedit-tool-merge.pl b/plugins/externaltools/scripts/gedit-tool-merge.pl new file mode 100755 index 00000000..780d95dd --- /dev/null +++ b/plugins/externaltools/scripts/gedit-tool-merge.pl @@ -0,0 +1,78 @@ +#!/usr/bin/perl + +# gedit-tool-merge.pl +# This file is part of gedit +# +# Copyright (C) 2006 - Steve Frécinaux <[email protected]> +# +# 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 + +# This script merges a script file with a desktop file containing +# metadata about the external tool. This is required in order to +# have translatable tools (bug #342042) since intltool can't extract +# string directly from tool files (a tool file being the combination +# of a script file and a metadata section). +# +# The desktop file is embedded in a comment of the script file, under +# the assumption that any scripting language supports # as a comment +# mark (this is likely to be true since the shebang uses #!). The +# section is placed at the top of the tool file, after the shebang and +# modelines if present. + +use strict; +use warnings; +use Getopt::Long; + +sub usage { + print <<EOF; +Usage: ${0} [OPTION]... [SCRIPT] [DESKTOP] +Merges a script file with a desktop file, embedding it in a comment. + + -o, --output=FILE Specify the output file name. [default: stdout] +EOF + exit; +} + +my $output = ""; +my $help; + +GetOptions ("help|h" => \$help, "output|o=s" => \$output) or &usage; +usage if $help or @ARGV lt 2; + +open INFILE, "<", $ARGV[0]; +open DFILE, "<", $ARGV[1]; +open STDOUT, ">", $output if $output; + +# Put shebang and various modelines at the top of the generated file. +$_ = <INFILE>; +print and $_ = <INFILE> if /^#!/; +print and $_ = <INFILE> if /-\*-/; +print and $_ = <INFILE> if /(ex|vi|vim):/; + +# Put a blank line before the info block if there is one in INFILE. +print and $_ = <INFILE> if /^\s*$/; +seek INFILE, -length, 1; + +# Embed the desktop file... +print "# $_" while <DFILE>; +print "\n"; + +# ...and write the remaining part of the script. +print while <INFILE>; + +close INFILE; +close DFILE; +close STDOUT; diff --git a/plugins/externaltools/tools/Makefile.am b/plugins/externaltools/tools/Makefile.am new file mode 100755 index 00000000..5edcab58 --- /dev/null +++ b/plugins/externaltools/tools/Makefile.am @@ -0,0 +1,23 @@ +# Python snippets plugin + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/externaltools +plugin_PYTHON = \ + __init__.py \ + capture.py \ + library.py \ + functions.py \ + manager.py \ + outputpanel.py \ + filelookup.py \ + linkparsing.py + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/externaltools/ui +ui_DATA = tools.ui \ + outputpanel.ui + +EXTRA_DIST = $(ui_DATA) + +CLEANFILES = *.bak *.gladep +DISTCLEANFILES = *.bak *.gladep + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py new file mode 100755 index 00000000..a46aef8f --- /dev/null +++ b/plugins/externaltools/tools/__init__.py @@ -0,0 +1,281 @@ +# -*- coding: UTF-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('ExternalToolsPlugin', 'ExternalToolsWindowHelper', + 'Manager', 'OutputPanel', 'Capture', 'UniqueById') + +import gedit +import gtk +from manager import Manager +from library import ToolLibrary +from outputpanel import OutputPanel +from capture import Capture +from functions import * + +class ToolMenu(object): + ACTION_HANDLER_DATA_KEY = "ExternalToolActionHandlerData" + ACTION_ITEM_DATA_KEY = "ExternalToolActionItemData" + + def __init__(self, library, window, menupath): + super(ToolMenu, self).__init__() + self._library = library + self._window = window + self._menupath = menupath + + self._merge_id = 0 + self._action_group = gtk.ActionGroup("ExternalToolsPluginToolActions") + self._signals = [] + + self.update() + + def deactivate(self): + self.remove() + + def remove(self): + if self._merge_id != 0: + self._window.get_ui_manager().remove_ui(self._merge_id) + self._window.get_ui_manager().remove_action_group(self._action_group) + self._merge_id = 0 + + for action in self._action_group.list_actions(): + handler = action.get_data(self.ACTION_HANDLER_DATA_KEY) + + if handler is not None: + action.disconnect(handler) + + action.set_data(self.ACTION_ITEM_DATA_KEY, None) + action.set_data(self.ACTION_HANDLER_DATA_KEY, None) + + self._action_group.remove_action(action) + + accelmap = gtk.accel_map_get() + + for s in self._signals: + accelmap.disconnect(s) + + self._signals = [] + + def _insert_directory(self, directory, path): + manager = self._window.get_ui_manager() + + for item in directory.subdirs: + action_name = 'ExternalToolDirectory%X' % id(item) + action = gtk.Action(action_name, item.name.replace('_', '__'), None, None) + self._action_group.add_action(action) + + manager.add_ui(self._merge_id, path, + action_name, action_name, + gtk.UI_MANAGER_MENU, False) + + self._insert_directory(item, path + '/' + action_name) + + for item in directory.tools: + action_name = 'ExternalToolTool%X' % id(item) + action = gtk.Action(action_name, item.name.replace('_', '__'), item.comment, None) + handler = action.connect("activate", capture_menu_action, self._window, item) + + action.set_data(self.ACTION_ITEM_DATA_KEY, item) + action.set_data(self.ACTION_HANDLER_DATA_KEY, handler) + + # Make sure to replace accel + accelpath = '<Actions>/ExternalToolsPluginToolActions/%s' % (action_name, ) + + if item.shortcut: + key, mod = gtk.accelerator_parse(item.shortcut) + gtk.accel_map_change_entry(accelpath, key, mod, True) + + self._signals.append(gtk.accel_map_get().connect('changed::%s' % (accelpath,), self.on_accelmap_changed, item)) + + self._action_group.add_action_with_accel(action, item.shortcut) + + manager.add_ui(self._merge_id, path, + action_name, action_name, + gtk.UI_MANAGER_MENUITEM, False) + + def on_accelmap_changed(self, accelmap, path, key, mod, tool): + tool.shortcut = gtk.accelerator_name(key, mod) + tool.save() + + self._window.get_data("ExternalToolsPluginWindowData").update_manager(tool) + + def update(self): + self.remove() + self._merge_id = self._window.get_ui_manager().new_merge_id() + self._insert_directory(self._library.tree, self._menupath) + self._window.get_ui_manager().insert_action_group(self._action_group, -1) + self.filter(self._window.get_active_document()) + + def filter_language(self, language, item): + if not item.languages: + return True + + if not language and 'plain' in item.languages: + return True + + if language and (language.get_id() in item.languages): + return True + else: + return False + + def filter(self, document): + if document is None: + return + + titled = document.get_uri() is not None + remote = not document.is_local() + + states = { + 'all' : True, + 'local': titled and not remote, + 'remote': titled and remote, + 'titled': titled, + 'untitled': not titled, + } + + language = document.get_language() + + for action in self._action_group.list_actions(): + item = action.get_data(self.ACTION_ITEM_DATA_KEY) + + if item is not None: + action.set_visible(states[item.applicability] and self.filter_language(language, item)) + +class ExternalToolsWindowHelper(object): + def __init__(self, plugin, window): + super(ExternalToolsWindowHelper, self).__init__() + + self._window = window + self._plugin = plugin + self._library = ToolLibrary() + + manager = window.get_ui_manager() + + self._action_group = gtk.ActionGroup('ExternalToolsPluginActions') + self._action_group.set_translation_domain('gedit') + self._action_group.add_actions([('ExternalToolManager', + None, + _('Manage _External Tools...'), + None, + _("Opens the External Tools Manager"), + lambda action: plugin.open_dialog()), + ('ExternalTools', + None, + _('External _Tools'), + None, + _("External tools"), + None)]) + manager.insert_action_group(self._action_group, -1) + + ui_string = """ + <ui> + <menubar name="MenuBar"> + <menu name="ToolsMenu" action="Tools"> + <placeholder name="ToolsOps_4"> + <separator/> + <menu name="ExternalToolsMenu" action="ExternalTools"> + <placeholder name="ExternalToolPlaceholder"/> + </menu> + <separator/> + </placeholder> + <placeholder name="ToolsOps_5"> + <menuitem name="ExternalToolManager" action="ExternalToolManager"/> + </placeholder> + </menu> + </menubar> + </ui>""" + + self._merge_id = manager.add_ui_from_string(ui_string) + + self.menu = ToolMenu(self._library, self._window, + "/MenuBar/ToolsMenu/ToolsOps_4/ExternalToolsMenu/ExternalToolPlaceholder") + manager.ensure_update() + + # Create output console + self._output_buffer = OutputPanel(self._plugin.get_data_dir(), window) + bottom = window.get_bottom_panel() + bottom.add_item(self._output_buffer.panel, + _("Shell Output"), + gtk.STOCK_EXECUTE) + + def update_ui(self): + self.menu.filter(self._window.get_active_document()) + self._window.get_ui_manager().ensure_update() + + def deactivate(self): + manager = self._window.get_ui_manager() + self.menu.deactivate() + manager.remove_ui(self._merge_id) + manager.remove_action_group(self._action_group) + manager.ensure_update() + + bottom = self._window.get_bottom_panel() + bottom.remove_item(self._output_buffer.panel) + + def update_manager(self, tool): + self._plugin.update_manager(tool) + +class ExternalToolsPlugin(gedit.Plugin): + WINDOW_DATA_KEY = "ExternalToolsPluginWindowData" + + def __init__(self): + super(ExternalToolsPlugin, self).__init__() + + self._manager = None + self._manager_default_size = None + + ToolLibrary().set_locations(os.path.join(self.get_data_dir(), 'tools')) + + def activate(self, window): + helper = ExternalToolsWindowHelper(self, window) + window.set_data(self.WINDOW_DATA_KEY, helper) + + def deactivate(self, window): + window.get_data(self.WINDOW_DATA_KEY).deactivate() + window.set_data(self.WINDOW_DATA_KEY, None) + + def update_ui(self, window): + window.get_data(self.WINDOW_DATA_KEY).update_ui() + + def create_configure_dialog(self): + return self.open_dialog() + + def open_dialog(self): + if not self._manager: + self._manager = Manager(self.get_data_dir()) + + if self._manager_default_size: + self._manager.dialog.set_default_size(*self._manager_default_size) + + self._manager.dialog.connect('destroy', self.on_manager_destroy) + + window = gedit.app_get_default().get_active_window() + self._manager.run(window) + + return self._manager.dialog + + def update_manager(self, tool): + if not self._manager: + return + + self._manager.tool_changed(tool, True) + + def on_manager_destroy(self, dialog): + self._manager_default_size = [dialog.allocation.width, dialog.allocation.height] + self._manager = None + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py new file mode 100755 index 00000000..e47862c8 --- /dev/null +++ b/plugins/externaltools/tools/capture.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('Capture', ) + +import os, sys, signal +import locale +import subprocess +import gobject +import fcntl +import glib + +class Capture(gobject.GObject): + CAPTURE_STDOUT = 0x01 + CAPTURE_STDERR = 0x02 + CAPTURE_BOTH = 0x03 + CAPTURE_NEEDS_SHELL = 0x04 + + WRITE_BUFFER_SIZE = 0x4000 + + __gsignals__ = { + 'stdout-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'stderr-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'begin-execute': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, tuple()), + 'end-execute' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)) + } + + def __init__(self, command, cwd = None, env = {}): + gobject.GObject.__init__(self) + self.pipe = None + self.env = env + self.cwd = cwd + self.flags = self.CAPTURE_BOTH | self.CAPTURE_NEEDS_SHELL + self.command = command + self.input_text = None + + def set_env(self, **values): + self.env.update(**values) + + def set_command(self, command): + self.command = command + + def set_flags(self, flags): + self.flags = flags + + def set_input(self, text): + self.input_text = text + + def set_cwd(self, cwd): + self.cwd = cwd + + def execute(self): + if self.command is None: + return + + # Initialize pipe + popen_args = { + 'cwd' : self.cwd, + 'shell': self.flags & self.CAPTURE_NEEDS_SHELL, + 'env' : self.env + } + + if self.input_text is not None: + popen_args['stdin'] = subprocess.PIPE + if self.flags & self.CAPTURE_STDOUT: + popen_args['stdout'] = subprocess.PIPE + if self.flags & self.CAPTURE_STDERR: + popen_args['stderr'] = subprocess.PIPE + + self.tried_killing = False + self.idle_write_id = 0 + self.read_buffer = '' + + try: + self.pipe = subprocess.Popen(self.command, **popen_args) + except OSError, e: + self.pipe = None + self.emit('stderr-line', _('Could not execute command: %s') % (e, )) + return + + # Signal + self.emit('begin-execute') + + if self.flags & self.CAPTURE_STDOUT: + # Set non blocking + flags = fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK + fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_SETFL, flags) + + gobject.io_add_watch(self.pipe.stdout, + gobject.IO_IN | gobject.IO_HUP, + self.on_output) + + if self.flags & self.CAPTURE_STDERR: + # Set non blocking + flags = fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK + fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_SETFL, flags) + + gobject.io_add_watch(self.pipe.stderr, + gobject.IO_IN | gobject.IO_HUP, + self.on_output) + + # IO + if self.input_text is not None: + # Write async, in chunks of something + self.write_buffer = str(self.input_text) + + if self.idle_write_chunk(): + self.idle_write_id = gobject.idle_add(self.idle_write_chunk) + + # Wait for the process to complete + gobject.child_watch_add(self.pipe.pid, self.on_child_end) + + def idle_write_chunk(self): + if not self.pipe: + self.idle_write_id = 0 + return False + + try: + l = len(self.write_buffer) + m = min(l, self.WRITE_BUFFER_SIZE) + + self.pipe.stdin.write(self.write_buffer[:m]) + + if m == l: + self.write_buffer = '' + self.pipe.stdin.close() + + self.idle_write_id = 0 + + return False + else: + self.write_buffer = self.write_buffer[m:] + return True + except IOError: + self.pipe.stdin.close() + self.idle_write_id = 0 + + return False + + def on_output(self, source, condition): + if condition & (glib.IO_IN | glib.IO_PRI): + line = source.read() + + if len(line) > 0: + try: + line = unicode(line, 'utf-8') + except: + line = unicode(line, + locale.getdefaultlocale()[1], + 'replace') + + self.read_buffer += line + lines = self.read_buffer.splitlines(True) + + if not lines[-1].endswith("\n"): + self.read_buffer = lines[-1] + lines = lines[0:-1] + else: + self.read_buffer = '' + + for line in lines: + if not self.pipe or source == self.pipe.stdout: + self.emit('stdout-line', line) + else: + self.emit('stderr-line', line) + + if condition & ~(glib.IO_IN | glib.IO_PRI): + if self.read_buffer: + if source == self.pipe.stdout: + self.emit('stdout-line', self.read_buffer) + else: + self.emit('stderr-line', self.read_buffer) + + self.read_buffer = '' + + self.pipe = None + + return False + else: + return True + + def stop(self, error_code = -1): + if self.pipe is not None: + if self.idle_write_id: + gobject.source_remove(self.idle_write_id) + self.idle_write_id = 0 + + if not self.tried_killing: + os.kill(self.pipe.pid, signal.SIGTERM) + self.tried_killing = True + else: + os.kill(self.pipe.pid, signal.SIGKILL) + + def on_child_end(self, pid, error_code): + # In an idle, so it is emitted after all the std*-line signals + # have been intercepted + gobject.idle_add(self.emit, 'end-execute', error_code) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/filelookup.py b/plugins/externaltools/tools/filelookup.py new file mode 100755 index 00000000..229823b7 --- /dev/null +++ b/plugins/externaltools/tools/filelookup.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2010 Per Arneng <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import gio +import gedit + +class FileLookup: + """ + This class is responsible for looking up files given a part or the whole + path of a real file. The lookup is delegated to providers wich use different + methods of trying to find the real file. + """ + + def __init__(self): + self.providers = [] + self.providers.append(AbsoluteFileLookupProvider()) + self.providers.append(CwdFileLookupProvider()) + self.providers.append(OpenDocumentRelPathFileLookupProvider()) + self.providers.append(OpenDocumentFileLookupProvider()) + + def lookup(self, path): + """ + Tries to find a file specified by the path parameter. It delegates to + different lookup providers and the first match is returned. If no file + was found then None is returned. + + path -- the path to find + """ + found_file = None + for provider in self.providers: + found_file = provider.lookup(path) + if found_file is not None: + break + + return found_file + + +class FileLookupProvider: + """ + The base class of all file lookup providers. + """ + + def lookup(self, path): + """ + This method must be implemented by subclasses. Implementors will be + given a path and will try to find a matching file. If no file is found + then None is returned. + """ + raise NotImplementedError("need to implement a lookup method") + + +class AbsoluteFileLookupProvider(FileLookupProvider): + """ + This file tries to see if the path given is an absolute path and that the + path references a file. + """ + + def lookup(self, path): + if os.path.isabs(path) and os.path.isfile(path): + return gio.File(path) + else: + return None + + +class CwdFileLookupProvider(FileLookupProvider): + """ + This lookup provider tries to find a file specified by the path relative to + the current working directory. + """ + + def lookup(self, path): + try: + cwd = os.getcwd() + except OSError: + cwd = os.getenv('HOME') + + real_path = os.path.join(cwd, path) + + if os.path.isfile(real_path): + return gio.File(real_path) + else: + return None + + +class OpenDocumentRelPathFileLookupProvider(FileLookupProvider): + """ + Tries to see if the path is relative to any directories where the + currently open documents reside in. Example: If you have a document opened + '/tmp/Makefile' and a lookup is made for 'src/test2.c' then this class + will try to find '/tmp/src/test2.c'. + """ + + def lookup(self, path): + if path.startswith('/'): + return None + + for doc in gedit.app_get_default().get_documents(): + if doc.is_local(): + location = doc.get_location() + if location: + rel_path = location.get_parent().get_path() + joined_path = os.path.join(rel_path, path) + if os.path.isfile(joined_path): + return gio.File(joined_path) + + return None + + +class OpenDocumentFileLookupProvider(FileLookupProvider): + """ + Makes a guess that the if the path that was looked for matches the end + of the path of a currently open document then that document is the one + that is looked for. Example: If a document is opened called '/tmp/t.c' + and a lookup is made for 't.c' or 'tmp/t.c' then both will match since + the open document ends with the path that is searched for. + """ + + def lookup(self, path): + if path.startswith('/'): + return None + + for doc in gedit.app_get_default().get_documents(): + if doc.is_local(): + location = doc.get_location() + if location and location.get_uri().endswith(path): + return location + return None + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/functions.py b/plugins/externaltools/tools/functions.py new file mode 100755 index 00000000..0d2bfdbf --- /dev/null +++ b/plugins/externaltools/tools/functions.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import gtk +from gtk import gdk +import gio +import gedit +#import gtksourceview +from outputpanel import OutputPanel +from capture import * + +def default(val, d): + if val is not None: + return val + else: + return d + +def current_word(document): + piter = document.get_iter_at_mark(document.get_insert()) + start = piter.copy() + + if not piter.starts_word() and (piter.inside_word() or piter.ends_word()): + start.backward_word_start() + + if not piter.ends_word() and piter.inside_word(): + piter.forward_word_end() + + return (start, piter) + +# ==== Capture related functions ==== +def run_external_tool(window, node): + # Configure capture environment + try: + cwd = os.getcwd() + except OSError: + cwd = os.getenv('HOME'); + + capture = Capture(node.command, cwd) + capture.env = os.environ.copy() + capture.set_env(GEDIT_CWD = cwd) + + view = window.get_active_view() + if view is not None: + # Environment vars relative to current document + document = view.get_buffer() + uri = document.get_uri() + + # Current line number + piter = document.get_iter_at_mark(document.get_insert()) + capture.set_env(GEDIT_CURRENT_LINE_NUMBER=str(piter.get_line() + 1)) + + # Current line text + piter.set_line_offset(0) + end = piter.copy() + + if not end.ends_line(): + end.forward_to_line_end() + + capture.set_env(GEDIT_CURRENT_LINE=piter.get_text(end)) + + # Selected text (only if input is not selection) + if node.input != 'selection' and node.input != 'selection-document': + bounds = document.get_selection_bounds() + + if bounds: + capture.set_env(GEDIT_SELECTED_TEXT=bounds[0].get_text(bounds[1])) + + bounds = current_word(document) + capture.set_env(GEDIT_CURRENT_WORD=bounds[0].get_text(bounds[1])) + + capture.set_env(GEDIT_CURRENT_DOCUMENT_TYPE=document.get_mime_type()) + + if uri is not None: + gfile = gio.File(uri) + scheme = gfile.get_uri_scheme() + name = os.path.basename(uri) + capture.set_env(GEDIT_CURRENT_DOCUMENT_URI = uri, + GEDIT_CURRENT_DOCUMENT_NAME = name, + GEDIT_CURRENT_DOCUMENT_SCHEME = scheme) + if gedit.utils.uri_has_file_scheme(uri): + path = gfile.get_path() + cwd = os.path.dirname(path) + capture.set_cwd(cwd) + capture.set_env(GEDIT_CURRENT_DOCUMENT_PATH = path, + GEDIT_CURRENT_DOCUMENT_DIR = cwd) + + documents_uri = [doc.get_uri() + for doc in window.get_documents() + if doc.get_uri() is not None] + documents_path = [gio.File(uri).get_path() + for uri in documents_uri + if gedit.utils.uri_has_file_scheme(uri)] + capture.set_env(GEDIT_DOCUMENTS_URI = ' '.join(documents_uri), + GEDIT_DOCUMENTS_PATH = ' '.join(documents_path)) + + flags = capture.CAPTURE_BOTH + + if not node.has_hash_bang(): + flags |= capture.CAPTURE_NEEDS_SHELL + + capture.set_flags(flags) + + # Get input text + input_type = node.input + output_type = node.output + + # Get the panel + panel = window.get_data("ExternalToolsPluginWindowData")._output_buffer + panel.clear() + + if output_type == 'output-panel': + panel.show() + + # Assign the error output to the output panel + panel.set_process(capture) + + if input_type != 'nothing' and view is not None: + if input_type == 'document': + start, end = document.get_bounds() + elif input_type == 'selection' or input_type == 'selection-document': + try: + start, end = document.get_selection_bounds() + + print start, end + except ValueError: + if input_type == 'selection-document': + start, end = document.get_bounds() + + if output_type == 'replace-selection': + document.select_range(start, end) + else: + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() + + elif input_type == 'line': + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() + if not start.starts_line(): + start.set_line_offset(0) + if not end.ends_line(): + end.forward_to_line_end() + elif input_type == 'word': + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() + if not start.inside_word(): + panel.write(_('You must be inside a word to run this command'), + panel.command_tag) + return + if not start.starts_word(): + start.backward_word_start() + if not end.ends_word(): + end.forward_word_end() + + input_text = document.get_text(start, end) + capture.set_input(input_text) + + # Assign the standard output to the chosen "file" + if output_type == 'new-document': + tab = window.create_tab(True) + view = tab.get_view() + document = tab.get_document() + pos = document.get_start_iter() + capture.connect('stdout-line', capture_stdout_line_document, document, pos) + document.begin_user_action() + view.set_editable(False) + view.set_cursor_visible(False) + elif output_type != 'output-panel' and output_type != 'nothing' and view is not None: + document.begin_user_action() + view.set_editable(False) + view.set_cursor_visible(False) + + if output_type == 'insert': + pos = document.get_iter_at_mark(document.get_mark('insert')) + elif output_type == 'replace-selection': + document.delete_selection(False, False) + pos = document.get_iter_at_mark(document.get_mark('insert')) + elif output_type == 'replace-document': + document.set_text('') + pos = document.get_end_iter() + else: + pos = document.get_end_iter() + capture.connect('stdout-line', capture_stdout_line_document, document, pos) + elif output_type != 'nothing': + capture.connect('stdout-line', capture_stdout_line_panel, panel) + document.begin_user_action() + + capture.connect('stderr-line', capture_stderr_line_panel, panel) + capture.connect('begin-execute', capture_begin_execute_panel, panel, view, node.name) + capture.connect('end-execute', capture_end_execute_panel, panel, view, output_type) + + # Run the command + capture.execute() + + if output_type != 'nothing': + document.end_user_action() + +class MultipleDocumentsSaver: + def __init__(self, window, docs, node): + self._window = window + self._node = node + self._error = False + + self._counter = len(docs) + self._signal_ids = {} + self._counter = 0 + + signals = {} + + for doc in docs: + signals[doc] = doc.connect('saving', self.on_document_saving) + gedit.commands.save_document(window, doc) + doc.disconnect(signals[doc]) + + def on_document_saving(self, doc, size, total_size): + self._counter += 1 + self._signal_ids[doc] = doc.connect('saved', self.on_document_saved) + + def on_document_saved(self, doc, error): + if error: + self._error = True + + doc.disconnect(self._signal_ids[doc]) + del self._signal_ids[doc] + + self._counter -= 1 + + if self._counter == 0 and not self._error: + run_external_tool(self._window, self._node) + +def capture_menu_action(action, window, node): + if node.save_files == 'document' and window.get_active_document(): + MultipleDocumentsSaver(window, [window.get_active_document()], node) + return + elif node.save_files == 'all': + MultipleDocumentsSaver(window, window.get_documents(), node) + return + + run_external_tool(window, node) + +def capture_stderr_line_panel(capture, line, panel): + if not panel.visible(): + panel.show() + + panel.write(line, panel.error_tag) + +def capture_begin_execute_panel(capture, panel, view, label): + view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gdk.Cursor(gdk.WATCH)) + + panel['stop'].set_sensitive(True) + panel.clear() + panel.write(_("Running tool:"), panel.italic_tag); + panel.write(" %s\n\n" % label, panel.bold_tag); + +def capture_end_execute_panel(capture, exit_code, panel, view, output_type): + panel['stop'].set_sensitive(False) + + if output_type in ('new-document','replace-document'): + doc = view.get_buffer() + start = doc.get_start_iter() + end = start.copy() + end.forward_chars(300) + + mtype = gio.content_type_guess(data=doc.get_text(start, end)) + lmanager = gedit.get_language_manager() + + language = lmanager.guess_language(doc.get_uri(), mtype) + + if language is not None: + doc.set_language(language) + + view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gdk.Cursor(gdk.XTERM)) + view.set_cursor_visible(True) + view.set_editable(True) + + if exit_code == 0: + panel.write("\n" + _("Done.") + "\n", panel.italic_tag) + else: + panel.write("\n" + _("Exited") + ":", panel.italic_tag) + panel.write(" %d\n" % exit_code, panel.bold_tag) + +def capture_stdout_line_panel(capture, line, panel): + panel.write(line) + +def capture_stdout_line_document(capture, line, document, pos): + document.insert(pos, line) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py new file mode 100755 index 00000000..6eb6ff1a --- /dev/null +++ b/plugins/externaltools/tools/library.py @@ -0,0 +1,493 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2006 Steve Frécinaux <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import re +import locale +import platform + +class Singleton(object): + _instance = None + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(Singleton, cls).__new__( + cls, *args, **kwargs) + cls._instance.__init_once__() + + return cls._instance + +class ToolLibrary(Singleton): + def __init_once__(self): + self.locations = [] + + def set_locations(self, datadir): + self.locations = [] + + if platform.platform() != 'Windows': + for d in self.get_xdg_data_dirs(): + self.locations.append(os.path.join(d, 'gedit-2', 'plugins', 'externaltools', 'tools')) + + self.locations.append(datadir) + + # self.locations[0] is where we save the custom scripts + if platform.platform() == 'Windows': + toolsdir = os.path.expanduser('~/gedit/tools') + else: + userdir = os.getenv('MATE22_USER_DIR') + if userdir: + toolsdir = os.path.join(userdir, 'gedit/tools') + else: + toolsdir = os.path.expanduser('~/.mate2/gedit/tools') + + self.locations.insert(0, toolsdir); + + if not os.path.isdir(self.locations[0]): + os.makedirs(self.locations[0]) + self.tree = ToolDirectory(self, '') + self.import_old_xml_store() + else: + self.tree = ToolDirectory(self, '') + + # cf. http://standards.freedesktop.org/basedir-spec/latest/ + def get_xdg_data_dirs(self): + dirs = os.getenv('XDG_DATA_DIRS') + if dirs: + dirs = dirs.split(os.pathsep) + else: + dirs = ('/usr/local/share', '/usr/share') + return dirs + + # This function is meant to be ran only once, when the tools directory is + # created. It imports eventual tools that have been saved in the old XML + # storage file. + def import_old_xml_store(self): + import xml.etree.ElementTree as et + userdir = os.getenv('MATE22_USER_DIR') + if userdir: + filename = os.path.join(userdir, 'gedit/gedit-tools.xml') + else: + filename = os.path.expanduser('~/.mate2/gedit/gedit-tools.xml') + + if not os.path.isfile(filename): + return + + print "External tools: importing old tools into the new store..." + + xtree = et.parse(filename) + xroot = xtree.getroot() + + for xtool in xroot: + for i in self.tree.tools: + if i.name == xtool.get('label'): + tool = i + break + else: + tool = Tool(self.tree) + tool.name = xtool.get('label') + tool.autoset_filename() + self.tree.tools.append(tool) + tool.comment = xtool.get('description') + tool.shortcut = xtool.get('accelerator') + tool.applicability = xtool.get('applicability') + tool.output = xtool.get('output') + tool.input = xtool.get('input') + + tool.save_with_script(xtool.text) + + def get_full_path(self, path, mode='r', system = True, local = True): + assert (system or local) + if path is None: + return None + if mode == 'r': + if system and local: + locations = self.locations + elif local and not system: + locations = [self.locations[0]] + elif system and not local: + locations = self.locations[1:] + else: + raise ValueError("system and local can't be both set to False") + + for i in locations: + p = os.path.join(i, path) + if os.path.lexists(p): + return p + return None + else: + path = os.path.join(self.locations[0], path) + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.mkdir(dirname) + return path + +class ToolDirectory(object): + def __init__(self, parent, dirname): + super(ToolDirectory, self).__init__() + self.subdirs = list() + self.tools = list() + if isinstance(parent, ToolDirectory): + self.parent = parent + self.library = parent.library + else: + self.parent = None + self.library = parent + self.dirname = dirname + self._load() + + def listdir(self): + elements = dict() + for l in self.library.locations: + d = os.path.join(l, self.dirname) + if not os.path.isdir(d): + continue + for i in os.listdir(d): + elements[i] = None + keys = elements.keys() + keys.sort() + return keys + + def _load(self): + for p in self.listdir(): + path = os.path.join(self.dirname, p) + full_path = self.library.get_full_path(path) + if os.path.isdir(full_path): + self.subdirs.append(ToolDirectory(self, p)) + elif os.path.isfile(full_path) and os.access(full_path, os.X_OK): + self.tools.append(Tool(self, p)) + + def get_path(self): + if self.parent is None: + return self.dirname + else: + return os.path.join(self.parent.get_path(), self.dirname) + path = property(get_path) + + def get_name(self): + return os.path.basename(self.dirname) + name = property(get_name) + + def delete_tool(self, tool): + # Only remove it if it lays in $HOME + if tool in self.tools: + path = tool.get_path() + if path is not None: + filename = os.path.join(self.library.locations[0], path) + if os.path.isfile(filename): + os.unlink(filename) + self.tools.remove(tool) + return True + else: + return False + + def revert_tool(self, tool): + # Only remove it if it lays in $HOME + filename = os.path.join(self.library.locations[0], tool.get_path()) + if tool in self.tools and os.path.isfile(filename): + os.unlink(filename) + tool._load() + return True + else: + return False + + +class Tool(object): + RE_KEY = re.compile('^([a-zA-Z_][a-zA-Z0-9_.\-]*)(\[([a-zA-Z_@]+)\])?$') + + def __init__(self, parent, filename = None): + super(Tool, self).__init__() + self.parent = parent + self.library = parent.library + self.filename = filename + self.changed = False + self._properties = dict() + self._transform = { + 'Languages': [self._to_list, self._from_list] + } + self._load() + + def _to_list(self, value): + if value.strip() == '': + return [] + else: + return map(lambda x: x.strip(), value.split(',')) + + def _from_list(self, value): + return ','.join(value) + + def _parse_value(self, key, value): + if key in self._transform: + return self._transform[key][0](value) + else: + return value + + def _load(self): + if self.filename is None: + return + + filename = self.library.get_full_path(self.get_path()) + if filename is None: + return + + fp = file(filename, 'r', 1) + in_block = False + lang = locale.getlocale(locale.LC_MESSAGES)[0] + + for line in fp: + if not in_block: + in_block = line.startswith('# [Gedit Tool]') + continue + if line.startswith('##') or line.startswith('# #'): continue + if not line.startswith('# '): break + + try: + (key, value) = [i.strip() for i in line[2:].split('=', 1)] + m = self.RE_KEY.match(key) + if m.group(3) is None: + self._properties[m.group(1)] = self._parse_value(m.group(1), value) + elif lang is not None and lang.startswith(m.group(3)): + self._properties[m.group(1)] = self._parse_value(m.group(1), value) + except ValueError: + break + fp.close() + self.changed = False + + def _set_property_if_changed(self, key, value): + if value != self._properties.get(key): + self._properties[key] = value + + self.changed = True + + def is_global(self): + return self.library.get_full_path(self.get_path(), local=False) is not None + + def is_local(self): + return self.library.get_full_path(self.get_path(), system=False) is not None + + def is_global(self): + return self.library.get_full_path(self.get_path(), local=False) is not None + + def get_path(self): + if self.filename is not None: + return os.path.join(self.parent.get_path(), self.filename) + else: + return None + path = property(get_path) + + # This command is the one that is meant to be ran + # (later, could have an Exec key or something) + def get_command(self): + return self.library.get_full_path(self.get_path()) + command = property(get_command) + + def get_applicability(self): + applicability = self._properties.get('Applicability') + if applicability: return applicability + return 'all' + def set_applicability(self, value): + self._set_property_if_changed('Applicability', value) + applicability = property(get_applicability, set_applicability) + + def get_name(self): + name = self._properties.get('Name') + if name: return name + return os.path.basename(self.filename) + def set_name(self, value): + self._set_property_if_changed('Name', value) + name = property(get_name, set_name) + + def get_shortcut(self): + shortcut = self._properties.get('Shortcut') + if shortcut: return shortcut + return None + def set_shortcut(self, value): + self._set_property_if_changed('Shortcut', value) + shortcut = property(get_shortcut, set_shortcut) + + def get_comment(self): + comment = self._properties.get('Comment') + if comment: return comment + return self.filename + def set_comment(self, value): + self._set_property_if_changed('Comment', value) + comment = property(get_comment, set_comment) + + def get_input(self): + input = self._properties.get('Input') + if input: return input + return 'nothing' + def set_input(self, value): + self._set_property_if_changed('Input', value) + input = property(get_input, set_input) + + def get_output(self): + output = self._properties.get('Output') + if output: return output + return 'output-panel' + def set_output(self, value): + self._set_property_if_changed('Output', value) + output = property(get_output, set_output) + + def get_save_files(self): + save_files = self._properties.get('Save-files') + if save_files: return save_files + return 'nothing' + def set_save_files(self, value): + self._set_property_if_changed('Save-files', value) + save_files = property(get_save_files, set_save_files) + + def get_languages(self): + languages = self._properties.get('Languages') + if languages: return languages + return [] + def set_languages(self, value): + self._set_property_if_changed('Languages', value) + languages = property(get_languages, set_languages) + + def has_hash_bang(self): + if self.filename is None: + return True + + filename = self.library.get_full_path(self.get_path()) + if filename is None: + return True + + fp = open(filename, 'r', 1) + for line in fp: + if line.strip() == '': + continue + + return line.startswith('#!') + + # There is no property for this one because this function is quite + # expensive to perform + def get_script(self): + if self.filename is None: + return ["#!/bin/sh\n"] + + filename = self.library.get_full_path(self.get_path()) + if filename is None: + return ["#!/bin/sh\n"] + + fp = open(filename, 'r', 1) + lines = list() + + # before entering the data block + for line in fp: + if line.startswith('# [Gedit Tool]'): + break + lines.append(line) + # in the block: + for line in fp: + if line.startswith('##'): continue + if not (line.startswith('# ') and '=' in line): + # after the block: strip one emtpy line (if present) + if line.strip() != '': + lines.append(line) + break + # after the block + for line in fp: + lines.append(line) + fp.close() + return lines + + def _dump_properties(self): + lines = ['# [Gedit Tool]'] + for item in self._properties.iteritems(): + if item[0] in self._transform: + lines.append('# %s=%s' % (item[0], self._transform[item[0]][1](item[1]))) + elif item[1] is not None: + lines.append('# %s=%s' % item) + return '\n'.join(lines) + '\n' + + def save_with_script(self, script): + filename = self.library.get_full_path(self.filename, 'w') + + fp = open(filename, 'w', 1) + + # Make sure to first print header (shebang, modeline), then + # properties, and then actual content + header = [] + content = [] + inheader = True + + # Parse + for line in script: + line = line.rstrip("\n") + + if not inheader: + content.append(line) + elif line.startswith('#!'): + # Shebang (should be always present) + header.append(line) + elif line.strip().startswith('#') and ('-*-' in line or 'ex:' in line or 'vi:' in line or 'vim:' in line): + header.append(line) + else: + content.append(line) + inheader = False + + # Write out header + for line in header: + fp.write(line + "\n") + + fp.write(self._dump_properties()) + fp.write("\n") + + for line in content: + fp.write(line + "\n") + + fp.close() + os.chmod(filename, 0750) + self.changed = False + + def save(self): + if self.changed: + self.save_with_script(self.get_script()) + + def autoset_filename(self): + if self.filename is not None: + return + dirname = self.parent.path + if dirname != '': + dirname += os.path.sep + + basename = self.name.lower().replace(' ', '-').replace('/', '-') + + if self.library.get_full_path(dirname + basename): + i = 2 + while self.library.get_full_path(dirname + "%s-%d" % (basename, i)): + i += 1 + basename = "%s-%d" % (basename, i) + self.filename = basename + +if __name__ == '__main__': + library = ToolLibrary() + + def print_tool(t, indent): + print indent * " " + "%s: %s" % (t.filename, t.name) + + def print_dir(d, indent): + print indent * " " + d.dirname + '/' + for i in d.subdirs: + print_dir(i, indent+1) + for i in d.tools: + print_tool(i, indent+1) + + print_dir(library.tree, 0) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/linkparsing.py b/plugins/externaltools/tools/linkparsing.py new file mode 100755 index 00000000..27b9ba89 --- /dev/null +++ b/plugins/externaltools/tools/linkparsing.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2010 Per Arneng <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import re + +class Link: + """ + This class represents a file link from within a string given by the + output of some software tool. A link contains a reference to a file, the + line number within the file and the boundaries within the given output + string that should be marked as a link. + """ + + def __init__(self, path, line_nr, start, end): + """ + path -- the path of the file (that could be extracted) + line_nr -- the line nr of the specified file + start -- the index within the string that the link starts at + end -- the index within the string where the link ends at + """ + self.path = path + self.line_nr = int(line_nr) + self.start = start + self.end = end + + def __repr__(self): + return "%s[%s](%s:%s)" % (self.path, self.line_nr, + self.start, self.end) + +class LinkParser: + """ + Parses a text using different parsing providers with the goal of finding one + or more file links within the text. A typical example could be the output + from a compiler that specifies an error in a specific file. The path of the + file, the line nr and some more info is then returned so that it can be used + to be able to navigate from the error output in to the specific file. + + The actual work of parsing the text is done by instances of classes that + inherits from AbstractLinkParser or by regular expressions. To add a new + parser just create a class that inherits from AbstractLinkParser and then + register in this class cunstructor using the method add_parser. If you want + to add a regular expression then just call add_regexp in this class + constructor and provide your regexp string as argument. + """ + + def __init__(self): + self._providers = [] + self.add_regexp(REGEXP_STANDARD) + self.add_regexp(REGEXP_PYTHON) + self.add_regexp(REGEXP_VALAC) + self.add_regexp(REGEXP_BASH) + self.add_regexp(REGEXP_RUBY) + self.add_regexp(REGEXP_PERL) + self.add_regexp(REGEXP_MCS) + + def add_parser(self, parser): + self._providers.append(parser) + + def add_regexp(self, regexp): + """ + Adds a regular expression string that should match a link using + re.MULTILINE and re.VERBOSE regexp. The area marked as a link should + be captured by a group named lnk. The path of the link should be + captured by a group named pth. The line number should be captured by + a group named ln. To read more about this look at the documentation + for the RegexpLinkParser constructor. + """ + self.add_parser(RegexpLinkParser(regexp)) + + def parse(self, text): + """ + Parses the given text and returns a list of links that are parsed from + the text. This method delegates to parser providers that can parse + output from different kinds of formats. If no links are found then an + empty list is returned. + + text -- the text to scan for file links. 'text' can not be None. + """ + if text is None: + raise ValueError("text can not be None") + + links = [] + + for provider in self._providers: + links.extend(provider.parse(text)) + + return links + +class AbstractLinkParser(object): + """The "abstract" base class for link parses""" + + def parse(self, text): + """ + This method should be implemented by subclasses. It takes a text as + argument (never None) and then returns a list of Link objects. If no + links are found then an empty list is expected. The Link class is + defined in this module. If you do not override this method then a + NotImplementedError will be thrown. + + text -- the text to parse. This argument is never None. + """ + raise NotImplementedError("need to implement a parse method") + +class RegexpLinkParser(AbstractLinkParser): + """ + A class that represents parsers that only use one single regular expression. + It can be used by subclasses or by itself. See the constructor documentation + for details about the rules surrouning the regexp. + """ + + def __init__(self, regex): + """ + Creates a new RegexpLinkParser based on the given regular expression. + The regular expression is multiline and verbose (se python docs on + compilation flags). The regular expression should contain three named + capturing groups 'lnk', 'pth' and 'ln'. 'lnk' represents the area wich + should be marked as a link in the text. 'pth' is the path that should + be looked for and 'ln' is the line number in that file. + """ + self.re = re.compile(regex, re.MULTILINE | re.VERBOSE) + + def parse(self, text): + links = [] + for m in re.finditer(self.re, text): + path = m.group("pth") + line_nr = m.group("ln") + start = m.start("lnk") + end = m.end("lnk") + link = Link(path, line_nr, start, end) + links.append(link) + + return links + +# gcc 'test.c:13: warning: ...' +# javac 'Test.java:13: ...' +# ruby 'test.rb:5: ...' +# scalac 'Test.scala:5: ...' +# 6g (go) 'test.go:9: ...' +REGEXP_STANDARD = r""" +^ +(?P<lnk> + (?P<pth> .*[a-z0-9] ) + \: + (?P<ln> \d+) +) +\:\s""" + +# python ' File "test.py", line 13' +REGEXP_PYTHON = r""" +^\s\sFile\s +(?P<lnk> + \" + (?P<pth> [^\"]+ ) + \",\sline\s + (?P<ln> \d+ ) +),""" + +# python 'test.sh: line 5:' +REGEXP_BASH = r""" +^(?P<lnk> + (?P<pth> .* ) + \:\sline\s + (?P<ln> \d+ ) +)\:""" + +# valac 'Test.vala:13.1-13.3: ...' +REGEXP_VALAC = r""" +^(?P<lnk> + (?P<pth> + .*vala + ) + \: + (?P<ln> + \d+ + ) + \.\d+-\d+\.\d+ + )\: """ + +#ruby +#test.rb:5: ... +# from test.rb:3:in `each' +# fist line parsed by REGEXP_STANDARD +REGEXP_RUBY = r""" +^\s+from\s +(?P<lnk> + (?P<pth> + .* + ) + \: + (?P<ln> + \d+ + ) + )""" + +# perl 'syntax error at test.pl line 88, near "$fake_var' +REGEXP_PERL = r""" +\sat\s +(?P<lnk> + (?P<pth> .* ) + \sline\s + (?P<ln> \d+ ) +)""" + +# mcs (C#) 'Test.cs(12,7): error CS0103: The name `fakeMethod' +REGEXP_MCS = r""" +^ +(?P<lnk> + (?P<pth> .*\.[cC][sS] ) + \( + (?P<ln> \d+ ) + ,\d+\) +) +\:\s +""" + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/manager.py b/plugins/externaltools/tools/manager.py new file mode 100755 index 00000000..e28a088a --- /dev/null +++ b/plugins/externaltools/tools/manager.py @@ -0,0 +1,948 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('Manager', ) + +import gedit +import gtk +import gtksourceview2 as gsv +import os.path +from library import * +from functions import * +import hashlib +from xml.sax import saxutils +import gobject + +class LanguagesPopup(gtk.Window): + COLUMN_NAME = 0 + COLUMN_ID = 1 + COLUMN_ENABLED = 2 + + def __init__(self, languages): + gtk.Window.__init__(self, gtk.WINDOW_POPUP) + + self.set_default_size(200, 200) + self.props.can_focus = True + + self.build() + self.init_languages(languages) + + self.show() + self.map() + + self.grab_add() + + gtk.gdk.keyboard_grab(self.window, False, 0L) + gtk.gdk.pointer_grab(self.window, False, gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.BUTTON_RELEASE_MASK | + gtk.gdk.POINTER_MOTION_MASK | + gtk.gdk.ENTER_NOTIFY_MASK | + gtk.gdk.LEAVE_NOTIFY_MASK | + gtk.gdk.PROXIMITY_IN_MASK | + gtk.gdk.PROXIMITY_OUT_MASK, None, None, 0L) + + self.view.get_selection().select_path((0,)) + + def build(self): + self.model = gtk.ListStore(str, str, bool) + + self.sw = gtk.ScrolledWindow() + self.sw.show() + + self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) + + self.view = gtk.TreeView(self.model) + self.view.show() + + self.view.set_headers_visible(False) + + column = gtk.TreeViewColumn() + + renderer = gtk.CellRendererToggle() + column.pack_start(renderer, False) + column.set_attributes(renderer, active=self.COLUMN_ENABLED) + + renderer.connect('toggled', self.on_language_toggled) + + renderer = gtk.CellRendererText() + column.pack_start(renderer, True) + column.set_attributes(renderer, text=self.COLUMN_NAME) + + self.view.append_column(column) + self.view.set_row_separator_func(self.on_separator) + + self.sw.add(self.view) + + self.add(self.sw) + + def enabled_languages(self, model, path, piter, ret): + enabled = model.get_value(piter, self.COLUMN_ENABLED) + + if path == (0,) and enabled: + return True + + if enabled: + ret.append(model.get_value(piter, self.COLUMN_ID)) + + return False + + def languages(self): + ret = [] + + self.model.foreach(self.enabled_languages, ret) + return ret + + def on_separator(self, model, piter): + val = model.get_value(piter, self.COLUMN_NAME) + return val == '-' + + def init_languages(self, languages): + manager = gsv.LanguageManager() + langs = gedit.language_manager_list_languages_sorted(manager, True) + + self.model.append([_('All languages'), None, not languages]) + self.model.append(['-', None, False]) + self.model.append([_('Plain Text'), 'plain', 'plain' in languages]) + self.model.append(['-', None, False]) + + for lang in langs: + self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages]) + + def correct_all(self, model, path, piter, enabled): + if path == (0,): + return False + + model.set_value(piter, self.COLUMN_ENABLED, enabled) + + def on_language_toggled(self, renderer, path): + piter = self.model.get_iter(path) + + enabled = self.model.get_value(piter, self.COLUMN_ENABLED) + self.model.set_value(piter, self.COLUMN_ENABLED, not enabled) + + if path == '0': + self.model.foreach(self.correct_all, False) + else: + self.model.set_value(self.model.get_iter_first(), self.COLUMN_ENABLED, False) + + def do_key_press_event(self, event): + if event.keyval == gtk.keysyms.Escape: + self.destroy() + return True + else: + event.window = self.view.get_bin_window() + return self.view.event(event) + + def do_key_release_event(self, event): + event.window = self.view.get_bin_window() + return self.view.event(event) + + def in_window(self, event, window=None): + if not window: + window = self.window + + geometry = window.get_geometry() + origin = window.get_origin() + + return event.x_root >= origin[0] and \ + event.x_root <= origin[0] + geometry[2] and \ + event.y_root >= origin[1] and \ + event.y_root <= origin[1] + geometry[3] + + def do_destroy(self): + gtk.gdk.keyboard_ungrab(0L) + gtk.gdk.pointer_ungrab(0L) + + return gtk.Window.do_destroy(self) + + def setup_event(self, event, window): + fr = event.window.get_origin() + to = window.get_origin() + + event.window = window + event.x += fr[0] - to[0] + event.y += fr[1] - to[1] + + def resolve_widgets(self, root): + res = [root] + + if isinstance(root, gtk.Container): + root.forall(lambda x, y: res.extend(self.resolve_widgets(x)), None) + + return res + + def resolve_windows(self, window): + if not window: + return [] + + res = [window] + res.extend(window.get_children()) + + return res + + def propagate_mouse_event(self, event): + allwidgets = self.resolve_widgets(self.get_child()) + allwidgets.reverse() + + orig = [event.x, event.y] + + for widget in allwidgets: + windows = self.resolve_windows(widget.window) + windows.reverse() + + for window in windows: + if not (window.get_events() & event.type): + continue + + if self.in_window(event, window): + self.setup_event(event, window) + + if widget.event(event): + return True + + return False + + def do_button_press_event(self, event): + if not self.in_window(event): + self.destroy() + else: + return self.propagate_mouse_event(event) + + def do_button_release_event(self, event): + if not self.in_window(event): + self.destroy() + else: + return self.propagate_mouse_event(event) + + def do_scroll_event(self, event): + return self.propagate_mouse_event(event) + + def do_motion_notify_event(self, event): + return self.propagate_mouse_event(event) + + def do_enter_notify_event(self, event): + return self.propagate_mouse_event(event) + + def do_leave_notify_event(self, event): + return self.propagate_mouse_event(event) + + def do_proximity_in_event(self, event): + return self.propagate_mouse_event(event) + + def do_proximity_out_event(self, event): + return self.propagate_mouse_event(event) + +gobject.type_register(LanguagesPopup) + +class Manager: + TOOL_COLUMN = 0 # For Tree + NAME_COLUMN = 1 # For Combo + + def __init__(self, datadir): + self.datadir = datadir + self.dialog = None + self._languages = {} + self._tool_rows = {} + + self.build() + + def build(self): + callbacks = { + 'on_new_tool_button_clicked' : self.on_new_tool_button_clicked, + 'on_remove_tool_button_clicked' : self.on_remove_tool_button_clicked, + 'on_tool_manager_dialog_response' : self.on_tool_manager_dialog_response, + 'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out, + 'on_accelerator_key_press' : self.on_accelerator_key_press, + 'on_accelerator_focus_in' : self.on_accelerator_focus_in, + 'on_accelerator_focus_out' : self.on_accelerator_focus_out, + 'on_languages_button_clicked' : self.on_languages_button_clicked + } + + # Load the "main-window" widget from the ui file. + self.ui = gtk.Builder() + self.ui.add_from_file(os.path.join(self.datadir, 'ui', 'tools.ui')) + self.ui.connect_signals(callbacks) + self.dialog = self.ui.get_object('tool-manager-dialog') + + self.view = self.ui.get_object('view') + + self.__init_tools_model() + self.__init_tools_view() + + for name in ['input', 'output', 'applicability', 'save-files']: + self.__init_combobox(name) + + self.do_update() + + def expand_from_doc(self, doc): + row = None + + if doc: + if doc.get_language(): + lid = doc.get_language().get_id() + + if lid in self._languages: + row = self._languages[lid] + elif 'plain' in self._languages: + row = self._languages['plain'] + + if not row and None in self._languages: + row = self._languages[None] + + if not row: + return + + self.view.expand_row(row.get_path(), False) + self.view.get_selection().select_path(row.get_path()) + + def run(self, window): + if self.dialog == None: + self.build() + + # Open up language + self.expand_from_doc(window.get_active_document()) + + self.dialog.set_transient_for(window) + window.get_group().add_window(self.dialog) + self.dialog.present() + + def add_accelerator(self, item): + if not item.shortcut: + return + + if item.shortcut in self.accelerators: + if not item in self.accelerators[item.shortcut]: + self.accelerators[item.shortcut].append(item) + else: + self.accelerators[item.shortcut] = [item] + + def remove_accelerator(self, item, shortcut=None): + if not shortcut: + shortcut = item.shortcut + + if not shortcut in self.accelerators: + return + + self.accelerators[shortcut].remove(item) + + if not self.accelerators[shortcut]: + del self.accelerators[shortcut] + + def add_tool_to_language(self, tool, language): + if isinstance(language, gsv.Language): + lid = language.get_id() + else: + lid = language + + if not lid in self._languages: + piter = self.model.append(None, [language]) + + parent = gtk.TreeRowReference(self.model, self.model.get_path(piter)) + self._languages[lid] = parent + else: + parent = self._languages[lid] + + piter = self.model.get_iter(parent.get_path()) + child = self.model.append(piter, [tool]) + + if not tool in self._tool_rows: + self._tool_rows[tool] = [] + + self._tool_rows[tool].append(gtk.TreeRowReference(self.model, self.model.get_path(child))) + return child + + def add_tool(self, tool): + manager = gsv.LanguageManager() + ret = None + + for lang in tool.languages: + l = manager.get_language(lang) + + if l: + ret = self.add_tool_to_language(tool, l) + elif lang == 'plain': + ret = self.add_tool_to_language(tool, 'plain') + + if not ret: + ret = self.add_tool_to_language(tool, None) + + self.add_accelerator(tool) + return ret + + def __init_tools_model(self): + self.tools = ToolLibrary() + self.current_node = None + self.script_hash = None + self.accelerators = dict() + + self.model = gtk.TreeStore(object) + self.view.set_model(self.model) + + for tool in self.tools.tree.tools: + self.add_tool(tool) + + self.model.set_default_sort_func(self.sort_tools) + self.model.set_sort_column_id(-1, gtk.SORT_ASCENDING) + + def sort_tools(self, model, iter1, iter2): + # For languages, sort All before everything else, otherwise alphabetical + t1 = model.get_value(iter1, self.TOOL_COLUMN) + t2 = model.get_value(iter2, self.TOOL_COLUMN) + + if model.iter_parent(iter1) == None: + if t1 == None: + return -1 + + if t2 == None: + return 1 + + def lang_name(lang): + if isinstance(lang, gsv.Language): + return lang.get_name() + else: + return _('Plain Text') + + n1 = lang_name(t1) + n2 = lang_name(t2) + else: + n1 = t1.name + n2 = t2.name + + return cmp(n1.lower(), n2.lower()) + + def __init_tools_view(self): + # Tools column + column = gtk.TreeViewColumn('Tools') + renderer = gtk.CellRendererText() + column.pack_start(renderer, False) + renderer.set_property('editable', True) + self.view.append_column(column) + + column.set_cell_data_func(renderer, self.get_cell_data_cb) + + renderer.connect('edited', self.on_view_label_cell_edited) + renderer.connect('editing-started', self.on_view_label_cell_editing_started) + + self.selection_changed_id = self.view.get_selection().connect('changed', self.on_view_selection_changed, None) + + def __init_combobox(self, name): + combo = self[name] + combo.set_active(0) + + # Convenience function to get an object from its name + def __getitem__(self, key): + return self.ui.get_object(key) + + def set_active_by_name(self, combo_name, option_name): + combo = self[combo_name] + model = combo.get_model() + piter = model.get_iter_first() + while piter is not None: + if model.get_value(piter, self.NAME_COLUMN) == option_name: + combo.set_active_iter(piter) + return True + piter = model.iter_next(piter) + return False + + def get_selected_tool(self): + model, piter = self.view.get_selection().get_selected() + + if piter is not None: + tool = model.get_value(piter, self.TOOL_COLUMN) + + if not isinstance(tool, Tool): + tool = None + + return piter, tool + else: + return None, None + + def compute_hash(self, string): + return hashlib.md5(string).hexdigest() + + def save_current_tool(self): + if self.current_node is None: + return + + if self.current_node.filename is None: + self.current_node.autoset_filename() + + def combo_value(o, name): + combo = o[name] + return combo.get_model().get_value(combo.get_active_iter(), self.NAME_COLUMN) + + self.current_node.input = combo_value(self, 'input') + self.current_node.output = combo_value(self, 'output') + self.current_node.applicability = combo_value(self, 'applicability') + self.current_node.save_files = combo_value(self, 'save-files') + + buf = self['commands'].get_buffer() + script = buf.get_text(*buf.get_bounds()) + h = self.compute_hash(script) + if h != self.script_hash: + # script has changed -> save it + self.current_node.save_with_script([line + "\n" for line in script.splitlines()]) + self.script_hash = h + else: + self.current_node.save() + + self.update_remove_revert() + + def clear_fields(self): + self['accelerator'].set_text('') + + buf = self['commands'].get_buffer() + buf.begin_not_undoable_action() + buf.set_text('') + buf.end_not_undoable_action() + + for nm in ('input', 'output', 'applicability', 'save-files'): + self[nm].set_active(0) + + self['languages_label'].set_text(_('All Languages')) + + def fill_languages_button(self): + if not self.current_node or not self.current_node.languages: + self['languages_label'].set_text(_('All Languages')) + else: + manager = gsv.LanguageManager() + langs = [] + + for lang in self.current_node.languages: + if lang == 'plain': + langs.append(_('Plain Text')) + else: + l = manager.get_language(lang) + + if l: + langs.append(l.get_name()) + + self['languages_label'].set_text(', '.join(langs)) + + def fill_fields(self): + node = self.current_node + self['accelerator'].set_text(default(node.shortcut, '')) + + buf = self['commands'].get_buffer() + script = default(''.join(node.get_script()), '') + + buf.begin_not_undoable_action() + buf.set_text(script) + buf.end_not_undoable_action() + + self.script_hash = self.compute_hash(script) + contenttype = gio.content_type_guess(data=script) + lmanager = gedit.get_language_manager() + language = lmanager.guess_language(content_type=contenttype) + + if language is not None: + buf.set_language(language) + buf.set_highlight_syntax(True) + else: + buf.set_highlight_syntax(False) + + for nm in ('input', 'output', 'applicability', 'save-files'): + model = self[nm].get_model() + piter = model.get_iter_first() + + self.set_active_by_name(nm, + default(node.__getattribute__(nm.replace('-', '_')), + model.get_value(piter, self.NAME_COLUMN))) + + self.fill_languages_button() + + def update_remove_revert(self): + piter, node = self.get_selected_tool() + + removable = node is not None and node.is_local() + + self['remove-tool-button'].set_sensitive(removable) + self['revert-tool-button'].set_sensitive(removable) + + if node is not None and node.is_global(): + self['remove-tool-button'].hide() + self['revert-tool-button'].show() + else: + self['remove-tool-button'].show() + self['revert-tool-button'].hide() + + def do_update(self): + self.update_remove_revert() + + piter, node = self.get_selected_tool() + self.current_node = node + + if node is not None: + self.fill_fields() + self['tool-table'].set_sensitive(True) + else: + self.clear_fields() + self['tool-table'].set_sensitive(False) + + def language_id_from_iter(self, piter): + if not piter: + return None + + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + if isinstance(tool, Tool): + piter = self.model.iter_parent(piter) + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + if isinstance(tool, gsv.Language): + return tool.get_id() + elif tool: + return 'plain' + + return None + + def selected_language_id(self): + # Find current language if there is any + model, piter = self.view.get_selection().get_selected() + + return self.language_id_from_iter(piter) + + def on_new_tool_button_clicked(self, button): + self.save_current_tool() + + # block handlers while inserting a new item + self.view.get_selection().handler_block(self.selection_changed_id) + + self.current_node = Tool(self.tools.tree); + self.current_node.name = _('New tool') + self.tools.tree.tools.append(self.current_node) + + lang = self.selected_language_id() + + if lang: + self.current_node.languages = [lang] + + piter = self.add_tool(self.current_node) + + self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), True) + self.fill_fields() + + self['tool-table'].set_sensitive(True) + self.view.get_selection().handler_unblock(self.selection_changed_id) + + def tool_changed(self, tool, refresh=False): + for row in self._tool_rows[tool]: + self.model.row_changed(row.get_path(), self.model.get_iter(row.get_path())) + + if refresh and tool == self.current_node: + self.fill_fields() + + self.update_remove_revert() + + def on_remove_tool_button_clicked(self, button): + piter, node = self.get_selected_tool() + + if not node: + return + + if node.is_global(): + shortcut = node.shortcut + + if node.parent.revert_tool(node): + self.remove_accelerator(node, shortcut) + self.add_accelerator(node) + + self['revert-tool-button'].set_sensitive(False) + self.fill_fields() + + self.tool_changed(node) + else: + parent = self.model.iter_parent(piter) + language = self.language_id_from_iter(parent) + + self.model.remove(piter) + + if language in node.languages: + node.languages.remove(language) + + self._tool_rows[node] = filter(lambda x: x.valid(), self._tool_rows[node]) + + if not self._tool_rows[node]: + del self._tool_rows[node] + + if node.parent.delete_tool(node): + self.remove_accelerator(node) + self.current_node = None + self.script_hash = None + + if self.model.iter_is_valid(piter): + self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), False) + + self.view.grab_focus() + + path = self._languages[language].get_path() + parent = self.model.get_iter(path) + + if not self.model.iter_has_child(parent): + self.model.remove(parent) + del self._languages[language] + + def on_view_label_cell_edited(self, cell, path, new_text): + if new_text != '': + piter = self.model.get_iter(path) + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + tool.name = new_text + + self.save_current_tool() + self.tool_changed(tool) + + def on_view_label_cell_editing_started(self, renderer, editable, path): + piter = self.model.get_iter(path) + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + if isinstance(editable, gtk.Entry): + editable.set_text(tool.name) + editable.grab_focus() + + def on_view_selection_changed(self, selection, userdata): + self.save_current_tool() + self.do_update() + + def accelerator_collision(self, name, node): + if not name in self.accelerators: + return [] + + ret = [] + + for other in self.accelerators[name]: + if not other.languages or not node.languages: + ret.append(other) + continue + + for lang in other.languages: + if lang in node.languages: + ret.append(other) + continue + + return ret + + def set_accelerator(self, keyval, mod): + # Check whether accelerator already exists + self.remove_accelerator(self.current_node) + + name = gtk.accelerator_name(keyval, mod) + + if name == '': + self.current_node.shorcut = None + self.save_current_tool() + return True + + col = self.accelerator_collision(name, self.current_node) + + if col: + dialog = gtk.MessageDialog(self.dialog, + gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + _('This accelerator is already bound to %s') % (', '.join(map(lambda x: x.name, col)),)) + + dialog.run() + dialog.destroy() + + self.add_accelerator(self.current_node) + return False + + self.current_node.shortcut = name + self.add_accelerator(self.current_node) + self.save_current_tool() + + return True + + def on_accelerator_key_press(self, entry, event): + mask = event.state & gtk.accelerator_get_default_mod_mask() + + if event.keyval == gtk.keysyms.Escape: + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() + return True + elif event.keyval == gtk.keysyms.Delete \ + or event.keyval == gtk.keysyms.BackSpace: + entry.set_text('') + self.remove_accelerator(self.current_node) + self.current_node.shortcut = None + self['commands'].grab_focus() + return True + elif event.keyval in range(gtk.keysyms.F1, gtk.keysyms.F12 + 1): + # New accelerator + if self.set_accelerator(event.keyval, mask): + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() + + # Capture all `normal characters` + return True + elif gtk.gdk.keyval_to_unicode(event.keyval): + if mask: + # New accelerator + if self.set_accelerator(event.keyval, mask): + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() + # Capture all `normal characters` + return True + else: + return False + + def on_accelerator_focus_in(self, entry, event): + if self.current_node is None: + return + if self.current_node.shortcut: + entry.set_text(_('Type a new accelerator, or press Backspace to clear')) + else: + entry.set_text(_('Type a new accelerator')) + + def on_accelerator_focus_out(self, entry, event): + if self.current_node is not None: + entry.set_text(default(self.current_node.shortcut, '')) + self.tool_changed(self.current_node) + + def on_tool_manager_dialog_response(self, dialog, response): + if response == gtk.RESPONSE_HELP: + gedit.help_display(self.dialog, 'gedit', 'gedit-external-tools-plugin') + return + + self.on_tool_manager_dialog_focus_out(dialog, None) + + self.dialog.destroy() + self.dialog = None + self.tools = None + + def on_tool_manager_dialog_focus_out(self, dialog, event): + self.save_current_tool() + + for window in gedit.app_get_default().get_windows(): + helper = window.get_data("ExternalToolsPluginWindowData") + helper.menu.update() + + def get_cell_data_cb(self, column, cell, model, piter): + tool = model.get_value(piter, self.TOOL_COLUMN) + + if tool == None or not isinstance(tool, Tool): + if tool == None: + label = _('All Languages') + elif not isinstance(tool, gsv.Language): + label = _('Plain Text') + else: + label = tool.get_name() + + markup = saxutils.escape(label) + editable = False + else: + escaped = saxutils.escape(tool.name) + + if tool.shortcut: + markup = '%s (<b>%s</b>)' % (escaped, saxutils.escape(tool.shortcut)) + else: + markup = escaped + + editable = True + + cell.set_properties(markup=markup, editable=editable) + + def tool_in_language(self, tool, lang): + if not lang in self._languages: + return False + + ref = self._languages[lang] + parent = ref.get_path() + + for row in self._tool_rows[tool]: + path = row.get_path() + + if path[0] == parent[0]: + return True + + return False + + def update_languages(self, popup): + self.current_node.languages = popup.languages() + self.fill_languages_button() + + piter, node = self.get_selected_tool() + ret = None + + if node: + ref = gtk.TreeRowReference(self.model, self.model.get_path(piter)) + + # Update languages, make sure to inhibit selection change stuff + self.view.get_selection().handler_block(self.selection_changed_id) + + # Remove all rows that are no longer + for row in list(self._tool_rows[self.current_node]): + piter = self.model.get_iter(row.get_path()) + language = self.language_id_from_iter(piter) + + if (not language and not self.current_node.languages) or \ + (language in self.current_node.languages): + continue + + # Remove from language + self.model.remove(piter) + self._tool_rows[self.current_node].remove(row) + + # If language is empty, remove it + parent = self.model.get_iter(self._languages[language].get_path()) + + if not self.model.iter_has_child(parent): + self.model.remove(parent) + del self._languages[language] + + # Now, add for any that are new + manager = gsv.LanguageManager() + + for lang in self.current_node.languages: + if not self.tool_in_language(self.current_node, lang): + l = manager.get_language(lang) + + if not l: + l = 'plain' + + self.add_tool_to_language(self.current_node, l) + + if not self.current_node.languages and not self.tool_in_language(self.current_node, None): + self.add_tool_to_language(self.current_node, None) + + # Check if we can still keep the current + if not ref or not ref.valid(): + # Change selection to first language + path = self._tool_rows[self.current_node][0].get_path() + piter = self.model.get_iter(path) + parent = self.model.iter_parent(piter) + + # Expand parent, select child and scroll to it + self.view.expand_row(self.model.get_path(parent), False) + self.view.get_selection().select_path(path) + self.view.set_cursor(path, self.view.get_column(self.TOOL_COLUMN), False) + + self.view.get_selection().handler_unblock(self.selection_changed_id) + + def on_languages_button_clicked(self, button): + popup = LanguagesPopup(self.current_node.languages) + popup.set_transient_for(self.dialog) + + origin = button.window.get_origin() + popup.move(origin[0], origin[1] - popup.allocation.height) + + popup.connect('destroy', self.update_languages) + +# ex:et:ts=4: diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py new file mode 100755 index 00000000..a30aad72 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <[email protected]> +# Copyright (C) 2010 Per Arneng <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('OutputPanel', 'UniqueById') + +import gtk, gedit +import pango +import gobject +import os +from weakref import WeakKeyDictionary +from capture import * +from gtk import gdk +import re +import gio +import linkparsing +import filelookup + +class UniqueById: + __shared_state = WeakKeyDictionary() + + def __init__(self, i): + if i in self.__class__.__shared_state: + self.__dict__ = self.__class__.__shared_state[i] + return True + else: + self.__class__.__shared_state[i] = self.__dict__ + return False + + def states(self): + return self.__class__.__shared_state + +class OutputPanel(UniqueById): + def __init__(self, datadir, window): + if UniqueById.__init__(self, window): + return + + callbacks = { + 'on_stop_clicked' : self.on_stop_clicked, + 'on_view_visibility_notify_event': self.on_view_visibility_notify_event, + 'on_view_motion_notify_event': self.on_view_motion_notify_event, + 'on_view_button_press_event': self.on_view_button_press_event + } + + self.window = window + self.ui = gtk.Builder() + self.ui.add_from_file(os.path.join(datadir, 'ui', 'outputpanel.ui')) + self.ui.connect_signals(callbacks) + + self.panel = self["output-panel"] + self['view'].modify_font(pango.FontDescription('Monospace')) + + buffer = self['view'].get_buffer() + + self.normal_tag = buffer.create_tag('normal') + + self.error_tag = buffer.create_tag('error') + self.error_tag.set_property('foreground', 'red') + + self.italic_tag = buffer.create_tag('italic') + self.italic_tag.set_property('style', pango.STYLE_OBLIQUE) + + self.bold_tag = buffer.create_tag('bold') + self.bold_tag.set_property('weight', pango.WEIGHT_BOLD) + + self.invalid_link_tag = buffer.create_tag('invalid_link') + + self.link_tag = buffer.create_tag('link') + self.link_tag.set_property('underline', pango.UNDERLINE_SINGLE) + + self.link_cursor = gdk.Cursor(gdk.HAND2) + self.normal_cursor = gdk.Cursor(gdk.XTERM) + + self.process = None + + self.links = [] + + self.link_parser = linkparsing.LinkParser() + self.file_lookup = filelookup.FileLookup() + + def set_process(self, process): + self.process = process + + def __getitem__(self, key): + # Convenience function to get an object from its name + return self.ui.get_object(key) + + def on_stop_clicked(self, widget, *args): + if self.process is not None: + self.write("\n" + _('Stopped.') + "\n", + self.italic_tag) + self.process.stop(-1) + + def scroll_to_end(self): + iter = self['view'].get_buffer().get_end_iter() + self['view'].scroll_to_iter(iter, 0.0) + return False # don't requeue this handler + + def clear(self): + self['view'].get_buffer().set_text("") + self.links = [] + + def visible(self): + panel = self.window.get_bottom_panel() + return panel.props.visible and panel.item_is_active(self.panel) + + def write(self, text, tag = None): + buffer = self['view'].get_buffer() + + end_iter = buffer.get_end_iter() + insert = buffer.create_mark(None, end_iter, True) + + if tag is None: + buffer.insert(end_iter, text) + else: + buffer.insert_with_tags(end_iter, text, tag) + + # find all links and apply the appropriate tag for them + links = self.link_parser.parse(text) + for lnk in links: + + insert_iter = buffer.get_iter_at_mark(insert) + lnk.start = insert_iter.get_offset() + lnk.start + lnk.end = insert_iter.get_offset() + lnk.end + + start_iter = buffer.get_iter_at_offset(lnk.start) + end_iter = buffer.get_iter_at_offset(lnk.end) + + tag = None + + # if the link points to an existing file then it is a valid link + if self.file_lookup.lookup(lnk.path) is not None: + self.links.append(lnk) + tag = self.link_tag + else: + tag = self.invalid_link_tag + + buffer.apply_tag(tag, start_iter, end_iter) + + buffer.delete_mark(insert) + gobject.idle_add(self.scroll_to_end) + + def show(self): + panel = self.window.get_bottom_panel() + panel.show() + panel.activate_item(self.panel) + + def update_cursor_style(self, view, x, y): + if self.get_link_at_location(view, x, y) is not None: + cursor = self.link_cursor + else: + cursor = self.normal_cursor + + view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) + + def on_view_motion_notify_event(self, view, event): + if event.window == view.get_window(gtk.TEXT_WINDOW_TEXT): + self.update_cursor_style(view, int(event.x), int(event.y)) + + return False + + def on_view_visibility_notify_event(self, view, event): + if event.window == view.get_window(gtk.TEXT_WINDOW_TEXT): + x, y, m = event.window.get_pointer() + self.update_cursor_style(view, x, y) + + return False + + def idle_grab_focus(self): + self.window.get_active_view().grab_focus() + return False + + def get_link_at_location(self, view, x, y): + """ + Get the link under a specified x,y coordinate. If no link exists then + None is returned. + """ + + # get the offset within the buffer from the x,y coordinates + buff_x, buff_y = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + x, y) + iter_at_xy = view.get_iter_at_location(buff_x, buff_y) + offset = iter_at_xy.get_offset() + + # find the first link that contains the offset + for lnk in self.links: + if offset >= lnk.start and offset <= lnk.end: + return lnk + + # no link was found at x,y + return None + + def on_view_button_press_event(self, view, event): + if event.button != 1 or event.type != gdk.BUTTON_PRESS or \ + event.window != view.get_window(gtk.TEXT_WINDOW_TEXT): + return False + + link = self.get_link_at_location(view, int(event.x), int(event.y)) + if link is None: + return False + + gfile = self.file_lookup.lookup(link.path) + + if gfile: + gedit.commands.load_uri(self.window, gfile.get_uri(), None, + link.line_nr) + gobject.idle_add(self.idle_grab_focus) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/outputpanel.ui b/plugins/externaltools/tools/outputpanel.ui new file mode 100755 index 00000000..f0281792 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.ui @@ -0,0 +1,53 @@ +<?xml version="1.0"?> +<!-- Generated with glade3 + Version: 2.91.3 + Date: Sat Nov 18 13:58:59 2006 + User: sf + Host: antea +--> +<interface> + <object class="GtkHBox" id="output-panel"> + <property name="visible">True</property> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <object class="GtkTextView" id="view"> + <property name="visible">True</property> + <property name="editable">False</property> + <property name="wrap_mode">GTK_WRAP_WORD</property> + <property name="cursor_visible">False</property> + <property name="accepts_tab">False</property> + <signal name="button_press_event" handler="on_view_button_press_event"/> + <signal name="motion_notify_event" handler="on_view_motion_notify_event"/> + <signal name="visibility_notify_event" handler="on_view_visibility_notify_event"/> + </object> + </child> + </object> + </child> + <child> + <object class="GtkVButtonBox" id="vbuttonbox1"> + <property name="visible">True</property> + <property name="border_width">6</property> + <property name="spacing">6</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <object class="GtkButton" id="stop"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="label">gtk-stop</property> + <property name="use_stock">True</property> + <signal handler="on_stop_clicked" name="clicked"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + </object> +</interface> diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui new file mode 100755 index 00000000..dff7d192 --- /dev/null +++ b/plugins/externaltools/tools/tools.ui @@ -0,0 +1,606 @@ +<?xml version="1.0"?> +<interface> + <object class="GtkListStore" id="model_save_files"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Nothing</col> + <col id="1">nothing</col> + </row> + <row> + <col id="0" translatable="yes">Current document</col> + <col id="1">document</col> + </row> + <row> + <col id="0" translatable="yes">All documents</col> + <col id="1">all</col> + </row> + </data> + </object> + <object class="GtkListStore" id="model_input"> + <columns> + <column type="gchararray"/> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Nothing</col> + <col id="1">nothing</col> + </row> + <row> + <col id="0" translatable="yes">Current document</col> + <col id="1">document</col> + </row> + <row> + <col id="0" translatable="yes">Current selection</col> + <col id="1">selection</col> + </row> + <row> + <col id="0" translatable="yes">Current selection (default to document)</col> + <col id="1">selection-document</col> + </row> + <row> + <col id="0" translatable="yes">Current line</col> + <col id="1">line</col> + </row> + <row> + <col id="0" translatable="yes">Current word</col> + <col id="1">word</col> + </row> + </data> + </object> + <object class="GtkListStore" id="model_output"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + <!-- column-name gchararray1 --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Nothing</col> + <col id="1">nothing</col> + </row> + <row> + <col id="0" translatable="yes">Display in bottom pane</col> + <col id="1">output-panel</col> + </row> + <row> + <col id="0" translatable="yes">Create new document</col> + <col id="1">new-document</col> + </row> + <row> + <col id="0" translatable="yes">Append to current document</col> + <col id="1">append-document</col> + </row> + <row> + <col id="0" translatable="yes">Replace current document</col> + <col id="1">replace-document</col> + </row> + <row> + <col id="0" translatable="yes">Replace current selection</col> + <col id="1">replace-selection</col> + </row> + <row> + <col id="0" translatable="yes">Insert at cursor position</col> + <col id="1">insert</col> + </row> + </data> + </object> + <object class="GtkListStore" id="model_applicability"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + <!-- column-name gchararray1 --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">All documents</col> + <col id="1">all</col> + </row> + <row> + <col id="0" translatable="yes">All documents except untitled ones</col> + <col id="1">titled</col> + </row> + <row> + <col id="0" translatable="yes">Local files only</col> + <col id="1">local</col> + </row> + <row> + <col id="0" translatable="yes">Remote files only</col> + <col id="1">remote</col> + </row> + <row> + <col id="0" translatable="yes">Untitled documents only</col> + <col id="1">untitled</col> + </row> + </data> + </object> + <object class="GeditDocument" id="commands_buffer"> + <property name="highlight-matching-brackets">True</property> + </object> + <object class="GtkDialog" id="tool-manager-dialog"> + <property name="title" translatable="yes">External Tools Manager</property> + <property name="default_width">750</property> + <property name="default_height">500</property> + <property name="type_hint">dialog</property> + <property name="skip_taskbar_hint">True</property> + <property name="has_separator">False</property> + <signal name="focus_out_event" handler="on_tool_manager_dialog_focus_out"/> + <signal name="response" handler="on_tool_manager_dialog_response"/> + <child internal-child="vbox"> + <object class="GtkVBox" id="tool-manager-dialog-vbox"> + <property name="visible">True</property> + <child> + <object class="GtkHPaned" id="paned"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="border_width">6</property> + <property name="position">275</property> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label20"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Tools:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">view</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolled_window1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="view"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + <property name="reorderable">True</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton" id="new-tool-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <signal name="clicked" handler="on_new_tool_button_clicked"/> + <child> + <object class="GtkImage" id="new-tool-image"> + <property name="visible">True</property> + <property name="stock">gtk-new</property> + <property name="icon-size">4</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="revert-tool-button"> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <signal name="clicked" handler="on_remove_tool_button_clicked"/> + <child> + <object class="GtkImage" id="revert-tool-image"> + <property name="visible">True</property> + <property name="stock">gtk-revert-to-saved</property> + <property name="icon-size">4</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="remove-tool-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <signal name="clicked" handler="on_remove_tool_button_clicked"/> + <child> + <object class="GtkImage" id="remove-tool-image"> + <property name="visible">True</property> + <property name="stock">gtk-delete</property> + <property name="icon-size">4</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <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="resize">False</property> + <property name="shrink">False</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox5"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="title"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="label" translatable="yes">_Edit:</property> + <property name="mnemonic_widget">commands</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="GtkHBox" id="hbox7"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="label22"> + <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="GtkTable" id="tool-table"> + <property name="visible">True</property> + <property name="n_rows">6</property> + <property name="n_columns">2</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkEntry" id="accelerator"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <signal name="key_press_event" handler="on_accelerator_key_press"/> + <signal name="focus_out_event" handler="on_accelerator_focus_out"/> + <signal name="focus_in_event" handler="on_accelerator_focus_in"/> + </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_EXPAND | GTK_SHRINK | GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <child> + <object class="GtkComboBox" id="applicability"> + <property name="visible">True</property> + <property name="model">model_applicability</property> + <child> + <object class="GtkCellRendererText" id="applicability_renderer"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="position">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="languages_event_box"> + <property name="visible">True</property> + <property name="visible-window">True</property> + <child> + <object class="GtkButton" id="languages_button"> + <property name="visible">True</property> + <signal name="clicked" handler="on_languages_button_clicked"/> + <child> + <object class="GtkLabel" id="languages_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">All Languages</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="output"> + <property name="visible">True</property> + <property name="model">model_output</property> + <child> + <object class="GtkCellRendererText" id="output_renderer"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="input"> + <property name="visible">True</property> + <property name="model">model_input</property> + <child> + <object class="GtkCellRendererText" id="input_renderer"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="save-files"> + <property name="model">model_save_files</property> + <property name="visible">True</property> + <child> + <object class="GtkCellRendererText" id="renderer1"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </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_EXPAND | GTK_SHRINK | GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label23"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Applicability:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">applicability</property> + </object> + <packing> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Output:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">output</property> + </object> + <packing> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Input:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">input</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Save:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">save-files</property> + <property name="visible">True</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="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Shortcut Key:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">accelerator</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="GtkScrolledWindow" id="scrolledwindow1"> + <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">in</property> + <child> + <object class="GeditView" id="commands"> + <property name="buffer">commands_buffer</property> + <property name="visible">True</property> + <property name="auto-indent">True</property> + <property name="insert-spaces-instead-of-tabs">False</property> + <property name="smart-home-end">GTK_SOURCE_SMART_HOME_END_AFTER</property> + <property name="tab-width">2</property> + <property name="highlight-current-line">True</property> + <property name="show-right-margin">False</property> + <property name="show-line-numbers">True</property> + </object> + </child> + </object> + <packing> + <property name="right_attach">2</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="resize">True</property> + <property name="shrink">False</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="hbuttonbox1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button1"> + <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="button2"> + <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">button1</action-widget> + <action-widget response="-7">button2</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/filebrowser/Makefile.am b/plugins/filebrowser/Makefile.am new file mode 100755 index 00000000..22301d5b --- /dev/null +++ b/plugins/filebrowser/Makefile.am @@ -0,0 +1,104 @@ +# filebrowser + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +BUILT_SOURCES = \ + gedit-file-browser-enum-types.h \ + gedit-file-browser-enum-types.c \ + gedit-file-browser-marshal.h \ + gedit-file-browser-marshal.c + +plugin_LTLIBRARIES = libfilebrowser.la + +NOINST_H_FILES = \ + gedit-file-bookmarks-store.h \ + gedit-file-browser-store.h \ + gedit-file-browser-view.h \ + gedit-file-browser-widget.h \ + gedit-file-browser-error.h \ + gedit-file-browser-utils.h \ + gedit-file-browser-plugin.h \ + gedit-file-browser-messages.h + +libfilebrowser_la_SOURCES = \ + $(BUILT_SOURCES) \ + gedit-file-bookmarks-store.c \ + gedit-file-browser-store.c \ + gedit-file-browser-view.c \ + gedit-file-browser-widget.c \ + gedit-file-browser-utils.c \ + gedit-file-browser-plugin.c \ + gedit-file-browser-messages.c \ + $(NOINST_H_FILES) + +libfilebrowser_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libfilebrowser_la_LIBADD = $(GEDIT_LIBS) + +# UI files (if you use ui for your plugin, list those files here) +uidir = $(GEDIT_PLUGINS_DATA_DIR)/filebrowser +ui_DATA = gedit-file-browser-widget-ui.xml + +plugin_in_files = filebrowser.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +gedit-file-browser-enum-types.h: gedit-file-browser-enum-types.h.template $(NOINST_H_FILES) $(GLIB_MKENUMS) + (cd $(srcdir) && $(GLIB_MKENUMS) --template gedit-file-browser-enum-types.h.template $(NOINST_H_FILES)) > $@ + +gedit-file-browser-enum-types.c: gedit-file-browser-enum-types.c.template gedit-file-browser-enum-register.c.template $(NOINST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN) (cd $(srcdir) && \ + $(GLIB_MKENUMS) --template gedit-file-browser-enum-types.c.template $(NOINST_H_FILES) && \ + $(GLIB_MKENUMS) --template gedit-file-browser-enum-register.c.template $(NOINST_H_FILES)) > $@ + +gedit-file-browser-marshal.h: gedit-file-browser-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=gedit_file_browser_marshal > $@ + +gedit-file-browser-marshal.c: gedit-file-browser-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) echo "#include \"gedit-file-browser-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=gedit_file_browser_marshal >> $@ + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +schemasdir = $(MATECONF_SCHEMA_FILE_DIR) +schemas_in_files = gedit-file-browser.schemas.in +schemas_DATA = $(schemas_in_files:.schemas.in=.schemas) +@INTLTOOL_SCHEMAS_RULE@ + +if MATECONF_SCHEMAS_INSTALL +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schemas_DATA) ; do \ + MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $(top_builddir)/plugins/filebrowser/$$p ; \ + done \ + fi +else +install-data-local: +endif + + +EXTRA_DIST = \ + $(ui_DATA) \ + $(plugin_in_files) \ + $(schemas_in_files) \ + gedit-file-browser-enum-types.h.template \ + gedit-file-browser-enum-types.c.template \ + gedit-file-browser-enum-register.c.template \ + gedit-file-browser-marshal.list + +CLEANFILES = \ + $(plugin_DATA) \ + $(schemas_DATA) \ + $(BUILT_SOURCES) + +DISTCLEANFILES = \ + $(plugin_DATA) \ + $(schemas_DATA) \ + $(BUILT_SOURCES) + +-include $(top_srcdir)/git.mk diff --git a/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in b/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in new file mode 100755 index 00000000..808816c5 --- /dev/null +++ b/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in @@ -0,0 +1,10 @@ +[Gedit Plugin] +Loader=C +Module=filebrowser +IAge=2 +_Name=File Browser Pane +_Description=Easy file access from the side pane +Icon=system-file-manager +Authors=Jesse van den Kieboom <[email protected]> +Copyright=Copyright © 2006 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.c b/plugins/filebrowser/gedit-file-bookmarks-store.c new file mode 100755 index 00000000..86e7f0c8 --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.c @@ -0,0 +1,879 @@ +/* + * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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 <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-plugin.h> + +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-utils.h" + +#define GEDIT_FILE_BOOKMARKS_STORE_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_FILE_BOOKMARKS_STORE, \ + GeditFileBookmarksStorePrivate)) + +struct _GeditFileBookmarksStorePrivate +{ + GVolumeMonitor * volume_monitor; + GFileMonitor * bookmarks_monitor; +}; + +static void remove_node (GtkTreeModel * model, + GtkTreeIter * iter); + +static void on_fs_changed (GVolumeMonitor *monitor, + GObject *object, + GeditFileBookmarksStore *model); + +static void on_bookmarks_file_changed (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + GeditFileBookmarksStore * model); +static gboolean find_with_flags (GtkTreeModel * model, + GtkTreeIter * iter, + gpointer obj, + guint flags, + guint notflags); + +GEDIT_PLUGIN_DEFINE_TYPE(GeditFileBookmarksStore, gedit_file_bookmarks_store, GTK_TYPE_TREE_STORE) + +static void +gedit_file_bookmarks_store_dispose (GObject * object) +{ + GeditFileBookmarksStore *obj = GEDIT_FILE_BOOKMARKS_STORE (object); + + if (obj->priv->volume_monitor != NULL) { + g_signal_handlers_disconnect_by_func (obj->priv->volume_monitor, + on_fs_changed, + obj); + + g_object_unref (obj->priv->volume_monitor); + obj->priv->volume_monitor = NULL; + } + + if (obj->priv->bookmarks_monitor != NULL) { + g_object_unref (obj->priv->bookmarks_monitor); + obj->priv->bookmarks_monitor = NULL; + } + + G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->dispose (object); +} + +static void +gedit_file_bookmarks_store_finalize (GObject * object) +{ + G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->finalize (object); +} + +static void +gedit_file_bookmarks_store_class_init (GeditFileBookmarksStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_file_bookmarks_store_dispose; + object_class->finalize = gedit_file_bookmarks_store_finalize; + + g_type_class_add_private (object_class, sizeof (GeditFileBookmarksStorePrivate)); +} + +static void +gedit_file_bookmarks_store_init (GeditFileBookmarksStore * obj) +{ + obj->priv = GEDIT_FILE_BOOKMARKS_STORE_GET_PRIVATE (obj); +} + +/* Private */ +static void +add_node (GeditFileBookmarksStore *model, + GdkPixbuf *pixbuf, + const gchar *name, + GObject *obj, + guint flags, + GtkTreeIter *iter) +{ + GtkTreeIter newiter; + + gtk_tree_store_append (GTK_TREE_STORE (model), &newiter, NULL); + + gtk_tree_store_set (GTK_TREE_STORE (model), &newiter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, pixbuf, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, name, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, obj, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, flags, + -1); + + if (iter != NULL) + *iter = newiter; +} + +static gboolean +add_file (GeditFileBookmarksStore *model, + GFile *file, + const gchar *name, + guint flags, + GtkTreeIter *iter) +{ + GdkPixbuf *pixbuf = NULL; + gboolean native; + gchar *newname; + + native = g_file_is_native (file); + + if (native && !g_file_query_exists (file, NULL)) { + return FALSE; + } + + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_HOME) + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("user-home", GTK_ICON_SIZE_MENU); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP) + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("user-desktop", GTK_ICON_SIZE_MENU); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT) + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("drive-harddisk", GTK_ICON_SIZE_MENU); + + if (pixbuf == NULL) { + /* getting the icon is a sync get_info call, so we just do it for local files */ + if (native) { + pixbuf = gedit_file_browser_utils_pixbuf_from_file (file, GTK_ICON_SIZE_MENU); + } else { + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("folder", GTK_ICON_SIZE_MENU); + } + } + + if (name == NULL) { + newname = gedit_file_browser_utils_file_basename (file); + } else { + newname = g_strdup (name); + } + + add_node (model, pixbuf, newname, G_OBJECT (file), flags, iter); + + if (pixbuf) + g_object_unref (pixbuf); + + g_free (newname); + + return TRUE; +} + +static void +check_mount_separator (GeditFileBookmarksStore * model, guint flags, + gboolean added) +{ + GtkTreeIter iter; + gboolean found; + + found = + find_with_flags (GTK_TREE_MODEL (model), &iter, NULL, + flags | + GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, 0); + + if (added && !found) { + /* Add the separator */ + add_node (model, NULL, NULL, NULL, + flags | GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, + NULL); + } else if (!added && found) { + remove_node (GTK_TREE_MODEL (model), &iter); + } +} + +static void +init_special_directories (GeditFileBookmarksStore * model) +{ + gchar const *path; + GFile * file; + + path = g_get_home_dir (); + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_HOME | + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL); + g_object_unref (file); + } + + path = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP | + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL); + g_object_unref (file); + } + + path = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS); + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS | + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL); + g_object_unref (file); + } + + file = g_file_new_for_uri ("file:///"); + add_file (model, file, _("File System"), GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, NULL); + g_object_unref (file); + + check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, TRUE); +} + +static void +get_fs_properties (gpointer fs, + gchar **name, + GdkPixbuf **pixbuf, + guint *flags) +{ + GIcon *icon = NULL; + + *flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS; + *name = NULL; + *pixbuf = NULL; + + if (G_IS_DRIVE (fs)) + { + icon = g_drive_get_icon (G_DRIVE (fs)); + *name = g_drive_get_name (G_DRIVE (fs)); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE; + } + else if (G_IS_VOLUME (fs)) + { + icon = g_volume_get_icon (G_VOLUME (fs)); + *name = g_volume_get_name (G_VOLUME (fs)); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME; + } + else if (G_IS_MOUNT (fs)) + { + icon = g_mount_get_icon (G_MOUNT (fs)); + *name = g_mount_get_name (G_MOUNT (fs)); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT; + } + + if (icon) + { + *pixbuf = gedit_file_browser_utils_pixbuf_from_icon (icon, GTK_ICON_SIZE_MENU); + g_object_unref (icon); + } +} + + +static void +add_fs (GeditFileBookmarksStore *model, + gpointer fs, + guint flags, + GtkTreeIter *iter) +{ + gchar *name; + GdkPixbuf *pixbuf; + guint fsflags; + + get_fs_properties (fs, &name, &pixbuf, &fsflags); + add_node (model, pixbuf, name, fs, flags | fsflags, iter); + + if (pixbuf) + g_object_unref (pixbuf); + + g_free (name); + check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_FS, TRUE); +} + +static void +process_volume_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GMount *mount; + guint flags = GEDIT_FILE_BOOKMARKS_STORE_NONE; + mount = g_volume_get_mount (volume); + + /* CHECK: should we use the LOCAL/REMOTE thing still? */ + if (mount) + { + /* Show mounted volume */ + add_fs (model, mount, flags, NULL); + g_object_unref (mount); + } + else if (g_volume_can_mount (volume)) + { + /* We also show the unmounted volume here so users can + mount it if they want to access it */ + add_fs (model, volume, flags, NULL); + } +} + +static void +process_drive_novolumes (GeditFileBookmarksStore *model, + GDrive *drive) +{ + if (g_drive_is_media_removable (drive) && + !g_drive_is_media_check_automatic (drive) && + g_drive_can_poll_for_media (drive)) + { + /* This can be the case for floppy drives or other + drives where media detection fails. We show the + drive and poll for media when the user activates + it */ + add_fs (model, drive, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL); + } +} + +static void +process_drive_cb (GDrive *drive, + GeditFileBookmarksStore *model) +{ + GList *volumes; + + volumes = g_drive_get_volumes (drive); + + if (volumes) + { + /* Add all volumes for the drive */ + g_list_foreach (volumes, (GFunc)process_volume_cb, model); + g_list_free (volumes); + } + else + { + process_drive_novolumes (model, drive); + } +} + +static void +init_drives (GeditFileBookmarksStore *model) +{ + GList *drives; + + drives = g_volume_monitor_get_connected_drives (model->priv->volume_monitor); + + g_list_foreach (drives, (GFunc)process_drive_cb, model); + g_list_foreach (drives, (GFunc)g_object_unref, NULL); + g_list_free (drives); +} + +static void +process_volume_nodrive_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GDrive *drive; + + drive = g_volume_get_drive (volume); + + if (drive) + { + g_object_unref (drive); + return; + } + + process_volume_cb (volume, model); +} + +static void +init_volumes (GeditFileBookmarksStore *model) +{ + GList *volumes; + + volumes = g_volume_monitor_get_volumes (model->priv->volume_monitor); + + g_list_foreach (volumes, (GFunc)process_volume_nodrive_cb, model); + g_list_foreach (volumes, (GFunc)g_object_unref, NULL); + g_list_free (volumes); +} + +static void +process_mount_novolume_cb (GMount *mount, + GeditFileBookmarksStore *model) +{ + GVolume *volume; + + volume = g_mount_get_volume (mount); + + if (volume) + { + g_object_unref (volume); + } + else if (!g_mount_is_shadowed (mount)) + { + /* Add the mount */ + add_fs (model, mount, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL); + } +} + +static void +init_mounts (GeditFileBookmarksStore *model) +{ + GList *mounts; + + mounts = g_volume_monitor_get_mounts (model->priv->volume_monitor); + + g_list_foreach (mounts, (GFunc)process_mount_novolume_cb, model); + g_list_foreach (mounts, (GFunc)g_object_unref, NULL); + g_list_free (mounts); +} + +static void +init_fs (GeditFileBookmarksStore * model) +{ + if (model->priv->volume_monitor == NULL) { + const gchar **ptr; + const gchar *signals[] = { + "drive-connected", "drive-changed", "drive-disconnected", + "volume-added", "volume-removed", "volume-changed", + "mount-added", "mount-removed", "mount-changed", + NULL + }; + + model->priv->volume_monitor = g_volume_monitor_get (); + + /* Connect signals */ + for (ptr = signals; *ptr; ptr++) + { + g_signal_connect (model->priv->volume_monitor, + *ptr, + G_CALLBACK (on_fs_changed), model); + } + } + + /* First go through all the connected drives */ + init_drives (model); + + /* Then add all volumes, not associated with a drive */ + init_volumes (model); + + /* Then finally add all mounts that have no volume */ + init_mounts (model); +} + +static gboolean +add_bookmark (GeditFileBookmarksStore * model, + gchar const * name, + gchar const * uri) +{ + GFile * file; + gboolean ret; + guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK; + GtkTreeIter iter; + + file = g_file_new_for_uri (uri); + + if (g_file_is_native (file)) { + flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK; + } else { + flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK; + } + + ret = add_file (model, file, name, flags, &iter); + + g_object_unref (file); + + return ret; +} + +static void +init_bookmarks (GeditFileBookmarksStore * model) +{ + gchar *bookmarks; + GError *error = NULL; + gchar *contents; + gchar **lines; + gchar **line; + gboolean added = FALSE; + + /* Read the bookmarks file */ + bookmarks = g_build_filename (g_get_home_dir (), + ".gtk-bookmarks", + NULL); + + if (g_file_get_contents (bookmarks, &contents, NULL, &error)) { + lines = g_strsplit (contents, "\n", 0); + + for (line = lines; *line; ++line) { + if (**line) { + gchar *pos; + gchar *name; + + /* CHECK: is this really utf8? */ + pos = g_utf8_strchr (*line, -1, ' '); + + if (pos != NULL) { + *pos = '\0'; + name = pos + 1; + } else { + name = NULL; + } + + /* the bookmarks file should contain valid + * URIs, but paranoia is good */ + if (gedit_utils_is_valid_uri (*line)) { + added |= add_bookmark (model, name, *line); + } + } + } + + g_strfreev (lines); + g_free (contents); + + /* Add a watch */ + if (model->priv->bookmarks_monitor == NULL) { + GFile * file; + + file = g_file_new_for_path (bookmarks); + model->priv->bookmarks_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + + g_signal_connect (model->priv->bookmarks_monitor, + "changed", + (GCallback)on_bookmarks_file_changed, + model); + } + } else { + /* The bookmarks file doesn't exist (which is perfectly fine) */ + g_error_free (error); + } + + if (added) { + /* Bookmarks separator */ + add_node (model, NULL, NULL, NULL, + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK | + GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, NULL); + } + + g_free (bookmarks); +} + +static gint flags_order[] = { + GEDIT_FILE_BOOKMARKS_STORE_IS_HOME, + GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP, + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, + GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, + GEDIT_FILE_BOOKMARKS_STORE_IS_FS, + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK, + -1 +}; + +static gint +utf8_casecmp (gchar const *s1, const gchar * s2) +{ + gchar *n1; + gchar *n2; + gint result; + + n1 = g_utf8_casefold (s1, -1); + n2 = g_utf8_casefold (s2, -1); + + result = g_utf8_collate (n1, n2); + + g_free (n1); + g_free (n2); + + return result; +} + +static gint +bookmarks_compare_names (GtkTreeModel * model, GtkTreeIter * a, + GtkTreeIter * b) +{ + gchar *n1; + gchar *n2; + gint result; + guint f1; + guint f2; + + gtk_tree_model_get (model, a, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n1, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1, + -1); + gtk_tree_model_get (model, b, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n2, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2, + -1); + + /* do not sort actual bookmarks to keep same order as in caja */ + if ((f1 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK) && + (f2 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK)) + result = 0; + else if (n1 == NULL && n2 == NULL) + result = 0; + else if (n1 == NULL) + result = -1; + else if (n2 == NULL) + result = 1; + else + result = utf8_casecmp (n1, n2); + + g_free (n1); + g_free (n2); + + return result; +} + +static gint +bookmarks_compare_flags (GtkTreeModel * model, GtkTreeIter * a, + GtkTreeIter * b) +{ + guint f1; + guint f2; + gint *flags; + guint sep; + + sep = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR; + + gtk_tree_model_get (model, a, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1, + -1); + gtk_tree_model_get (model, b, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2, + -1); + + for (flags = flags_order; *flags != -1; ++flags) { + if ((f1 & *flags) != (f2 & *flags)) { + if (f1 & *flags) { + return -1; + } else { + return 1; + } + } else if ((f1 & *flags) && (f1 & sep) != (f2 & sep)) { + if (f1 & sep) + return -1; + else + return 1; + } + } + + return 0; +} + +static gint +bookmarks_compare_func (GtkTreeModel * model, GtkTreeIter * a, + GtkTreeIter * b, gpointer user_data) +{ + gint result; + + result = bookmarks_compare_flags (model, a, b); + + if (result == 0) + result = bookmarks_compare_names (model, a, b); + + return result; +} + +static gboolean +find_with_flags (GtkTreeModel * model, GtkTreeIter * iter, gpointer obj, + guint flags, guint notflags) +{ + GtkTreeIter child; + guint childflags = 0; + GObject * childobj; + gboolean fequal; + + if (!gtk_tree_model_get_iter_first (model, &child)) + return FALSE; + + do { + gtk_tree_model_get (model, &child, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &childobj, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &childflags, -1); + + fequal = (obj == childobj); + + if (childobj) + g_object_unref (childobj); + + if ((obj == NULL || fequal) && + (childflags & flags) == flags + && !(childflags & notflags)) { + *iter = child; + return TRUE; + } + } while (gtk_tree_model_iter_next (model, &child)); + + return FALSE; +} + +static void +remove_node (GtkTreeModel * model, GtkTreeIter * iter) +{ + guint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!(flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR)) { + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS) { + check_mount_separator (GEDIT_FILE_BOOKMARKS_STORE (model), + flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS, + FALSE); + } + } + + gtk_tree_store_remove (GTK_TREE_STORE (model), iter); +} + +static void +remove_bookmarks (GeditFileBookmarksStore * model) +{ + GtkTreeIter iter; + + while (find_with_flags (GTK_TREE_MODEL (model), &iter, NULL, + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK, + 0)) { + remove_node (GTK_TREE_MODEL (model), &iter); + } +} + +static void +initialize_fill (GeditFileBookmarksStore * model) +{ + init_special_directories (model); + init_fs (model); + init_bookmarks (model); +} + +/* Public */ +GeditFileBookmarksStore * +gedit_file_bookmarks_store_new (void) +{ + GeditFileBookmarksStore *model; + GType column_types[] = { + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_OBJECT, + G_TYPE_UINT + }; + + model = g_object_new (GEDIT_TYPE_FILE_BOOKMARKS_STORE, NULL); + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), + GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS, + column_types); + + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), + bookmarks_compare_func, + NULL, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); + + initialize_fill (model); + + return model; +} + +gchar * +gedit_file_bookmarks_store_get_uri (GeditFileBookmarksStore * model, + GtkTreeIter * iter) +{ + GObject * obj; + GFile * file = NULL; + guint flags; + gchar * ret = NULL; + gboolean isfs; + + g_return_val_if_fail (GEDIT_IS_FILE_BOOKMARKS_STORE (model), NULL); + g_return_val_if_fail (iter != NULL, NULL); + + gtk_tree_model_get (GTK_TREE_MODEL (model), iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &flags, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &obj, + -1); + + if (obj == NULL) + return NULL; + + isfs = (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS); + + if (isfs && (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT)) + { + file = g_mount_get_root (G_MOUNT (obj)); + } + else if (!isfs) + { + file = g_object_ref (obj); + } + + g_object_unref (obj); + + if (file) + { + ret = g_file_get_uri (file); + g_object_unref (file); + } + + return ret; +} + +void +gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore * model) +{ + gtk_tree_store_clear (GTK_TREE_STORE (model)); + initialize_fill (model); +} + +static void +on_fs_changed (GVolumeMonitor *monitor, + GObject *object, + GeditFileBookmarksStore *model) +{ + GtkTreeModel *tree_model = GTK_TREE_MODEL (model); + guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS; + guint noflags = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR; + GtkTreeIter iter; + + /* clear all fs items */ + while (find_with_flags (tree_model, &iter, NULL, flags, noflags)) + remove_node (tree_model, &iter); + + /* then reinitialize */ + init_fs (model); +} + +static void +on_bookmarks_file_changed (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + GeditFileBookmarksStore * model) +{ + switch (event_type) { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + /* Re-initialize bookmarks */ + remove_bookmarks (model); + init_bookmarks (model); + break; + case G_FILE_MONITOR_EVENT_DELETED: // FIXME: shouldn't we also monitor the directory? + /* Remove bookmarks */ + remove_bookmarks (model); + g_object_unref (monitor); + model->priv->bookmarks_monitor = NULL; + break; + default: + break; + } +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.h b/plugins/filebrowser/gedit-file-bookmarks-store.h new file mode 100755 index 00000000..bd20911e --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.h @@ -0,0 +1,90 @@ +/* + * gedit-file-bookmarks-store.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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_FILE_BOOKMARKS_STORE_H__ +#define __GEDIT_FILE_BOOKMARKS_STORE_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BOOKMARKS_STORE (gedit_file_bookmarks_store_get_type ()) +#define GEDIT_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore)) +#define GEDIT_FILE_BOOKMARKS_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore const)) +#define GEDIT_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass)) +#define GEDIT_IS_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE)) +#define GEDIT_IS_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE)) +#define GEDIT_FILE_BOOKMARKS_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass)) + +typedef struct _GeditFileBookmarksStore GeditFileBookmarksStore; +typedef struct _GeditFileBookmarksStoreClass GeditFileBookmarksStoreClass; +typedef struct _GeditFileBookmarksStorePrivate GeditFileBookmarksStorePrivate; + +enum +{ + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON = 0, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS +}; + +enum +{ + GEDIT_FILE_BOOKMARKS_STORE_NONE = 0, + GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR = 1 << 0, /* Separator item */ + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR = 1 << 1, /* Special user dir */ + GEDIT_FILE_BOOKMARKS_STORE_IS_HOME = 1 << 2, /* The special Home user directory */ + GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP = 1 << 3, /* The special Desktop user directory */ + GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS = 1 << 4, /* The special Documents user directory */ + GEDIT_FILE_BOOKMARKS_STORE_IS_FS = 1 << 5, /* A mount object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT = 1 << 6, /* A mount object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME = 1 << 7, /* A volume object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE = 1 << 8, /* A drive object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT = 1 << 9, /* The root file system (file:///) */ + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK = 1 << 10, /* A gtk bookmark */ + GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK = 1 << 11, /* A remote gtk bookmark */ + GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK = 1 << 12 /* A local gtk bookmark */ +}; + +struct _GeditFileBookmarksStore +{ + GtkTreeStore parent; + + GeditFileBookmarksStorePrivate *priv; +}; + +struct _GeditFileBookmarksStoreClass +{ + GtkTreeStoreClass parent_class; +}; + +GType gedit_file_bookmarks_store_get_type (void) G_GNUC_CONST; +GType gedit_file_bookmarks_store_register_type (GTypeModule * module); + +GeditFileBookmarksStore *gedit_file_bookmarks_store_new (void); +gchar *gedit_file_bookmarks_store_get_uri (GeditFileBookmarksStore * model, + GtkTreeIter * iter); +void gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore * model); + +G_END_DECLS +#endif /* __GEDIT_FILE_BOOKMARKS_STORE_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-enum-register.c.template b/plugins/filebrowser/gedit-file-browser-enum-register.c.template new file mode 100755 index 00000000..63a9c562 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-register.c.template @@ -0,0 +1,20 @@ +/*** BEGIN file-header ***/ +void +gedit_file_browser_enum_and_flag_register_type (GTypeModule * module) +{ +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + /* Enumerations from "@filename@" */ + +/*** END file-production ***/ + +/*** BEGIN enumeration-production ***/ + register_@enum_name@ (module); + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +} + +/*** END file-tail ***/ diff --git a/plugins/filebrowser/gedit-file-browser-enum-types.c.template b/plugins/filebrowser/gedit-file-browser-enum-types.c.template new file mode 100755 index 00000000..4e89370d --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types.c.template @@ -0,0 +1,45 @@ +/*** BEGIN file-header ***/ +#include "gedit-file-browser-enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +#include "@filename@" + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +static GType @enum_name@_type = 0; + +static GType +register_@enum_name@ (GTypeModule *module) +{ + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, + "@VALUENAME@", + "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + @enum_name@_type = + g_type_module_register_@type@ (module, + "@EnumName@", + values); + + return @enum_name@_type; +} + +GType +@enum_name@_get_type (void) +{ + return @enum_name@_type; +} + +/*** END value-tail ***/ diff --git a/plugins/filebrowser/gedit-file-browser-enum-types.h.template b/plugins/filebrowser/gedit-file-browser-enum-types.h.template new file mode 100755 index 00000000..aea4fad9 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types.h.template @@ -0,0 +1,29 @@ +/*** BEGIN file-header ***/ +#ifndef __GEDIT_FILE_BROWSER_ENUM_TYPES_H__ +#define __GEDIT_FILE_BROWSER_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 ***/ +void gedit_file_browser_enum_and_flag_register_type (GTypeModule * module); + +G_END_DECLS + +#endif /* __GEDIT_FILE_BROWSER_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + diff --git a/plugins/filebrowser/gedit-file-browser-error.h b/plugins/filebrowser/gedit-file-browser-error.h new file mode 100755 index 00000000..ec5b8618 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-error.h @@ -0,0 +1,41 @@ +/* + * gedit-file-browser-error.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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_FILE_BROWSER_ERROR_H__ +#define __GEDIT_FILE_BROWSER_ERROR_H__ + +G_BEGIN_DECLS + +typedef enum { + GEDIT_FILE_BROWSER_ERROR_NONE, + GEDIT_FILE_BROWSER_ERROR_RENAME, + GEDIT_FILE_BROWSER_ERROR_DELETE, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY, + GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + GEDIT_FILE_BROWSER_ERROR_NUM +} GeditFileBrowserError; + +G_END_DECLS + +#endif /* __GEDIT_FILE_BROWSER_ERROR_H__ */ diff --git a/plugins/filebrowser/gedit-file-browser-marshal.list b/plugins/filebrowser/gedit-file-browser-marshal.list new file mode 100755 index 00000000..5fa72c8b --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-marshal.list @@ -0,0 +1,5 @@ +VOID:UINT,STRING +VOID:STRING,STRING +BOOL:OBJECT,POINTER +BOOL:POINTER +BOOL:VOID diff --git a/plugins/filebrowser/gedit-file-browser-messages.c b/plugins/filebrowser/gedit-file-browser-messages.c new file mode 100755 index 00000000..b587edf1 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.c @@ -0,0 +1,1033 @@ +#include "gedit-file-browser-messages.h" +#include "gedit-file-browser-store.h" +#include <gedit/gedit-message.h> + +#define MESSAGE_OBJECT_PATH "/plugins/filebrowser" +#define WINDOW_DATA_KEY "GeditFileBrowserMessagesWindowData" + +#define BUS_CONNECT(bus, name, data) gedit_message_bus_connect(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data, NULL) + +typedef struct +{ + GeditWindow *window; + GeditMessage *message; +} MessageCacheData; + +typedef struct +{ + guint row_inserted_id; + guint row_deleted_id; + guint root_changed_id; + guint begin_loading_id; + guint end_loading_id; + + GList *merge_ids; + GtkActionGroup *merged_actions; + + GeditMessageBus *bus; + GeditFileBrowserWidget *widget; + GHashTable *row_tracking; + + GHashTable *filters; +} WindowData; + +typedef struct +{ + gulong id; + + GeditWindow *window; + GeditMessage *message; +} FilterData; + +static WindowData * +window_data_new (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + WindowData *data = g_slice_new (WindowData); + GtkUIManager *manager; + GList *groups; + + data->bus = gedit_window_get_message_bus (window); + data->widget = widget; + data->row_tracking = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)gtk_tree_row_reference_free); + + data->filters = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + NULL); + + manager = gedit_file_browser_widget_get_ui_manager (widget); + + data->merge_ids = NULL; + data->merged_actions = gtk_action_group_new ("MessageMergedActions"); + + groups = gtk_ui_manager_get_action_groups (manager); + gtk_ui_manager_insert_action_group (manager, data->merged_actions, g_list_length (groups)); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, data); + + return data; +} + +static WindowData * +get_window_data (GeditWindow * window) +{ + return (WindowData *) (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY)); +} + +static void +window_data_free (GeditWindow *window) +{ + WindowData *data = get_window_data (window); + GtkUIManager *manager; + GList *item; + + g_hash_table_destroy (data->row_tracking); + g_hash_table_destroy (data->filters); + + manager = gedit_file_browser_widget_get_ui_manager (data->widget); + gtk_ui_manager_remove_action_group (manager, data->merged_actions); + + for (item = data->merge_ids; item; item = item->next) + gtk_ui_manager_remove_ui (manager, GPOINTER_TO_INT (item->data)); + + g_list_free (data->merge_ids); + g_object_unref (data->merged_actions); + + g_slice_free (WindowData, data); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static FilterData * +filter_data_new (GeditWindow *window, + GeditMessage *message) +{ + FilterData *data = g_slice_new (FilterData); + WindowData *wdata; + + data->window = window; + data->id = 0; + data->message = message; + + wdata = get_window_data (window); + + g_hash_table_insert (wdata->filters, + gedit_message_type_identifier (gedit_message_get_object_path (message), + gedit_message_get_method (message)), + data); + + return data; +} + +static void +filter_data_free (FilterData *data) +{ + WindowData *wdata = get_window_data (data->window); + gchar *identifier; + + identifier = gedit_message_type_identifier (gedit_message_get_object_path (data->message), + gedit_message_get_method (data->message)); + + g_hash_table_remove (wdata->filters, identifier); + g_free (identifier); + + g_object_unref (data->message); + g_slice_free (FilterData, data); +} + +static GtkTreePath * +track_row_lookup (WindowData *data, + const gchar *id) +{ + GtkTreeRowReference *ref; + + ref = (GtkTreeRowReference *)g_hash_table_lookup (data->row_tracking, id); + + if (!ref) + return NULL; + + return gtk_tree_row_reference_get_path (ref); +} + +static void +message_cache_data_free (MessageCacheData *data) +{ + g_object_unref (data->message); + g_slice_free (MessageCacheData, data); +} + +static MessageCacheData * +message_cache_data_new (GeditWindow *window, + GeditMessage *message) +{ + MessageCacheData *data = g_slice_new (MessageCacheData); + + data->window = window; + data->message = message; + + return data; +} + +static void +message_get_root_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserStore *store; + gchar *uri; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + uri = gedit_file_browser_store_get_virtual_root (store); + + gedit_message_set (message, "uri", uri, NULL); + g_free (uri); +} + +static void +message_set_root_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *root = NULL; + gchar *virtual = NULL; + + gedit_message_get (message, "uri", &root, NULL); + + if (!root) + return; + + if (gedit_message_has_key (message, "virtual")) + gedit_message_get (message, "virtual", &virtual, NULL); + + if (virtual) + gedit_file_browser_widget_set_root_and_virtual_root (data->widget, root, virtual); + else + gedit_file_browser_widget_set_root (data->widget, root, TRUE); + + g_free (root); + g_free (virtual); +} + +static void +message_set_emblem_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *id = NULL; + gchar *emblem = NULL; + GtkTreePath *path; + GeditFileBrowserStore *store; + + gedit_message_get (message, "id", &id, "emblem", &emblem, NULL); + + if (!id || !emblem) + { + g_free (id); + g_free (emblem); + + return; + } + + path = track_row_lookup (data, id); + + if (path != NULL) + { + GError *error = NULL; + GdkPixbuf *pixbuf; + + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + emblem, + 10, + 0, + &error); + + if (pixbuf) + { + GValue value = { 0, }; + GtkTreeIter iter; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path)) + { + g_value_init (&value, GDK_TYPE_PIXBUF); + g_value_set_object (&value, pixbuf); + + gedit_file_browser_store_set_value (store, + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM, + &value); + + g_value_unset (&value); + } + + g_object_unref (pixbuf); + } + + if (error) + g_error_free (error); + } + + g_free (id); + g_free (emblem); +} + +static gchar * +item_id (const gchar *path, + const gchar *uri) +{ + return g_strconcat (path, "::", uri, NULL); +} + +static gchar * +track_row (WindowData *data, + GeditFileBrowserStore *store, + GtkTreePath *path, + const gchar *uri) +{ + GtkTreeRowReference *ref; + gchar *id; + gchar *pathstr; + + pathstr = gtk_tree_path_to_string (path); + id = item_id (pathstr, uri); + + ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path); + g_hash_table_insert (data->row_tracking, g_strdup (id), ref); + + g_free (pathstr); + + return id; +} + +static void +set_item_message (WindowData *data, + GtkTreeIter *iter, + GtkTreePath *path, + GeditMessage *message) +{ + GeditFileBrowserStore *store; + gchar *uri = NULL; + guint flags = 0; + gchar *track_id; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!uri) + return; + + if (path && gtk_tree_path_get_depth (path) != 0) + track_id = track_row (data, store, path, uri); + else + track_id = NULL; + + gedit_message_set (message, + "id", track_id, + "uri", uri, + NULL); + + if (gedit_message_has_key (message, "is_directory")) + { + gedit_message_set (message, + "is_directory", FILE_IS_DIR (flags), + NULL); + } + + g_free (uri); + g_free (track_id); +} + +static gboolean +custom_message_filter_func (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GtkTreeIter *iter, + FilterData *data) +{ + WindowData *wdata = get_window_data (data->window); + gchar *uri = NULL; + guint flags = 0; + gboolean filter = FALSE; + GtkTreePath *path; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!uri || FILE_IS_DUMMY (flags)) + { + g_free (uri); + return FALSE; + } + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + set_item_message (wdata, iter, path, data->message); + gtk_tree_path_free (path); + + gedit_message_set (data->message, "filter", filter, NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + gedit_message_get (data->message, "filter", &filter, NULL); + + return !filter; +} + +static void +message_add_filter_cb (GeditMessageBus *bus, + GeditMessage *message, + GeditWindow *window) +{ + gchar *object_path = NULL; + gchar *method = NULL; + gulong id; + GeditMessageType *message_type; + GeditMessage *cbmessage; + FilterData *filter_data; + WindowData *data = get_window_data (window); + + gedit_message_get (message, + "object_path", &object_path, + "method", &method, + NULL); + + // Check if there exists such a 'callback' message + if (!object_path || !method) + { + g_free (object_path); + g_free (method); + + return; + } + + message_type = gedit_message_bus_lookup (bus, object_path, method); + + if (!message_type) + { + g_free (object_path); + g_free (method); + + return; + } + + // Check if the message type has the correct arguments + if (gedit_message_type_lookup (message_type, "id") != G_TYPE_STRING || + gedit_message_type_lookup (message_type, "uri") != G_TYPE_STRING || + gedit_message_type_lookup (message_type, "is_directory") != G_TYPE_BOOLEAN || + gedit_message_type_lookup (message_type, "filter") != G_TYPE_BOOLEAN) + { + return; + } + + cbmessage = gedit_message_type_instantiate (message_type, + "id", NULL, + "uri", NULL, + "is_directory", FALSE, + "filter", FALSE, + NULL); + + // Register the custom filter on the widget + filter_data = filter_data_new (window, cbmessage); + id = gedit_file_browser_widget_add_filter (data->widget, + (GeditFileBrowserWidgetFilterFunc)custom_message_filter_func, + filter_data, + (GDestroyNotify)filter_data_free); + + filter_data->id = id; +} + +static void +message_remove_filter_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gulong id = 0; + + gedit_message_get (message, "id", &id, NULL); + + if (!id) + return; + + gedit_file_browser_widget_remove_filter (data->widget, id); +} + +static void +message_up_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserStore *store = gedit_file_browser_widget_get_browser_store (data->widget); + + gedit_file_browser_store_set_virtual_root_up (store); +} + +static void +message_history_back_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_history_back (data->widget); +} + +static void +message_history_forward_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_history_forward (data->widget); +} + +static void +message_refresh_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_refresh (data->widget); +} + +static void +message_set_show_hidden_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gboolean active = FALSE; + GeditFileBrowserStore *store; + GeditFileBrowserStoreFilterMode mode; + + gedit_message_get (message, "active", &active, NULL); + + store = gedit_file_browser_widget_get_browser_store (data->widget); + mode = gedit_file_browser_store_get_filter_mode (store); + + if (active) + mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + else + mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + + gedit_file_browser_store_set_filter_mode (store, mode); +} + +static void +message_set_show_binary_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gboolean active = FALSE; + GeditFileBrowserStore *store; + GeditFileBrowserStoreFilterMode mode; + + gedit_message_get (message, "active", &active, NULL); + + store = gedit_file_browser_widget_get_browser_store (data->widget); + mode = gedit_file_browser_store_get_filter_mode (store); + + if (active) + mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + else + mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + + gedit_file_browser_store_set_filter_mode (store, mode); +} + +static void +message_show_bookmarks_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_show_bookmarks (data->widget); +} + +static void +message_show_files_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_show_files (data->widget); +} + +static void +message_add_context_item_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GtkAction *action = NULL; + gchar *path = NULL; + gchar *name; + GtkUIManager *manager; + guint merge_id; + + gedit_message_get (message, + "action", &action, + "path", &path, + NULL); + + if (!action || !path) + { + if (action) + g_object_unref (action); + + g_free (path); + return; + } + + gtk_action_group_add_action (data->merged_actions, action); + manager = gedit_file_browser_widget_get_ui_manager (data->widget); + name = g_strconcat (gtk_action_get_name (action), "MenuItem", NULL); + merge_id = gtk_ui_manager_new_merge_id (manager); + + gtk_ui_manager_add_ui (manager, + merge_id, + path, + name, + gtk_action_get_name (action), + GTK_UI_MANAGER_AUTO, + FALSE); + + if (gtk_ui_manager_get_widget (manager, path)) + { + data->merge_ids = g_list_prepend (data->merge_ids, GINT_TO_POINTER (merge_id)); + gedit_message_set (message, "id", merge_id, NULL); + } + else + { + gedit_message_set (message, "id", 0, NULL); + } + + g_object_unref (action); + g_free (path); + g_free (name); +} + +static void +message_remove_context_item_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + guint merge_id = 0; + GtkUIManager *manager; + + gedit_message_get (message, "id", &merge_id, NULL); + + if (merge_id == 0) + return; + + manager = gedit_file_browser_widget_get_ui_manager (data->widget); + + data->merge_ids = g_list_remove (data->merge_ids, GINT_TO_POINTER (merge_id)); + gtk_ui_manager_remove_ui (manager, merge_id); +} + +static void +message_get_view_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserView *view; + view = gedit_file_browser_widget_get_browser_view (data->widget); + + gedit_message_set (message, "view", view, NULL); +} + +static void +register_methods (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + GeditMessageBus *bus = gedit_window_get_message_bus (window); + WindowData *data = get_window_data (window); + + /* Register method calls */ + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "get_root", + 1, + "uri", G_TYPE_STRING, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_root", + 1, + "uri", G_TYPE_STRING, + "virtual", G_TYPE_STRING, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_emblem", + 0, + "id", G_TYPE_STRING, + "emblem", G_TYPE_STRING, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "add_filter", + 1, + "object_path", G_TYPE_STRING, + "method", G_TYPE_STRING, + "id", G_TYPE_ULONG, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "remove_filter", + 0, + "id", G_TYPE_ULONG, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "add_context_item", + 1, + "action", GTK_TYPE_ACTION, + "path", G_TYPE_STRING, + "id", G_TYPE_UINT, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "remove_context_item", + 0, + "id", G_TYPE_UINT, + NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "up", 0, NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "history_back", 0, NULL); + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "history_forward", 0, NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "refresh", 0, NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_show_hidden", + 0, + "active", G_TYPE_BOOLEAN, + NULL); + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_show_binary", + 0, + "active", G_TYPE_BOOLEAN, + NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "show_bookmarks", 0, NULL); + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "show_files", 0, NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "get_view", + 1, + "view", GEDIT_TYPE_FILE_BROWSER_VIEW, + NULL); + + BUS_CONNECT (bus, get_root, data); + BUS_CONNECT (bus, set_root, data); + BUS_CONNECT (bus, set_emblem, data); + BUS_CONNECT (bus, add_filter, window); + BUS_CONNECT (bus, remove_filter, data); + + BUS_CONNECT (bus, add_context_item, data); + BUS_CONNECT (bus, remove_context_item, data); + + BUS_CONNECT (bus, up, data); + BUS_CONNECT (bus, history_back, data); + BUS_CONNECT (bus, history_forward, data); + + BUS_CONNECT (bus, refresh, data); + + BUS_CONNECT (bus, set_show_hidden, data); + BUS_CONNECT (bus, set_show_binary, data); + + BUS_CONNECT (bus, show_bookmarks, data); + BUS_CONNECT (bus, show_files, data); + + BUS_CONNECT (bus, get_view, data); +} + +static void +store_row_inserted (GeditFileBrowserStore *store, + GtkTreePath *path, + GtkTreeIter *iter, + MessageCacheData *data) +{ + gchar *uri = NULL; + guint flags = 0; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags)) + { + WindowData *wdata = get_window_data (data->window); + + set_item_message (wdata, iter, path, data->message); + gedit_message_bus_send_message_sync (wdata->bus, data->message); + } + + g_free (uri); +} + +static void +store_row_deleted (GeditFileBrowserStore *store, + GtkTreePath *path, + MessageCacheData *data) +{ + GtkTreeIter iter; + gchar *uri = NULL; + guint flags = 0; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags)) + { + WindowData *wdata = get_window_data (data->window); + + set_item_message (wdata, &iter, path, data->message); + gedit_message_bus_send_message_sync (wdata->bus, data->message); + } + + g_free (uri); +} + +static void +store_virtual_root_changed (GeditFileBrowserStore *store, + GParamSpec *spec, + MessageCacheData *data) +{ + WindowData *wdata = get_window_data (data->window); + gchar *uri; + + uri = gedit_file_browser_store_get_virtual_root (store); + + if (!uri) + return; + + gedit_message_set (data->message, + "uri", uri, + NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + + g_free (uri); +} + +static void +store_begin_loading (GeditFileBrowserStore *store, + GtkTreeIter *iter, + MessageCacheData *data) +{ + GtkTreePath *path; + WindowData *wdata = get_window_data (data->window); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + + set_item_message (wdata, iter, path, data->message); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + gtk_tree_path_free (path); +} + +static void +store_end_loading (GeditFileBrowserStore *store, + GtkTreeIter *iter, + MessageCacheData *data) +{ + GtkTreePath *path; + WindowData *wdata = get_window_data (data->window); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + + set_item_message (wdata, iter, path, data->message); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + gtk_tree_path_free (path); +} + +static void +register_signals (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + GeditMessageBus *bus = gedit_window_get_message_bus (window); + GeditFileBrowserStore *store; + GeditMessageType *inserted_type; + GeditMessageType *deleted_type; + GeditMessageType *begin_loading_type; + GeditMessageType *end_loading_type; + GeditMessageType *root_changed_type; + + GeditMessage *message; + WindowData *data; + + /* Register signals */ + root_changed_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "root_changed", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + NULL); + + begin_loading_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "begin_loading", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + NULL); + + end_loading_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "end_loading", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + NULL); + + inserted_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "inserted", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + "is_directory", G_TYPE_BOOLEAN, + NULL); + + deleted_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "deleted", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + "is_directory", G_TYPE_BOOLEAN, + NULL); + + store = gedit_file_browser_widget_get_browser_store (widget); + + message = gedit_message_type_instantiate (inserted_type, + "id", NULL, + "uri", NULL, + "is_directory", FALSE, + NULL); + + data = get_window_data (window); + + data->row_inserted_id = + g_signal_connect_data (store, + "row-inserted", + G_CALLBACK (store_row_inserted), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (deleted_type, + "id", NULL, + "uri", NULL, + "is_directory", FALSE, + NULL); + data->row_deleted_id = + g_signal_connect_data (store, + "row-deleted", + G_CALLBACK (store_row_deleted), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (root_changed_type, + "id", NULL, + "uri", NULL, + NULL); + data->root_changed_id = + g_signal_connect_data (store, + "notify::virtual-root", + G_CALLBACK (store_virtual_root_changed), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (begin_loading_type, + "id", NULL, + "uri", NULL, + NULL); + data->begin_loading_id = + g_signal_connect_data (store, + "begin_loading", + G_CALLBACK (store_begin_loading), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (end_loading_type, + "id", NULL, + "uri", NULL, + NULL); + data->end_loading_id = + g_signal_connect_data (store, + "end_loading", + G_CALLBACK (store_end_loading), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); +} + +static void +message_unregistered (GeditMessageBus *bus, + GeditMessageType *message_type, + GeditWindow *window) +{ + gchar *identifier = gedit_message_type_identifier (gedit_message_type_get_object_path (message_type), + gedit_message_type_get_method (message_type)); + FilterData *data; + WindowData *wdata = get_window_data (window); + + data = g_hash_table_lookup (wdata->filters, identifier); + + if (data) + gedit_file_browser_widget_remove_filter (wdata->widget, data->id); + + g_free (identifier); +} + +void +gedit_file_browser_messages_register (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + window_data_new (window, widget); + + register_methods (window, widget); + register_signals (window, widget); + + g_signal_connect (gedit_window_get_message_bus (window), + "unregistered", + G_CALLBACK (message_unregistered), + window); +} + +static void +cleanup_signals (GeditWindow *window) +{ + WindowData *data = get_window_data (window); + GeditFileBrowserStore *store; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + g_signal_handler_disconnect (store, data->row_inserted_id); + g_signal_handler_disconnect (store, data->row_deleted_id); + g_signal_handler_disconnect (store, data->root_changed_id); + g_signal_handler_disconnect (store, data->begin_loading_id); + g_signal_handler_disconnect (store, data->end_loading_id); + + g_signal_handlers_disconnect_by_func (data->bus, message_unregistered, window); +} + +void +gedit_file_browser_messages_unregister (GeditWindow *window) +{ + GeditMessageBus *bus = gedit_window_get_message_bus (window); + + cleanup_signals (window); + gedit_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH); + + window_data_free (window); +} diff --git a/plugins/filebrowser/gedit-file-browser-messages.h b/plugins/filebrowser/gedit-file-browser-messages.h new file mode 100755 index 00000000..e62094e8 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.h @@ -0,0 +1,35 @@ +/* + * gedit-file-browser-messages.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2008 - Jesse van den Kieboom <[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, 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_FILE_BROWSER_MESSAGES_H__ +#define __GEDIT_FILE_BROWSER_MESSAGES_H__ + +#include <gedit/gedit-window.h> +#include <gedit/gedit-message-bus.h> +#include "gedit-file-browser-widget.h" + +void gedit_file_browser_messages_register (GeditWindow *window, + GeditFileBrowserWidget *widget); +void gedit_file_browser_messages_unregister (GeditWindow *window); + +#endif /* __GEDIT_FILE_BROWSER_MESSAGES_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-plugin.c b/plugins/filebrowser/gedit-file-browser-plugin.c new file mode 100755 index 00000000..f2da19f5 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.c @@ -0,0 +1,1254 @@ +/* + * gedit-file-browser-plugin.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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/gedit-commands.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-app.h> +#include <glib/gi18n-lib.h> +#include <gedit/gedit-debug.h> +#include <mateconf/mateconf-client.h> +#include <string.h> + +#include "gedit-file-browser-enum-types.h" +#include "gedit-file-browser-plugin.h" +#include "gedit-file-browser-utils.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-widget.h" +#include "gedit-file-browser-messages.h" + +#define WINDOW_DATA_KEY "GeditFileBrowserPluginWindowData" +#define FILE_BROWSER_BASE_KEY "/apps/gedit-2/plugins/filebrowser" +#define CAJA_CLICK_POLICY_BASE_KEY "/apps/caja/preferences" +#define CAJA_CLICK_POLICY_KEY "click_policy" +#define CAJA_ENABLE_DELETE_KEY "enable_delete" +#define CAJA_CONFIRM_TRASH_KEY "confirm_trash" +#define TERMINAL_EXEC_KEY "/desktop/mate/applications/terminal/exec" + +#define GEDIT_FILE_BROWSER_PLUGIN_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginPrivate)) + +struct _GeditFileBrowserPluginPrivate +{ + gpointer dummy; +}; + +typedef struct _GeditFileBrowserPluginData +{ + GeditFileBrowserWidget * tree_widget; + gulong merge_id; + GtkActionGroup * action_group; + GtkActionGroup * single_selection_action_group; + gboolean auto_root; + gulong end_loading_handle; + gboolean confirm_trash; + + guint click_policy_handle; + guint enable_delete_handle; + guint confirm_trash_handle; +} GeditFileBrowserPluginData; + +static void on_uri_activated_cb (GeditFileBrowserWidget * widget, + gchar const *uri, + GeditWindow * window); +static void on_error_cb (GeditFileBrowserWidget * widget, + guint code, + gchar const *message, + GeditWindow * window); +static void on_model_set_cb (GeditFileBrowserView * widget, + GParamSpec *arg1, + GeditWindow * window); +static void on_virtual_root_changed_cb (GeditFileBrowserStore * model, + GParamSpec * param, + GeditWindow * window); +static void on_filter_mode_changed_cb (GeditFileBrowserStore * model, + GParamSpec * param, + GeditWindow * window); +static void on_rename_cb (GeditFileBrowserStore * model, + const gchar * olduri, + const gchar * newuri, + GeditWindow * window); +static void on_filter_pattern_changed_cb (GeditFileBrowserWidget * widget, + GParamSpec * param, + GeditWindow * window); +static void on_tab_added_cb (GeditWindow * window, + GeditTab * tab, + GeditFileBrowserPluginData * data); +static gboolean on_confirm_delete_cb (GeditFileBrowserWidget * widget, + GeditFileBrowserStore * store, + GList * rows, + GeditWindow * window); +static gboolean on_confirm_no_trash_cb (GeditFileBrowserWidget * widget, + GList * files, + GeditWindow * window); + +GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE (GeditFileBrowserPlugin, filetree_plugin, \ + gedit_file_browser_enum_and_flag_register_type (type_module); \ + gedit_file_browser_store_register_type (type_module); \ + gedit_file_bookmarks_store_register_type (type_module); \ + gedit_file_browser_view_register_type (type_module); \ + gedit_file_browser_widget_register_type (type_module); \ +) + + +static void +filetree_plugin_init (GeditFileBrowserPlugin * plugin) +{ + plugin->priv = GEDIT_FILE_BROWSER_PLUGIN_GET_PRIVATE (plugin); +} + +static void +filetree_plugin_finalize (GObject * object) +{ + //GeditFileBrowserPlugin * plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + G_OBJECT_CLASS (filetree_plugin_parent_class)->finalize (object); +} + +static GeditFileBrowserPluginData * +get_plugin_data (GeditWindow * window) +{ + return (GeditFileBrowserPluginData *) (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY)); +} + +static void +on_end_loading_cb (GeditFileBrowserStore * store, + GtkTreeIter * iter, + GeditFileBrowserPluginData * data) +{ + /* Disconnect the signal */ + g_signal_handler_disconnect (store, data->end_loading_handle); + data->end_loading_handle = 0; + data->auto_root = FALSE; +} + +static void +prepare_auto_root (GeditFileBrowserPluginData *data) +{ + GeditFileBrowserStore *store; + + data->auto_root = TRUE; + + store = gedit_file_browser_widget_get_browser_store (data->tree_widget); + + if (data->end_loading_handle != 0) { + g_signal_handler_disconnect (store, data->end_loading_handle); + data->end_loading_handle = 0; + } + + data->end_loading_handle = g_signal_connect (store, + "end-loading", + G_CALLBACK (on_end_loading_cb), + data); +} + +static void +restore_default_location (GeditFileBrowserPluginData *data) +{ + gchar * root; + gchar * virtual_root; + gboolean bookmarks; + gboolean remote; + MateConfClient * client; + + client = mateconf_client_get_default (); + if (!client) + return; + + bookmarks = !mateconf_client_get_bool (client, + FILE_BROWSER_BASE_KEY "/on_load/tree_view", + NULL); + + if (bookmarks) { + g_object_unref (client); + gedit_file_browser_widget_show_bookmarks (data->tree_widget); + return; + } + + root = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/on_load/root", + NULL); + virtual_root = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/on_load/virtual_root", + NULL); + + remote = mateconf_client_get_bool (client, + FILE_BROWSER_BASE_KEY "/on_load/enable_remote", + NULL); + + if (root != NULL && *root != '\0') { + GFile *file; + + file = g_file_new_for_uri (root); + + if (remote || g_file_is_native (file)) { + if (virtual_root != NULL && *virtual_root != '\0') { + prepare_auto_root (data); + gedit_file_browser_widget_set_root_and_virtual_root (data->tree_widget, + root, + virtual_root); + } else { + prepare_auto_root (data); + gedit_file_browser_widget_set_root (data->tree_widget, + root, + TRUE); + } + } + + g_object_unref (file); + } + + g_object_unref (client); + g_free (root); + g_free (virtual_root); +} + +static void +restore_filter (GeditFileBrowserPluginData * data) +{ + MateConfClient * client; + gchar *filter_mode; + GeditFileBrowserStoreFilterMode mode; + gchar *pattern; + + client = mateconf_client_get_default (); + if (!client) + return; + + /* Get filter_mode */ + filter_mode = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + NULL); + + /* Filter mode */ + mode = gedit_file_browser_store_filter_mode_get_default (); + + if (filter_mode != NULL) { + if (strcmp (filter_mode, "hidden") == 0) { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + } else if (strcmp (filter_mode, "binary") == 0) { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + } else if (strcmp (filter_mode, "hidden_and_binary") == 0 || + strcmp (filter_mode, "binary_and_hidden") == 0) { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN | + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + } else if (strcmp (filter_mode, "none") == 0 || + *filter_mode == '\0') { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_NONE; + } + } + + /* Set the filter mode */ + gedit_file_browser_store_set_filter_mode ( + gedit_file_browser_widget_get_browser_store (data->tree_widget), + mode); + + pattern = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/filter_pattern", + NULL); + + gedit_file_browser_widget_set_filter_pattern (data->tree_widget, + pattern); + + g_object_unref (client); + g_free (filter_mode); + g_free (pattern); +} + +static GeditFileBrowserViewClickPolicy +click_policy_from_string (gchar const *click_policy) +{ + if (click_policy && strcmp (click_policy, "single") == 0) + return GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE; + else + return GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE; +} + +static void +on_click_policy_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + MateConfValue *value; + GeditFileBrowserPluginData * data; + gchar const *click_policy; + GeditFileBrowserViewClickPolicy policy = GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE; + GeditFileBrowserView *view; + + data = (GeditFileBrowserPluginData *)(user_data); + value = mateconf_entry_get_value (entry); + + if (value && value->type == MATECONF_VALUE_STRING) { + click_policy = mateconf_value_get_string (value); + + policy = click_policy_from_string (click_policy); + } + + view = gedit_file_browser_widget_get_browser_view (data->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); +} + +static void +on_enable_delete_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + MateConfValue *value; + GeditFileBrowserPluginData *data; + gboolean enable = FALSE; + + data = (GeditFileBrowserPluginData *)(user_data); + value = mateconf_entry_get_value (entry); + + if (value && value->type == MATECONF_VALUE_BOOL) + enable = mateconf_value_get_bool (value); + + g_object_set (G_OBJECT (data->tree_widget), "enable-delete", enable, NULL); +} + +static void +on_confirm_trash_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + MateConfValue *value; + GeditFileBrowserPluginData *data; + gboolean enable = FALSE; + + data = (GeditFileBrowserPluginData *)(user_data); + value = mateconf_entry_get_value (entry); + + if (value && value->type == MATECONF_VALUE_BOOL) + enable = mateconf_value_get_bool (value); + + data->confirm_trash = enable; +} + +static void +install_caja_prefs (GeditFileBrowserPluginData *data) +{ + MateConfClient *client; + gchar *pref; + gboolean prefb; + GeditFileBrowserViewClickPolicy policy; + GeditFileBrowserView *view; + + client = mateconf_client_get_default (); + if (!client) + return; + + mateconf_client_add_dir (client, + CAJA_CLICK_POLICY_BASE_KEY, + MATECONF_CLIENT_PRELOAD_NONE, + NULL); + + /* Get click_policy */ + pref = mateconf_client_get_string (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CLICK_POLICY_KEY, + NULL); + + policy = click_policy_from_string (pref); + + view = gedit_file_browser_widget_get_browser_view (data->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); + + if (pref) { + data->click_policy_handle = + mateconf_client_notify_add (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CLICK_POLICY_KEY, + on_click_policy_changed, + data, + NULL, + NULL); + g_free (pref); + } + + /* Get enable_delete */ + prefb = mateconf_client_get_bool (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_ENABLE_DELETE_KEY, + NULL); + + g_object_set (G_OBJECT (data->tree_widget), "enable-delete", prefb, NULL); + + data->enable_delete_handle = + mateconf_client_notify_add (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_ENABLE_DELETE_KEY, + on_enable_delete_changed, + data, + NULL, + NULL); + + /* Get confirm_trash */ + prefb = mateconf_client_get_bool (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CONFIRM_TRASH_KEY, + NULL); + + data->confirm_trash = prefb; + + data->confirm_trash_handle = + mateconf_client_notify_add (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CONFIRM_TRASH_KEY, + on_confirm_trash_changed, + data, + NULL, + NULL); + g_object_unref (client); +} + +static void +set_root_from_doc (GeditFileBrowserPluginData * data, + GeditDocument * doc) +{ + GFile *file; + GFile *parent; + + if (doc == NULL) + return; + + file = gedit_document_get_location (doc); + if (file == NULL) + return; + + parent = g_file_get_parent (file); + + if (parent != NULL) { + gchar * root; + + root = g_file_get_uri (parent); + + gedit_file_browser_widget_set_root (data->tree_widget, + root, + TRUE); + + g_object_unref (parent); + g_free (root); + } + + g_object_unref (file); +} + +static void +on_action_set_active_root (GtkAction * action, + GeditWindow * window) +{ + GeditFileBrowserPluginData *data; + + data = get_plugin_data (window); + set_root_from_doc (data, + gedit_window_get_active_document (window)); +} + +static gchar * +get_terminal (void) +{ + MateConfClient * client; + gchar * terminal; + + client = mateconf_client_get_default (); + terminal = mateconf_client_get_string (client, + TERMINAL_EXEC_KEY, + NULL); + g_object_unref (client); + + if (terminal == NULL) { + const gchar *term = g_getenv ("TERM"); + + if (term != NULL) + terminal = g_strdup (term); + else + terminal = g_strdup ("xterm"); + } + + return terminal; +} + +static void +on_action_open_terminal (GtkAction * action, + GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + gchar * terminal; + gchar * wd = NULL; + gchar * local; + gchar * argv[2]; + GFile * file; + + GtkTreeIter iter; + GeditFileBrowserStore * store; + + data = get_plugin_data (window); + + /* Get the current directory */ + if (!gedit_file_browser_widget_get_selected_directory (data->tree_widget, &iter)) + return; + + store = gedit_file_browser_widget_get_browser_store (data->tree_widget); + gtk_tree_model_get (GTK_TREE_MODEL (store), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &wd, + -1); + + if (wd == NULL) + return; + + terminal = get_terminal (); + + file = g_file_new_for_uri (wd); + local = g_file_get_path (file); + g_object_unref (file); + + argv[0] = terminal; + argv[1] = NULL; + + g_spawn_async (local, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, + NULL, + NULL, + NULL); + + g_free (terminal); + g_free (wd); + g_free (local); +} + +static void +on_selection_changed_cb (GtkTreeSelection *selection, + GeditWindow *window) +{ + GeditFileBrowserPluginData * data; + GtkTreeView * tree_view; + GtkTreeModel * model; + GtkTreeIter iter; + gboolean sensitive; + gchar * uri; + + data = get_plugin_data (window); + + tree_view = GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (data->tree_widget)); + model = gtk_tree_view_get_model (tree_view); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + sensitive = gedit_file_browser_widget_get_selected_directory (data->tree_widget, &iter); + + if (sensitive) { + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, -1); + + sensitive = gedit_utils_uri_has_file_scheme (uri); + g_free (uri); + } + + gtk_action_set_sensitive ( + gtk_action_group_get_action (data->single_selection_action_group, + "OpenTerminal"), + sensitive); +} + +#define POPUP_UI "" \ +"<ui>" \ +" <popup name=\"FilePopup\">" \ +" <placeholder name=\"FilePopup_Opt1\">" \ +" <menuitem action=\"SetActiveRoot\"/>" \ +" </placeholder>" \ +" <placeholder name=\"FilePopup_Opt4\">" \ +" <menuitem action=\"OpenTerminal\"/>" \ +" </placeholder>" \ +" </popup>" \ +" <popup name=\"BookmarkPopup\">" \ +" <placeholder name=\"BookmarkPopup_Opt1\">" \ +" <menuitem action=\"SetActiveRoot\"/>" \ +" </placeholder>" \ +" </popup>" \ +"</ui>" + +static GtkActionEntry extra_actions[] = +{ + {"SetActiveRoot", GTK_STOCK_JUMP_TO, N_("_Set root to active document"), + NULL, + N_("Set the root to the active document location"), + G_CALLBACK (on_action_set_active_root)} +}; + +static GtkActionEntry extra_single_selection_actions[] = { + {"OpenTerminal", "utilities-terminal", N_("_Open terminal here"), + NULL, + N_("Open a terminal at the currently opened directory"), + G_CALLBACK (on_action_open_terminal)} +}; + +static void +add_popup_ui (GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GtkUIManager * manager; + GtkActionGroup * action_group; + GError * error = NULL; + + data = get_plugin_data (window); + manager = gedit_file_browser_widget_get_ui_manager (data->tree_widget); + + action_group = gtk_action_group_new ("FileBrowserPluginExtra"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + extra_actions, + G_N_ELEMENTS (extra_actions), + window); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + data->action_group = action_group; + + action_group = gtk_action_group_new ("FileBrowserPluginSingleSelectionExtra"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + extra_single_selection_actions, + G_N_ELEMENTS (extra_single_selection_actions), + window); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + data->single_selection_action_group = action_group; + + data->merge_id = gtk_ui_manager_add_ui_from_string (manager, + POPUP_UI, + -1, + &error); + + if (data->merge_id == 0) { + g_warning("Unable to merge UI: %s", error->message); + g_error_free(error); + } +} + +static void +remove_popup_ui (GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GtkUIManager * manager; + + data = get_plugin_data (window); + manager = gedit_file_browser_widget_get_ui_manager (data->tree_widget); + gtk_ui_manager_remove_ui (manager, data->merge_id); + + gtk_ui_manager_remove_action_group (manager, data->action_group); + g_object_unref (data->action_group); + + gtk_ui_manager_remove_action_group (manager, data->single_selection_action_group); + g_object_unref (data->single_selection_action_group); +} + +static void +impl_updateui (GeditPlugin * plugin, GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GeditDocument * doc; + + data = get_plugin_data (window); + + doc = gedit_window_get_active_document (window); + + gtk_action_set_sensitive (gtk_action_group_get_action (data->action_group, + "SetActiveRoot"), + doc != NULL && + !gedit_document_is_untitled (doc)); +} + +static void +impl_activate (GeditPlugin * plugin, GeditWindow * window) +{ + GeditPanel * panel; + GeditFileBrowserPluginData * data; + GtkWidget * image; + GdkPixbuf * pixbuf; + GeditFileBrowserStore * store; + gchar *data_dir; + + data = g_new0 (GeditFileBrowserPluginData, 1); + + data_dir = gedit_plugin_get_data_dir (plugin); + data->tree_widget = GEDIT_FILE_BROWSER_WIDGET (gedit_file_browser_widget_new (data_dir)); + g_free (data_dir); + + g_signal_connect (data->tree_widget, + "uri-activated", + G_CALLBACK (on_uri_activated_cb), window); + + g_signal_connect (data->tree_widget, + "error", G_CALLBACK (on_error_cb), window); + + g_signal_connect (data->tree_widget, + "notify::filter-pattern", + G_CALLBACK (on_filter_pattern_changed_cb), + window); + + g_signal_connect (data->tree_widget, + "confirm-delete", + G_CALLBACK (on_confirm_delete_cb), + window); + + g_signal_connect (data->tree_widget, + "confirm-no-trash", + G_CALLBACK (on_confirm_no_trash_cb), + window); + + g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW + (gedit_file_browser_widget_get_browser_view + (data->tree_widget))), + "changed", + G_CALLBACK (on_selection_changed_cb), + window); + + panel = gedit_window_get_side_panel (window); + pixbuf = gedit_file_browser_utils_pixbuf_from_theme("system-file-manager", + GTK_ICON_SIZE_MENU); + + if (pixbuf) { + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + } else { + image = gtk_image_new_from_stock(GTK_STOCK_INDEX, GTK_ICON_SIZE_MENU); + } + + gtk_widget_show(image); + gedit_panel_add_item (panel, + GTK_WIDGET (data->tree_widget), + _("File Browser"), + image); + gtk_widget_show (GTK_WIDGET (data->tree_widget)); + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, data); + + add_popup_ui (window); + + /* Restore filter options */ + restore_filter (data); + + /* Install caja preferences */ + install_caja_prefs (data); + + /* Connect signals to store the last visited location */ + g_signal_connect (gedit_file_browser_widget_get_browser_view (data->tree_widget), + "notify::model", + G_CALLBACK (on_model_set_cb), + window); + + store = gedit_file_browser_widget_get_browser_store (data->tree_widget); + g_signal_connect (store, + "notify::virtual-root", + G_CALLBACK (on_virtual_root_changed_cb), + window); + + g_signal_connect (store, + "notify::filter-mode", + G_CALLBACK (on_filter_mode_changed_cb), + window); + + g_signal_connect (store, + "rename", + G_CALLBACK (on_rename_cb), + window); + + g_signal_connect (window, + "tab-added", + G_CALLBACK (on_tab_added_cb), + data); + + /* Register messages on the bus */ + gedit_file_browser_messages_register (window, data->tree_widget); + + impl_updateui (plugin, window); +} + +static void +impl_deactivate (GeditPlugin * plugin, GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GeditPanel * panel; + MateConfClient *client; + + data = get_plugin_data (window); + + /* Unregister messages from the bus */ + gedit_file_browser_messages_unregister (window); + + /* Disconnect signals */ + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + data); + + client = mateconf_client_get_default (); + mateconf_client_remove_dir (client, CAJA_CLICK_POLICY_BASE_KEY, NULL); + + if (data->click_policy_handle) + mateconf_client_notify_remove (client, data->click_policy_handle); + + if (data->enable_delete_handle) + mateconf_client_notify_remove (client, data->enable_delete_handle); + + if (data->confirm_trash_handle) + mateconf_client_notify_remove (client, data->confirm_trash_handle); + + g_object_unref (client); + remove_popup_ui (window); + + panel = gedit_window_get_side_panel (window); + gedit_panel_remove_item (panel, GTK_WIDGET (data->tree_widget)); + + g_free (data); + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static void +filetree_plugin_class_init (GeditFileBrowserPluginClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass * plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = filetree_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_updateui; + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserPluginPrivate)); +} + +/* Callbacks */ +static void +on_uri_activated_cb (GeditFileBrowserWidget * tree_widget, + gchar const *uri, GeditWindow * window) +{ + gedit_commands_load_uri (window, uri, NULL, 0); +} + +static void +on_error_cb (GeditFileBrowserWidget * tree_widget, + guint code, gchar const *message, GeditWindow * window) +{ + gchar * title; + GtkWidget * dlg; + GeditFileBrowserPluginData * data; + + data = get_plugin_data (window); + + /* Do not show the error when the root has been set automatically */ + if (data->auto_root && (code == GEDIT_FILE_BROWSER_ERROR_SET_ROOT || + code == GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY)) + { + /* Show bookmarks */ + gedit_file_browser_widget_show_bookmarks (data->tree_widget); + return; + } + + switch (code) { + case GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY: + title = + _("An error occurred while creating a new directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_NEW_FILE: + title = _("An error occurred while creating a new file"); + break; + case GEDIT_FILE_BROWSER_ERROR_RENAME: + title = + _ + ("An error occurred while renaming a file or directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_DELETE: + title = + _ + ("An error occurred while deleting a file or directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY: + title = + _ + ("An error occurred while opening a directory in the file manager"); + break; + case GEDIT_FILE_BROWSER_ERROR_SET_ROOT: + title = + _("An error occurred while setting a root directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY: + title = + _("An error occurred while loading a directory"); + break; + default: + title = _("An error occurred"); + break; + } + + dlg = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "%s", title); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), + "%s", message); + + gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); +} + +static void +on_model_set_cb (GeditFileBrowserView * widget, + GParamSpec *arg1, + GeditWindow * window) +{ + GeditFileBrowserPluginData * data = get_plugin_data (window); + GtkTreeModel * model; + MateConfClient * client; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (data->tree_widget))); + + if (model == NULL) + return; + + client = mateconf_client_get_default (); + mateconf_client_set_bool (client, + FILE_BROWSER_BASE_KEY "/on_load/tree_view", + GEDIT_IS_FILE_BROWSER_STORE (model), + NULL); + g_object_unref (client); +} + +static void +on_filter_mode_changed_cb (GeditFileBrowserStore * model, + GParamSpec * param, + GeditWindow * window) +{ + MateConfClient * client; + GeditFileBrowserStoreFilterMode mode; + + client = mateconf_client_get_default (); + + if (!client) + return; + + mode = gedit_file_browser_store_get_filter_mode (model); + + if ((mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) && + (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY)) { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "hidden_and_binary", + NULL); + } else if (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "hidden", + NULL); + } else if (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY) { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "binary", + NULL); + } else { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "none", + NULL); + } + + g_object_unref (client); + +} + +static void +on_rename_cb (GeditFileBrowserStore * store, + const gchar * olduri, + const gchar * newuri, + GeditWindow * window) +{ + GeditApp * app; + GList * documents; + GList * item; + GeditDocument * doc; + GFile * docfile; + GFile * oldfile; + GFile * newfile; + gchar * uri; + + /* Find all documents and set its uri to newuri where it matches olduri */ + app = gedit_app_get_default (); + documents = gedit_app_get_documents (app); + + oldfile = g_file_new_for_uri (olduri); + newfile = g_file_new_for_uri (newuri); + + for (item = documents; item; item = item->next) { + doc = GEDIT_DOCUMENT (item->data); + uri = gedit_document_get_uri (doc); + + if (!uri) + continue; + + docfile = g_file_new_for_uri (uri); + + if (g_file_equal (docfile, oldfile)) { + gedit_document_set_uri (doc, newuri); + } else { + gchar *relative; + + relative = g_file_get_relative_path (oldfile, docfile); + + if (relative) { + /* relative now contains the part in docfile without + the prefix oldfile */ + + g_object_unref (docfile); + g_free (uri); + + docfile = g_file_get_child (newfile, relative); + uri = g_file_get_uri (docfile); + + gedit_document_set_uri (doc, uri); + } + + g_free (relative); + } + + g_free (uri); + g_object_unref (docfile); + } + + g_object_unref (oldfile); + g_object_unref (newfile); + + g_list_free (documents); +} + +static void +on_filter_pattern_changed_cb (GeditFileBrowserWidget * widget, + GParamSpec * param, + GeditWindow * window) +{ + MateConfClient * client; + gchar * pattern; + + client = mateconf_client_get_default (); + + if (!client) + return; + + g_object_get (G_OBJECT (widget), "filter-pattern", &pattern, NULL); + + if (pattern == NULL) + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_pattern", + "", + NULL); + else + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_pattern", + pattern, + NULL); + + g_free (pattern); +} + +static void +on_virtual_root_changed_cb (GeditFileBrowserStore * store, + GParamSpec * param, + GeditWindow * window) +{ + GeditFileBrowserPluginData * data = get_plugin_data (window); + gchar * root; + gchar * virtual_root; + MateConfClient * client; + + root = gedit_file_browser_store_get_root (store); + + if (!root) + return; + + client = mateconf_client_get_default (); + + if (!client) + return; + + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/on_load/root", + root, + NULL); + + virtual_root = gedit_file_browser_store_get_virtual_root (store); + + if (!virtual_root) { + /* Set virtual to same as root then */ + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/on_load/virtual_root", + root, + NULL); + } else { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/on_load/virtual_root", + virtual_root, + NULL); + } + + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + data); + + g_object_unref (client); + g_free (root); + g_free (virtual_root); +} + +static void +on_tab_added_cb (GeditWindow * window, + GeditTab * tab, + GeditFileBrowserPluginData * data) +{ + MateConfClient *client; + gboolean open; + gboolean load_default = TRUE; + + client = mateconf_client_get_default (); + + if (!client) + return; + + open = mateconf_client_get_bool (client, + FILE_BROWSER_BASE_KEY "/open_at_first_doc", + NULL); + + if (open) { + GeditDocument *doc; + gchar *uri; + + doc = gedit_tab_get_document (tab); + + uri = gedit_document_get_uri (doc); + + if (uri != NULL && gedit_utils_uri_has_file_scheme (uri)) { + prepare_auto_root (data); + set_root_from_doc (data, doc); + load_default = FALSE; + } + + g_free (uri); + } + + if (load_default) + restore_default_location (data); + + g_object_unref (client); + + /* Disconnect this signal, it's only called once */ + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + data); +} + +static gchar * +get_filename_from_path (GtkTreeModel *model, GtkTreePath *path) +{ + GtkTreeIter iter; + gchar *uri; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + return gedit_file_browser_utils_uri_basename (uri); +} + +static gboolean +on_confirm_no_trash_cb (GeditFileBrowserWidget * widget, + GList * files, + GeditWindow * window) +{ + gchar *normal; + gchar *message; + gchar *secondary; + gboolean result; + + message = _("Cannot move file to trash, do you\nwant to delete permanently?"); + + if (files->next == NULL) { + normal = gedit_file_browser_utils_file_basename (G_FILE (files->data)); + secondary = g_strdup_printf (_("The file \"%s\" cannot be moved to the trash."), normal); + g_free (normal); + } else { + secondary = g_strdup (_("The selected files cannot be moved to the trash.")); + } + + result = gedit_file_browser_utils_confirmation_dialog (window, + GTK_MESSAGE_QUESTION, + message, + secondary, + GTK_STOCK_DELETE, + NULL); + g_free (secondary); + + return result; +} + +static gboolean +on_confirm_delete_cb (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GList *paths, + GeditWindow *window) +{ + gchar *normal; + gchar *message; + gchar *secondary; + gboolean result; + GeditFileBrowserPluginData *data; + + data = get_plugin_data (window); + + if (!data->confirm_trash) + return TRUE; + + if (paths->next == NULL) { + normal = get_filename_from_path (GTK_TREE_MODEL (store), (GtkTreePath *)(paths->data)); + message = g_strdup_printf (_("Are you sure you want to permanently delete \"%s\"?"), normal); + g_free (normal); + } else { + message = g_strdup (_("Are you sure you want to permanently delete the selected files?")); + } + + secondary = _("If you delete an item, it is permanently lost."); + + result = gedit_file_browser_utils_confirmation_dialog (window, + GTK_MESSAGE_QUESTION, + message, + secondary, + GTK_STOCK_DELETE, + NULL); + + g_free (message); + + return result; +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-plugin.h b/plugins/filebrowser/gedit-file-browser-plugin.h new file mode 100755 index 00000000..19ca86bf --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.h @@ -0,0 +1,71 @@ +/* + * gedit-file-browser-plugin.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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_FILE_BROWSER_PLUGIN_H__ +#define __GEDIT_FILE_BROWSER_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_FILE_BROWSER_PLUGIN (filetree_plugin_get_type ()) +#define GEDIT_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPlugin)) +#define GEDIT_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass)) +#define GEDIT_IS_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN)) +#define GEDIT_IS_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN)) +#define GEDIT_FILE_BROWSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass)) + +/* Private structure type */ +typedef struct _GeditFileBrowserPluginPrivate GeditFileBrowserPluginPrivate; +typedef struct _GeditFileBrowserPlugin GeditFileBrowserPlugin; +typedef struct _GeditFileBrowserPluginClass GeditFileBrowserPluginClass; + +struct _GeditFileBrowserPlugin +{ + GeditPlugin parent_instance; + + /*< private > */ + GeditFileBrowserPluginPrivate *priv; +}; + + + +struct _GeditFileBrowserPluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType filetree_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule * module); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_PLUGIN_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-store.c b/plugins/filebrowser/gedit-file-browser-store.c new file mode 100755 index 00000000..6c4f5b51 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.c @@ -0,0 +1,3625 @@ +/* + * gedit-file-browser-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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 <string.h> +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include <gedit/gedit-plugin.h> +#include <gedit/gedit-utils.h> + +#include "gedit-file-browser-store.h" +#include "gedit-file-browser-marshal.h" +#include "gedit-file-browser-enum-types.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-utils.h" + +#define GEDIT_FILE_BROWSER_STORE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GEDIT_TYPE_FILE_BROWSER_STORE, \ + GeditFileBrowserStorePrivate)) + +#define NODE_IS_DIR(node) (FILE_IS_DIR((node)->flags)) +#define NODE_IS_HIDDEN(node) (FILE_IS_HIDDEN((node)->flags)) +#define NODE_IS_TEXT(node) (FILE_IS_TEXT((node)->flags)) +#define NODE_LOADED(node) (FILE_LOADED((node)->flags)) +#define NODE_IS_FILTERED(node) (FILE_IS_FILTERED((node)->flags)) +#define NODE_IS_DUMMY(node) (FILE_IS_DUMMY((node)->flags)) + +#define FILE_BROWSER_NODE_DIR(node) ((FileBrowserNodeDir *)(node)) + +#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100 +#define STANDARD_ATTRIBUTE_TYPES G_FILE_ATTRIBUTE_STANDARD_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \ + G_FILE_ATTRIBUTE_STANDARD_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_ICON + +typedef struct _FileBrowserNode FileBrowserNode; +typedef struct _FileBrowserNodeDir FileBrowserNodeDir; +typedef struct _AsyncData AsyncData; +typedef struct _AsyncNode AsyncNode; + +typedef gint (*SortFunc) (FileBrowserNode * node1, + FileBrowserNode * node2); + +struct _AsyncData +{ + GeditFileBrowserStore * model; + GCancellable * cancellable; + gboolean trash; + GList * files; + GList * iter; + gboolean removed; +}; + +struct _AsyncNode +{ + FileBrowserNodeDir *dir; + GCancellable *cancellable; + GSList *original_children; +}; + +typedef struct { + GeditFileBrowserStore * model; + gchar * virtual_root; + GMountOperation * operation; + GCancellable * cancellable; +} MountInfo; + +struct _FileBrowserNode +{ + GFile *file; + guint flags; + gchar *name; + + GdkPixbuf *icon; + GdkPixbuf *emblem; + + FileBrowserNode *parent; + gint pos; + gboolean inserted; +}; + +struct _FileBrowserNodeDir +{ + FileBrowserNode node; + GSList *children; + GHashTable *hidden_file_hash; + + GCancellable *cancellable; + GFileMonitor *monitor; + GeditFileBrowserStore *model; +}; + +struct _GeditFileBrowserStorePrivate +{ + FileBrowserNode *root; + FileBrowserNode *virtual_root; + GType column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NUM]; + + GeditFileBrowserStoreFilterMode filter_mode; + GeditFileBrowserStoreFilterFunc filter_func; + gpointer filter_user_data; + + SortFunc sort_func; + + GSList *async_handles; + MountInfo *mount_info; +}; + +static FileBrowserNode *model_find_node (GeditFileBrowserStore *model, + FileBrowserNode *node, + GFile *uri); +static void model_remove_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath * path, + gboolean free_nodes); + +static void set_virtual_root_from_node (GeditFileBrowserStore * model, + FileBrowserNode * node); + +static void gedit_file_browser_store_iface_init (GtkTreeModelIface * iface); +static GtkTreeModelFlags gedit_file_browser_store_get_flags (GtkTreeModel * tree_model); +static gint gedit_file_browser_store_get_n_columns (GtkTreeModel * tree_model); +static GType gedit_file_browser_store_get_column_type (GtkTreeModel * tree_model, + gint index); +static gboolean gedit_file_browser_store_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreePath * path); +static GtkTreePath *gedit_file_browser_store_get_path (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static void gedit_file_browser_store_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, + gint column, + GValue * value); +static gboolean gedit_file_browser_store_iter_next (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static gboolean gedit_file_browser_store_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent); +static gboolean gedit_file_browser_store_iter_has_child (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static gint gedit_file_browser_store_iter_n_children (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static gboolean gedit_file_browser_store_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent, + gint n); +static gboolean gedit_file_browser_store_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * child); +static void gedit_file_browser_store_row_inserted (GtkTreeModel * tree_model, + GtkTreePath * path, + GtkTreeIter * iter); + +static void gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface * iface); +static gboolean gedit_file_browser_store_row_draggable (GtkTreeDragSource * drag_source, + GtkTreePath * path); +static gboolean gedit_file_browser_store_drag_data_delete (GtkTreeDragSource * drag_source, + GtkTreePath * path); +static gboolean gedit_file_browser_store_drag_data_get (GtkTreeDragSource * drag_source, + GtkTreePath * path, + GtkSelectionData * selection_data); + +static void file_browser_node_free (GeditFileBrowserStore * model, + FileBrowserNode * node); +static void model_add_node (GeditFileBrowserStore * model, + FileBrowserNode * child, + FileBrowserNode * parent); +static void model_clear (GeditFileBrowserStore * model, + gboolean free_nodes); +static gint model_sort_default (FileBrowserNode * node1, + FileBrowserNode * node2); +static void model_check_dummy (GeditFileBrowserStore * model, + FileBrowserNode * node); +static void next_files_async (GFileEnumerator * enumerator, + AsyncNode * async); + +GEDIT_PLUGIN_DEFINE_TYPE_WITH_CODE (GeditFileBrowserStore, gedit_file_browser_store, + G_TYPE_OBJECT, + GEDIT_PLUGIN_IMPLEMENT_INTERFACE (gedit_file_browser_store_tree_model, + GTK_TYPE_TREE_MODEL, + gedit_file_browser_store_iface_init) + GEDIT_PLUGIN_IMPLEMENT_INTERFACE (gedit_file_browser_store_drag_source, + GTK_TYPE_TREE_DRAG_SOURCE, + gedit_file_browser_store_drag_source_init)) + +/* Properties */ +enum { + PROP_0, + + PROP_ROOT, + PROP_VIRTUAL_ROOT, + PROP_FILTER_MODE +}; + +/* Signals */ +enum +{ + BEGIN_LOADING, + END_LOADING, + ERROR, + NO_TRASH, + RENAME, + BEGIN_REFRESH, + END_REFRESH, + UNLOAD, + NUM_SIGNALS +}; + +static guint model_signals[NUM_SIGNALS] = { 0 }; + +static void +cancel_mount_operation (GeditFileBrowserStore *obj) +{ + if (obj->priv->mount_info != NULL) + { + obj->priv->mount_info->model = NULL; + g_cancellable_cancel (obj->priv->mount_info->cancellable); + obj->priv->mount_info = NULL; + } +} + +static void +gedit_file_browser_store_finalize (GObject * object) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + GSList *item; + + /* Free all the nodes */ + file_browser_node_free (obj, obj->priv->root); + + /* Cancel any asynchronous operations */ + for (item = obj->priv->async_handles; item; item = item->next) + { + AsyncData *data = (AsyncData *) (item->data); + g_cancellable_cancel (data->cancellable); + + data->removed = TRUE; + } + + cancel_mount_operation (obj); + + g_slist_free (obj->priv->async_handles); + G_OBJECT_CLASS (gedit_file_browser_store_parent_class)->finalize (object); +} + +static void +set_gvalue_from_node (GValue *value, + FileBrowserNode *node) +{ + gchar * uri; + + if (node == NULL || !node->file) { + g_value_set_string (value, NULL); + } else { + uri = g_file_get_uri (node->file); + g_value_take_string (value, uri); + } +} + +static void +gedit_file_browser_store_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + + switch (prop_id) + { + case PROP_ROOT: + set_gvalue_from_node (value, obj->priv->root); + break; + case PROP_VIRTUAL_ROOT: + set_gvalue_from_node (value, obj->priv->virtual_root); + break; + case PROP_FILTER_MODE: + g_value_set_flags (value, obj->priv->filter_mode); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_store_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + + switch (prop_id) + { + case PROP_FILTER_MODE: + gedit_file_browser_store_set_filter_mode (obj, + g_value_get_flags (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_store_class_init (GeditFileBrowserStoreClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_file_browser_store_finalize; + + object_class->get_property = gedit_file_browser_store_get_property; + object_class->set_property = gedit_file_browser_store_set_property; + + g_object_class_install_property (object_class, PROP_ROOT, + g_param_spec_string ("root", + "Root", + "The root uri", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_VIRTUAL_ROOT, + g_param_spec_string ("virtual-root", + "Virtual Root", + "The virtual root uri", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_FILTER_MODE, + g_param_spec_flags ("filter-mode", + "Filter Mode", + "The filter mode", + GEDIT_TYPE_FILE_BROWSER_STORE_FILTER_MODE, + gedit_file_browser_store_filter_mode_get_default (), + G_PARAM_READWRITE)); + + model_signals[BEGIN_LOADING] = + g_signal_new ("begin-loading", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + begin_loading), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); + model_signals[END_LOADING] = + g_signal_new ("end-loading", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + end_loading), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); + model_signals[ERROR] = + g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + error), NULL, NULL, + gedit_file_browser_marshal_VOID__UINT_STRING, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + model_signals[NO_TRASH] = + g_signal_new ("no-trash", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + no_trash), g_signal_accumulator_true_handled, NULL, + gedit_file_browser_marshal_BOOL__POINTER, + G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); + model_signals[RENAME] = + g_signal_new ("rename", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + rename), NULL, NULL, + gedit_file_browser_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_STRING); + model_signals[BEGIN_REFRESH] = + g_signal_new ("begin-refresh", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + begin_refresh), NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + model_signals[END_REFRESH] = + g_signal_new ("end-refresh", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + end_refresh), NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + model_signals[UNLOAD] = + g_signal_new ("unload", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + unload), NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserStorePrivate)); +} + +static void +gedit_file_browser_store_iface_init (GtkTreeModelIface * iface) +{ + iface->get_flags = gedit_file_browser_store_get_flags; + iface->get_n_columns = gedit_file_browser_store_get_n_columns; + iface->get_column_type = gedit_file_browser_store_get_column_type; + iface->get_iter = gedit_file_browser_store_get_iter; + iface->get_path = gedit_file_browser_store_get_path; + iface->get_value = gedit_file_browser_store_get_value; + iface->iter_next = gedit_file_browser_store_iter_next; + iface->iter_children = gedit_file_browser_store_iter_children; + iface->iter_has_child = gedit_file_browser_store_iter_has_child; + iface->iter_n_children = gedit_file_browser_store_iter_n_children; + iface->iter_nth_child = gedit_file_browser_store_iter_nth_child; + iface->iter_parent = gedit_file_browser_store_iter_parent; + iface->row_inserted = gedit_file_browser_store_row_inserted; +} + +static void +gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface * iface) +{ + iface->row_draggable = gedit_file_browser_store_row_draggable; + iface->drag_data_delete = gedit_file_browser_store_drag_data_delete; + iface->drag_data_get = gedit_file_browser_store_drag_data_get; +} + +static void +gedit_file_browser_store_init (GeditFileBrowserStore * obj) +{ + obj->priv = GEDIT_FILE_BROWSER_STORE_GET_PRIVATE (obj); + + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_URI] = + G_TYPE_STRING; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NAME] = + G_TYPE_STRING; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS] = + G_TYPE_UINT; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_ICON] = + GDK_TYPE_PIXBUF; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM] = + GDK_TYPE_PIXBUF; + + // Default filter mode is hiding the hidden files + obj->priv->filter_mode = gedit_file_browser_store_filter_mode_get_default (); + obj->priv->sort_func = model_sort_default; +} + +static gboolean +node_has_parent (FileBrowserNode * node, FileBrowserNode * parent) +{ + if (node->parent == NULL) + return FALSE; + + if (node->parent == parent) + return TRUE; + + return node_has_parent (node->parent, parent); +} + +static gboolean +node_in_tree (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + return node_has_parent (node, model->priv->virtual_root); +} + +static gboolean +model_node_visibility (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + if (node == NULL) + return FALSE; + + if (NODE_IS_DUMMY (node)) + return !NODE_IS_HIDDEN (node); + + if (node == model->priv->virtual_root) + return TRUE; + + if (!node_has_parent (node, model->priv->virtual_root)) + return FALSE; + + return !NODE_IS_FILTERED (node); +} + +static gboolean +model_node_inserted (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + return node == model->priv->virtual_root || (model_node_visibility (model, node) && node->inserted); +} + +/* Interface implementation */ + +static GtkTreeModelFlags +gedit_file_browser_store_get_flags (GtkTreeModel * tree_model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + (GtkTreeModelFlags) 0); + + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static gint +gedit_file_browser_store_get_n_columns (GtkTreeModel * tree_model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), 0); + + return GEDIT_FILE_BROWSER_STORE_COLUMN_NUM; +} + +static GType +gedit_file_browser_store_get_column_type (GtkTreeModel * tree_model, gint idx) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + G_TYPE_INVALID); + g_return_val_if_fail (idx < GEDIT_FILE_BROWSER_STORE_COLUMN_NUM && + idx >= 0, G_TYPE_INVALID); + + return GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[idx]; +} + +static gboolean +gedit_file_browser_store_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreePath * path) +{ + gint * indices, depth, i; + FileBrowserNode * node; + GeditFileBrowserStore * model; + gint num; + + g_assert (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_assert (path != NULL); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + node = model->priv->virtual_root; + + for (i = 0; i < depth; ++i) { + GSList * item; + + if (node == NULL) + return FALSE; + + num = 0; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) { + FileBrowserNode * child; + + child = (FileBrowserNode *) (item->data); + + if (model_node_inserted (model, child)) { + if (num == indices[i]) { + node = child; + break; + } + + num++; + } + } + + if (item == NULL) + return FALSE; + + node = (FileBrowserNode *) (item->data); + } + + iter->user_data = node; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + + return node != NULL; +} + +static GtkTreePath * +gedit_file_browser_store_get_path_real (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GtkTreePath *path; + gint num = 0; + + path = gtk_tree_path_new (); + + while (node != model->priv->virtual_root) { + GSList *item; + + if (node->parent == NULL) { + gtk_tree_path_free (path); + return NULL; + } + + num = 0; + + for (item = FILE_BROWSER_NODE_DIR (node->parent)->children; item; item = item->next) { + FileBrowserNode *check; + + check = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, check) && (check == node || check->inserted)) { + if (check == node) { + gtk_tree_path_prepend_index (path, + num); + break; + } + + ++num; + } else if (check == node) { + if (NODE_IS_DUMMY (node)) + g_warning ("Dummy not visible???"); + + gtk_tree_path_free (path); + return NULL; + } + } + + node = node->parent; + } + + return path; +} + +static GtkTreePath * +gedit_file_browser_store_get_path (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->user_data != NULL, NULL); + + return gedit_file_browser_store_get_path_real (GEDIT_FILE_BROWSER_STORE (tree_model), + (FileBrowserNode *) (iter->user_data)); +} + +static void +gedit_file_browser_store_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, + gint column, + GValue * value) +{ + FileBrowserNode *node; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *) (iter->user_data); + + g_value_init (value, GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[column]); + + switch (column) { + case GEDIT_FILE_BROWSER_STORE_COLUMN_URI: + set_gvalue_from_node (value, node); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_NAME: + g_value_set_string (value, node->name); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS: + g_value_set_uint (value, node->flags); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_ICON: + g_value_set_object (value, node->icon); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM: + g_value_set_object (value, node->emblem); + break; + default: + g_return_if_reached (); + } +} + +static gboolean +gedit_file_browser_store_iter_next (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + GeditFileBrowserStore * model; + FileBrowserNode * node; + GSList * item; + GSList * first; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + node = (FileBrowserNode *) (iter->user_data); + + if (node->parent == NULL) + return FALSE; + + first = g_slist_next (g_slist_find (FILE_BROWSER_NODE_DIR (node->parent)->children, node)); + + for (item = first; item; item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) { + iter->user_data = item->data; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent) +{ + FileBrowserNode * node; + GeditFileBrowserStore * model; + GSList * item; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (parent == NULL + || parent->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (parent == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (parent->user_data); + + if (node == NULL) + return FALSE; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) { + iter->user_data = item->data; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +filter_tree_model_iter_has_child_real (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GSList *item; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) + return TRUE; + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_has_child (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (iter == NULL + || iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (iter == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (iter->user_data); + + return filter_tree_model_iter_has_child_real (model, node); +} + +static gint +gedit_file_browser_store_iter_n_children (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + GSList *item; + gint num = 0; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (iter == NULL + || iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (iter == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (iter->user_data); + + if (!NODE_IS_DIR (node)) + return 0; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) + ++num; + + return num; +} + +static gboolean +gedit_file_browser_store_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent, gint n) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + GSList *item; + gint num = 0; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (parent == NULL + || parent->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (parent == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (parent->user_data); + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) { + if (num == n) { + iter->user_data = item->data; + return TRUE; + } + + ++num; + } + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * child) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (child != NULL, FALSE); + g_return_val_if_fail (child->user_data != NULL, FALSE); + + node = (FileBrowserNode *) (child->user_data); + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (!node_in_tree (model, node)) + return FALSE; + + if (node->parent == NULL) + return FALSE; + + iter->user_data = node->parent; + return TRUE; +} + +static void +gedit_file_browser_store_row_inserted (GtkTreeModel * tree_model, + GtkTreePath * path, + GtkTreeIter * iter) +{ + FileBrowserNode * node = (FileBrowserNode *)(iter->user_data); + + node->inserted = TRUE; +} + +static gboolean +gedit_file_browser_store_row_draggable (GtkTreeDragSource * drag_source, + GtkTreePath * path) +{ + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), + &iter, path)) + { + return FALSE; + } + + gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + return !FILE_IS_DUMMY(flags); +} + +static gboolean +gedit_file_browser_store_drag_data_delete (GtkTreeDragSource * drag_source, + GtkTreePath * path) +{ + return FALSE; +} + +static gboolean +gedit_file_browser_store_drag_data_get (GtkTreeDragSource * drag_source, + GtkTreePath * path, + GtkSelectionData * selection_data) +{ + GtkTreeIter iter; + gchar *uri; + gchar *uris[2] = {0, }; + gboolean ret; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), + &iter, path)) + { + return FALSE; + } + + gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + g_assert (uri); + + uris[0] = uri; + ret = gtk_selection_data_set_uris (selection_data, uris); + + g_free (uri); + + return ret; +} + +#define FILTER_HIDDEN(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) +#define FILTER_BINARY(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY) + +/* Private */ +static void +model_begin_loading (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + GtkTreeIter iter; + + iter.user_data = node; + g_signal_emit (model, model_signals[BEGIN_LOADING], 0, &iter); +} + +static void +model_end_loading (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + GtkTreeIter iter; + + iter.user_data = node; + g_signal_emit (model, model_signals[END_LOADING], 0, &iter); +} + +static void +model_node_update_visibility (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GtkTreeIter iter; + + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + + if (FILTER_HIDDEN (model->priv->filter_mode) && + NODE_IS_HIDDEN (node)) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + else if (FILTER_BINARY (model->priv->filter_mode) && + (!NODE_IS_TEXT (node) && !NODE_IS_DIR (node))) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + else if (model->priv->filter_func) { + iter.user_data = node; + + if (!model->priv-> + filter_func (model, &iter, + model->priv->filter_user_data)) + node->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + } +} + +static gint +collate_nodes (FileBrowserNode * node1, FileBrowserNode * node2) +{ + if (node1->name == NULL) + return -1; + else if (node2->name == NULL) + return 1; + else { + gchar *k1, *k2; + gint result; + + k1 = g_utf8_collate_key_for_filename (node1->name, -1); + k2 = g_utf8_collate_key_for_filename (node2->name, -1); + + result = strcmp (k1, k2); + + g_free (k1); + g_free (k2); + + return result; + } +} + +static gint +model_sort_default (FileBrowserNode * node1, FileBrowserNode * node2) +{ + gint f1; + gint f2; + + f1 = NODE_IS_DUMMY (node1); + f2 = NODE_IS_DUMMY (node2); + + if (f1 && f2) + { + return 0; + } + else if (f1 || f2) + { + return f1 ? -1 : 1; + } + + f1 = NODE_IS_DIR (node1); + f2 = NODE_IS_DIR (node2); + + if (f1 != f2) + { + return f1 ? -1 : 1; + } + + f1 = NODE_IS_HIDDEN (node1); + f2 = NODE_IS_HIDDEN (node2); + + if (f1 != f2) + { + return f2 ? -1 : 1; + } + + return collate_nodes (node1, node2); +} + +static void +model_resort_node (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + FileBrowserNodeDir *dir; + GSList *item; + FileBrowserNode *child; + gint pos = 0; + GtkTreeIter iter; + GtkTreePath *path; + gint *neworder; + + dir = FILE_BROWSER_NODE_DIR (node->parent); + + if (!model_node_visibility (model, node->parent)) { + /* Just sort the children of the parent */ + dir->children = g_slist_sort (dir->children, + (GCompareFunc) (model->priv-> + sort_func)); + } else { + /* Store current positions */ + for (item = dir->children; item; item = item->next) { + child = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, child)) + child->pos = pos++; + } + + dir->children = g_slist_sort (dir->children, + (GCompareFunc) (model->priv-> + sort_func)); + neworder = g_new (gint, pos); + pos = 0; + + /* Store the new positions */ + for (item = dir->children; item; item = item->next) { + child = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, child)) + neworder[pos++] = child->pos; + } + + iter.user_data = node->parent; + path = + gedit_file_browser_store_get_path_real (model, + node->parent); + + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + path, &iter, neworder); + + g_free (neworder); + gtk_tree_path_free (path); + } +} + +static void +row_changed (GeditFileBrowserStore * model, + GtkTreePath ** path, + GtkTreeIter * iter) +{ + GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path); + + /* Insert a copy of the actual path here because the row-inserted + signal may alter the path */ + gtk_tree_model_row_changed (GTK_TREE_MODEL(model), *path, iter); + gtk_tree_path_free (*path); + + *path = gtk_tree_row_reference_get_path (ref); + gtk_tree_row_reference_free (ref); +} + +static void +row_inserted (GeditFileBrowserStore * model, + GtkTreePath ** path, + GtkTreeIter * iter) +{ + /* This function creates a row reference for the path because it's + uncertain what might change the actual model/view when we insert + a node, maybe another directory load is triggered for example. + Because functions that use this function rely on the notion that + the path remains pointed towards the inserted node, we use the + reference to keep track. */ + GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path); + GtkTreePath * copy = gtk_tree_path_copy (*path); + + gtk_tree_model_row_inserted (GTK_TREE_MODEL(model), copy, iter); + gtk_tree_path_free (copy); + + if (ref) + { + gtk_tree_path_free (*path); + + /* To restore the path, we get the path from the reference. But, since + we inserted a row, the path will be one index further than the + actual path of our node. We therefore call gtk_tree_path_prev */ + *path = gtk_tree_row_reference_get_path (ref); + gtk_tree_path_prev (*path); + } + + gtk_tree_row_reference_free (ref); +} + +static void +row_deleted (GeditFileBrowserStore * model, + const GtkTreePath * path) +{ + GtkTreePath *copy = gtk_tree_path_copy (path); + + /* Delete a copy of the actual path here because the row-deleted + signal may alter the path */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL(model), copy); + gtk_tree_path_free (copy); +} + +static void +model_refilter_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath ** path) +{ + gboolean old_visible; + gboolean new_visible; + FileBrowserNodeDir *dir; + GSList *item; + GtkTreeIter iter; + GtkTreePath *tmppath = NULL; + gboolean in_tree; + + if (node == NULL) + return; + + old_visible = model_node_visibility (model, node); + model_node_update_visibility (model, node); + + in_tree = node_in_tree (model, node); + + if (path == NULL) + { + if (in_tree) + tmppath = gedit_file_browser_store_get_path_real (model, + node); + else + tmppath = gtk_tree_path_new_first (); + + path = &tmppath; + } + + if (NODE_IS_DIR (node)) { + if (in_tree) + gtk_tree_path_down (*path); + + dir = FILE_BROWSER_NODE_DIR (node); + + for (item = dir->children; item; item = item->next) { + model_refilter_node (model, + (FileBrowserNode *) (item->data), + path); + } + + if (in_tree) + gtk_tree_path_up (*path); + } + + if (in_tree) { + new_visible = model_node_visibility (model, node); + + if (old_visible != new_visible) { + if (old_visible) { + node->inserted = FALSE; + row_deleted (model, *path); + } else { + iter.user_data = node; + row_inserted (model, path, &iter); + gtk_tree_path_next (*path); + } + } else if (old_visible) { + gtk_tree_path_next (*path); + } + } + + model_check_dummy (model, node); + + if (tmppath) + gtk_tree_path_free (tmppath); +} + +static void +model_refilter (GeditFileBrowserStore * model) +{ + model_refilter_node (model, model->priv->root, NULL); +} + +static void +file_browser_node_set_name (FileBrowserNode * node) +{ + g_free (node->name); + + if (node->file) { + node->name = gedit_file_browser_utils_file_basename (node->file); + } else { + node->name = NULL; + } +} + +static void +file_browser_node_init (FileBrowserNode * node, GFile * file, + FileBrowserNode * parent) +{ + if (file != NULL) { + node->file = g_object_ref (file); + file_browser_node_set_name (node); + } + + node->parent = parent; +} + +static FileBrowserNode * +file_browser_node_new (GFile * file, FileBrowserNode * parent) +{ + FileBrowserNode *node = g_slice_new0 (FileBrowserNode); + + file_browser_node_init (node, file, parent); + return node; +} + +static FileBrowserNode * +file_browser_node_dir_new (GeditFileBrowserStore * model, + GFile * file, FileBrowserNode * parent) +{ + FileBrowserNode *node = + (FileBrowserNode *) g_slice_new0 (FileBrowserNodeDir); + + file_browser_node_init (node, file, parent); + + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY; + + FILE_BROWSER_NODE_DIR (node)->model = model; + + return node; +} + +static void +file_browser_node_free_children (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GSList *item; + + if (node == NULL) + return; + + if (NODE_IS_DIR (node)) { + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) + file_browser_node_free (model, + (FileBrowserNode *) (item-> + data)); + + g_slist_free (FILE_BROWSER_NODE_DIR (node)->children); + FILE_BROWSER_NODE_DIR (node)->children = NULL; + + /* This node is no longer loaded */ + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + } +} + +static void +file_browser_node_free (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + gchar *uri; + + if (node == NULL) + return; + + if (NODE_IS_DIR (node)) + { + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->cancellable) { + g_cancellable_cancel (dir->cancellable); + g_object_unref (dir->cancellable); + + model_end_loading (model, node); + } + + file_browser_node_free_children (model, node); + + if (dir->monitor) { + g_file_monitor_cancel (dir->monitor); + g_object_unref (dir->monitor); + } + + if (dir->hidden_file_hash) + g_hash_table_destroy (dir->hidden_file_hash); + } + + if (node->file) + { + uri = g_file_get_uri (node->file); + g_signal_emit (model, model_signals[UNLOAD], 0, uri); + + g_free (uri); + g_object_unref (node->file); + } + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) + g_object_unref (node->emblem); + + g_free (node->name); + + if (NODE_IS_DIR (node)) + g_slice_free (FileBrowserNodeDir, (FileBrowserNodeDir *)node); + else + g_slice_free (FileBrowserNode, (FileBrowserNode *)node); +} + +/** + * model_remove_node_children: + * @model: the #GeditFileBrowserStore + * @node: the FileBrowserNode to remove + * @path: the path of the node, or NULL to let the path be calculated + * @free_nodes: whether to also remove the nodes from memory + * + * Removes all the children of node from the model. This function is used + * to remove the child nodes from the _model_. Don't use it to just free + * a node. + **/ +static void +model_remove_node_children (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath * path, + gboolean free_nodes) +{ + FileBrowserNodeDir *dir; + GtkTreePath *path_child; + GSList *list; + GSList *item; + + if (node == NULL || !NODE_IS_DIR (node)) + return; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->children == NULL) + return; + + if (!model_node_visibility (model, node)) { + // Node is invisible and therefore the children can just + // be freed + if (free_nodes) + file_browser_node_free_children (model, node); + + return; + } + + if (path == NULL) + path_child = + gedit_file_browser_store_get_path_real (model, node); + else + path_child = gtk_tree_path_copy (path); + + gtk_tree_path_down (path_child); + + list = g_slist_copy (dir->children); + + for (item = list; item; item = item->next) { + model_remove_node (model, (FileBrowserNode *) (item->data), + path_child, free_nodes); + } + + g_slist_free (list); + gtk_tree_path_free (path_child); +} + +/** + * model_remove_node: + * @model: the #GeditFileBrowserStore + * @node: the FileBrowserNode to remove + * @path: the path to use to remove this node, or NULL to use the path + * calculated from the node itself + * @free_nodes: whether to also remove the nodes from memory + * + * Removes this node and all its children from the model. This function is used + * to remove the node from the _model_. Don't use it to just free + * a node. + **/ +static void +model_remove_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath * path, + gboolean free_nodes) +{ + gboolean free_path = FALSE; + FileBrowserNode *parent; + + if (path == NULL) { + path = + gedit_file_browser_store_get_path_real (model, node); + free_path = TRUE; + } + + model_remove_node_children (model, node, path, free_nodes); + + /* Only delete if the node is visible in the tree (but only when it's + not the virtual root) */ + if (model_node_visibility (model, node) && node != model->priv->virtual_root) + { + node->inserted = FALSE; + row_deleted (model, path); + } + + if (free_path) + gtk_tree_path_free (path); + + parent = node->parent; + + if (free_nodes) { + /* Remove the node from the parents children list */ + if (parent) + FILE_BROWSER_NODE_DIR (node->parent)->children = + g_slist_remove (FILE_BROWSER_NODE_DIR + (node->parent)->children, + node); + } + + /* If this is the virtual root, than set the parent as the virtual root */ + if (node == model->priv->virtual_root) + set_virtual_root_from_node (model, parent); + else if (parent && model_node_visibility (model, parent) && !(free_nodes && NODE_IS_DUMMY(node))) + model_check_dummy (model, parent); + + /* Now free the node if necessary */ + if (free_nodes) + file_browser_node_free (model, node); +} + +/** + * model_clear: + * @model: the #GeditFileBrowserStore + * @free_nodes: whether to also remove the nodes from memory + * + * Removes all nodes from the model. This function is used + * to remove all the nodes from the _model_. Don't use it to just free the + * nodes in the model. + **/ +static void +model_clear (GeditFileBrowserStore * model, gboolean free_nodes) +{ + GtkTreePath *path; + FileBrowserNodeDir *dir; + FileBrowserNode *dummy; + + path = gtk_tree_path_new (); + model_remove_node_children (model, model->priv->virtual_root, path, + free_nodes); + gtk_tree_path_free (path); + + /* Remove the dummy if there is one */ + if (model->priv->virtual_root) { + dir = FILE_BROWSER_NODE_DIR (model->priv->virtual_root); + + if (dir->children != NULL) { + dummy = (FileBrowserNode *) (dir->children->data); + + if (NODE_IS_DUMMY (dummy) + && model_node_visibility (model, dummy)) { + path = gtk_tree_path_new_first (); + + dummy->inserted = FALSE; + row_deleted (model, path); + gtk_tree_path_free (path); + } + } + } +} + +static void +file_browser_node_unload (GeditFileBrowserStore * model, + FileBrowserNode * node, gboolean remove_children) +{ + FileBrowserNodeDir *dir; + + if (node == NULL) + return; + + if (!NODE_IS_DIR (node) || !NODE_LOADED (node)) + return; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (remove_children) + model_remove_node_children (model, node, NULL, TRUE); + + if (dir->cancellable) { + g_cancellable_cancel (dir->cancellable); + g_object_unref (dir->cancellable); + + model_end_loading (model, node); + dir->cancellable = NULL; + } + + if (dir->monitor) { + g_file_monitor_cancel (dir->monitor); + g_object_unref (dir->monitor); + + dir->monitor = NULL; + } + + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; +} + +static void +model_recomposite_icon_real (GeditFileBrowserStore * tree_model, + FileBrowserNode * node, + GFileInfo * info) +{ + GdkPixbuf *icon; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (node != NULL); + + if (node->file == NULL) + return; + + if (info) { + GIcon *gicon = g_file_info_get_icon (info); + if (gicon != NULL) + icon = gedit_file_browser_utils_pixbuf_from_icon (gicon, GTK_ICON_SIZE_MENU); + else + icon = NULL; + } else { + icon = gedit_file_browser_utils_pixbuf_from_file (node->file, GTK_ICON_SIZE_MENU); + } + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) { + gint icon_size; + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size); + + if (icon == NULL) { + node->icon = + gdk_pixbuf_new (gdk_pixbuf_get_colorspace (node->emblem), + gdk_pixbuf_get_has_alpha (node->emblem), + gdk_pixbuf_get_bits_per_sample (node->emblem), + icon_size, + icon_size); + } else { + node->icon = gdk_pixbuf_copy (icon); + g_object_unref (icon); + } + + gdk_pixbuf_composite (node->emblem, node->icon, + icon_size - 10, icon_size - 10, 10, + 10, icon_size - 10, icon_size - 10, + 1, 1, GDK_INTERP_NEAREST, 255); + } else { + node->icon = icon; + } +} + +static void +model_recomposite_icon (GeditFileBrowserStore * tree_model, + GtkTreeIter * iter) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + model_recomposite_icon_real (tree_model, + (FileBrowserNode *) (iter->user_data), + NULL); +} + +static FileBrowserNode * +model_create_dummy_node (GeditFileBrowserStore * model, + FileBrowserNode * parent) +{ + FileBrowserNode *dummy; + + dummy = file_browser_node_new (NULL, parent); + dummy->name = g_strdup (_("(Empty)")); + + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY; + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + return dummy; +} + +static FileBrowserNode * +model_add_dummy_node (GeditFileBrowserStore * model, + FileBrowserNode * parent) +{ + FileBrowserNode *dummy; + + dummy = model_create_dummy_node (model, parent); + + if (model_node_visibility (model, parent)) + dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + model_add_node (model, dummy, parent); + + return dummy; +} + +static void +model_check_dummy (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + // Hide the dummy child if needed + if (NODE_IS_DIR (node)) { + FileBrowserNode *dummy; + GtkTreeIter iter; + GtkTreePath *path; + guint flags; + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->children == NULL) { + model_add_dummy_node (model, node); + return; + } + + dummy = (FileBrowserNode *) (dir->children->data); + + if (!NODE_IS_DUMMY (dummy)) { + dummy = model_create_dummy_node (model, node); + dir->children = g_slist_prepend (dir->children, dummy); + } + + if (!model_node_visibility (model, node)) { + dummy->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + return; + } + + /* Temporarily set the node to invisible to check + * for real children */ + flags = dummy->flags; + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (!filter_tree_model_iter_has_child_real (model, node)) { + dummy->flags &= + ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (FILE_IS_HIDDEN (flags)) { + // Was hidden, needs to be inserted + iter.user_data = dummy; + path = + gedit_file_browser_store_get_path_real + (model, dummy); + + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + } else { + if (!FILE_IS_HIDDEN (flags)) { + // Was shown, needs to be removed + + // To get the path we need to set it to visible temporarily + dummy->flags &= + ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + path = + gedit_file_browser_store_get_path_real + (model, dummy); + dummy->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + dummy->inserted = FALSE; + row_deleted (model, path); + gtk_tree_path_free (path); + } + } + } +} + +static void +insert_node_sorted (GeditFileBrowserStore * model, + FileBrowserNode * child, + FileBrowserNode * parent) +{ + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (parent); + + if (model->priv->sort_func == NULL) { + dir->children = g_slist_append (dir->children, child); + } else { + dir->children = + g_slist_insert_sorted (dir->children, child, + (GCompareFunc) (model->priv-> + sort_func)); + } +} + +static void +model_add_node (GeditFileBrowserStore * model, FileBrowserNode * child, + FileBrowserNode * parent) +{ + /* Add child to parents children */ + insert_node_sorted (model, child, parent); + + if (model_node_visibility (model, parent) && + model_node_visibility (model, child)) { + GtkTreeIter iter; + GtkTreePath *path; + + iter.user_data = child; + path = gedit_file_browser_store_get_path_real (model, child); + + /* Emit row inserted */ + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, parent); + model_check_dummy (model, child); +} + +static void +model_add_nodes_batch (GeditFileBrowserStore * model, + GSList * children, + FileBrowserNode * parent) +{ + GSList *sorted_children; + GSList *child; + GSList *prev; + GSList *l; + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (parent); + + sorted_children = g_slist_sort (children, (GCompareFunc) model->priv->sort_func); + + child = sorted_children; + l = dir->children; + prev = NULL; + + model_check_dummy (model, parent); + + while (child) { + FileBrowserNode *node = child->data; + GtkTreeIter iter; + GtkTreePath *path; + + /* reached the end of the first list, just append the second */ + if (l == NULL) { + + dir->children = g_slist_concat (dir->children, child); + + for (l = child; l; l = l->next) { + if (model_node_visibility (model, parent) && + model_node_visibility (model, l->data)) { + iter.user_data = l->data; + path = gedit_file_browser_store_get_path_real (model, l->data); + + // Emit row inserted + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, l->data); + } + + break; + } + + if (model->priv->sort_func (l->data, node) > 0) { + GSList *next_child; + + if (prev == NULL) { + /* prepend to the list */ + dir->children = g_slist_prepend (dir->children, child); + } else { + prev->next = child; + } + + next_child = child->next; + prev = child; + child->next = l; + child = next_child; + + if (model_node_visibility (model, parent) && + model_node_visibility (model, node)) { + iter.user_data = node; + path = gedit_file_browser_store_get_path_real (model, node); + + // Emit row inserted + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, node); + + /* try again at the same l position with the + * next child */ + } else { + + /* Move to the next item in the list */ + prev = l; + l = l->next; + } + } +} + +static gchar const * +backup_content_type (GFileInfo * info) +{ + gchar const * content; + + if (!g_file_info_get_is_backup (info)) + return NULL; + + content = g_file_info_get_content_type (info); + + if (!content || g_content_type_equals (content, "application/x-trash")) + return "text/plain"; + + return content; +} + +static void +file_browser_node_set_from_info (GeditFileBrowserStore * model, + FileBrowserNode * node, + GFileInfo * info, + gboolean isadded) +{ + FileBrowserNodeDir * dir; + gchar const * content; + gchar const * name; + gboolean free_info = FALSE; + GtkTreePath * path; + gchar * uri; + GError * error = NULL; + + if (info == NULL) { + info = g_file_query_info (node->file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + + if (!info) { + if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)) { + uri = g_file_get_uri (node->file); + g_warning ("Could not get info for %s: %s", uri, error->message); + g_free (uri); + } + g_error_free (error); + + return; + } + + free_info = TRUE; + } + + dir = FILE_BROWSER_NODE_DIR (node->parent); + name = g_file_info_get_name (info); + + if (g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info)) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + else if (dir != NULL && dir->hidden_file_hash != NULL && + g_hash_table_lookup (dir->hidden_file_hash, name) != NULL) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY; + else { + if (!(content = backup_content_type (info))) + content = g_file_info_get_content_type (info); + + if (!content || + g_content_type_is_unknown (content) || + g_content_type_is_a (content, "text/plain")) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT; + } + + model_recomposite_icon_real (model, node, info); + + if (free_info) + g_object_unref (info); + + if (isadded) { + path = gedit_file_browser_store_get_path_real (model, node); + model_refilter_node (model, node, &path); + gtk_tree_path_free (path); + + model_check_dummy (model, node->parent); + } else { + model_node_update_visibility (model, node); + } +} + +static FileBrowserNode * +node_list_contains_file (GSList *children, GFile * file) +{ + GSList *item; + + for (item = children; item; item = item->next) { + FileBrowserNode *node; + + node = (FileBrowserNode *) (item->data); + + if (node->file != NULL + && g_file_equal (node->file, file)) + return node; + } + + return NULL; +} + +static FileBrowserNode * +model_add_node_from_file (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GFile * file, + GFileInfo * info) +{ + FileBrowserNode *node; + gboolean free_info = FALSE; + GError * error = NULL; + + if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) { + if (info == NULL) { + info = g_file_query_info (file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + free_info = TRUE; + } + + if (!info) { + g_warning ("Error querying file info: %s", error->message); + g_error_free (error); + + /* FIXME: What to do now then... */ + node = file_browser_node_new (file, parent); + } else if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + node = file_browser_node_dir_new (model, file, parent); + } else { + node = file_browser_node_new (file, parent); + } + + file_browser_node_set_from_info (model, node, info, FALSE); + model_add_node (model, node, parent); + + if (info && free_info) + g_object_unref (info); + } + + return node; +} + +/* We pass in a copy of the list of parent->children so that we do + * not have to check if a file already exists among the ones we just + * added */ +static void +model_add_nodes_from_files (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GSList * original_children, + GList * files) +{ + GList *item; + GSList *nodes = NULL; + + for (item = files; item; item = item->next) { + GFileInfo *info = G_FILE_INFO (item->data); + GFileType type; + gchar const * name; + GFile * file; + FileBrowserNode *node; + + type = g_file_info_get_file_type (info); + + /* Skip all non regular, non directory files */ + if (type != G_FILE_TYPE_REGULAR && + type != G_FILE_TYPE_DIRECTORY && + type != G_FILE_TYPE_SYMBOLIC_LINK) { + g_object_unref (info); + continue; + } + + name = g_file_info_get_name (info); + + /* Skip '.' and '..' directories */ + if (type == G_FILE_TYPE_DIRECTORY && + (strcmp (name, ".") == 0 || + strcmp (name, "..") == 0)) { + continue; + } + + file = g_file_get_child (parent->file, name); + + if ((node = node_list_contains_file (original_children, file)) == NULL) { + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + node = file_browser_node_dir_new (model, file, parent); + } else { + node = file_browser_node_new (file, parent); + } + + file_browser_node_set_from_info (model, node, info, FALSE); + + nodes = g_slist_prepend (nodes, node); + } + + g_object_unref (file); + g_object_unref (info); + } + + if (nodes) + model_add_nodes_batch (model, nodes, parent); +} + +static FileBrowserNode * +model_add_node_from_dir (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GFile * file) +{ + FileBrowserNode *node; + + /* Check if it already exists */ + if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) { + node = file_browser_node_dir_new (model, file, parent); + file_browser_node_set_from_info (model, node, NULL, FALSE); + + if (node->name == NULL) { + file_browser_node_set_name (node); + } + + if (node->icon == NULL) { + node->icon = gedit_file_browser_utils_pixbuf_from_theme ("folder", GTK_ICON_SIZE_MENU); + } + + model_add_node (model, node, parent); + } + + return node; +} + +/* Read is sync, but we only do it for local files */ +static void +parse_dot_hidden_file (FileBrowserNode *directory) +{ + gsize file_size; + char *file_contents; + GFile *child; + GFileInfo *info; + GFileType type; + int i; + FileBrowserNodeDir * dir = FILE_BROWSER_NODE_DIR (directory); + + /* FIXME: We only support .hidden on file: uri's for the moment. + * Need to figure out if we should do this async or sync to extend + * it to all types of uris. + */ + if (directory->file == NULL || !g_file_is_native (directory->file)) { + return; + } + + child = g_file_get_child (directory->file, ".hidden"); + info = g_file_query_info (child, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); + + type = info ? g_file_info_get_file_type (info) : G_FILE_TYPE_UNKNOWN; + + if (info) + g_object_unref (info); + + if (type != G_FILE_TYPE_REGULAR) { + g_object_unref (child); + + return; + } + + if (!g_file_load_contents (child, NULL, &file_contents, &file_size, NULL, NULL)) { + g_object_unref (child); + return; + } + + g_object_unref (child); + + if (dir->hidden_file_hash == NULL) { + dir->hidden_file_hash = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + } + + /* Now parse the data */ + i = 0; + while (i < file_size) { + int start; + + start = i; + while (i < file_size && file_contents[i] != '\n') { + i++; + } + + if (i > start) { + char *hidden_filename; + + hidden_filename = g_strndup (file_contents + start, i - start); + g_hash_table_insert (dir->hidden_file_hash, + hidden_filename, hidden_filename); + } + + i++; + + } + + g_free (file_contents); +} + +static void +on_directory_monitor_event (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + FileBrowserNode * parent) +{ + FileBrowserNode *node; + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: + node = node_list_contains_file (dir->children, file); + + if (node != NULL) { + model_remove_node (dir->model, node, NULL, TRUE); + } + break; + case G_FILE_MONITOR_EVENT_CREATED: + if (g_file_query_exists (file, NULL)) { + model_add_node_from_file (dir->model, parent, file, NULL); + } + + break; + default: + break; + } +} + +static void +async_node_free (AsyncNode *async) +{ + g_object_unref (async->cancellable); + g_slist_free (async->original_children); + g_free (async); +} + +static void +model_iterate_next_files_cb (GFileEnumerator * enumerator, + GAsyncResult * result, + AsyncNode * async) +{ + GList * files; + GError * error = NULL; + FileBrowserNodeDir * dir = async->dir; + FileBrowserNode * parent = (FileBrowserNode *)dir; + + files = g_file_enumerator_next_files_finish (enumerator, result, &error); + + if (files == NULL) { + g_file_enumerator_close (enumerator, NULL, NULL); + async_node_free (async); + + if (!error) + { + /* We're done loading */ + g_object_unref (dir->cancellable); + dir->cancellable = NULL; + +/* + * FIXME: This is temporarly, it is a bug in gio: + * http://bugzilla.mate.org/show_bug.cgi?id=565924 + */ +#ifndef G_OS_WIN32 + if (g_file_is_native (parent->file) && dir->monitor == NULL) { + dir->monitor = g_file_monitor_directory (parent->file, + G_FILE_MONITOR_NONE, + NULL, + NULL); + if (dir->monitor != NULL) + { + g_signal_connect (dir->monitor, + "changed", + G_CALLBACK (on_directory_monitor_event), + parent); + } + } +#endif + + model_check_dummy (dir->model, parent); + model_end_loading (dir->model, parent); + } else { + /* Simply return if we were cancelled */ + if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) + return; + + /* Otherwise handle the error appropriately */ + g_signal_emit (dir->model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + error->message); + + file_browser_node_unload (dir->model, (FileBrowserNode *)parent, TRUE); + g_error_free (error); + } + } else if (g_cancellable_is_cancelled (async->cancellable)) { + /* Check cancel state manually */ + g_file_enumerator_close (enumerator, NULL, NULL); + async_node_free (async); + } else { + model_add_nodes_from_files (dir->model, parent, async->original_children, files); + + g_list_free (files); + next_files_async (enumerator, async); + } +} + +static void +next_files_async (GFileEnumerator * enumerator, + AsyncNode * async) +{ + g_file_enumerator_next_files_async (enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + async->cancellable, + (GAsyncReadyCallback)model_iterate_next_files_cb, + async); +} + +static void +model_iterate_children_cb (GFile * file, + GAsyncResult * result, + AsyncNode * async) +{ + GError * error = NULL; + GFileEnumerator * enumerator; + + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_node_free (async); + return; + } + + enumerator = g_file_enumerate_children_finish (file, result, &error); + + if (enumerator == NULL) { + /* Simply return if we were cancelled or if the dir is not there */ + FileBrowserNodeDir *dir = async->dir; + + /* Otherwise handle the error appropriately */ + g_signal_emit (dir->model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + error->message); + + file_browser_node_unload (dir->model, (FileBrowserNode *)dir, TRUE); + g_error_free (error); + async_node_free (async); + } else { + next_files_async (enumerator, async); + } +} + +static void +model_load_directory (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + FileBrowserNodeDir *dir; + AsyncNode *async; + + g_return_if_fail (NODE_IS_DIR (node)); + + dir = FILE_BROWSER_NODE_DIR (node); + + /* Cancel a previous load */ + if (dir->cancellable != NULL) { + file_browser_node_unload (dir->model, node, TRUE); + } + + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + model_begin_loading (model, node); + + /* Read the '.hidden' file first (if any) */ + parse_dot_hidden_file (node); + + dir->cancellable = g_cancellable_new (); + + async = g_new (AsyncNode, 1); + async->dir = dir; + async->cancellable = g_object_ref (dir->cancellable); + async->original_children = g_slist_copy (dir->children); + + /* Start loading async */ + g_file_enumerate_children_async (node->file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + async->cancellable, + (GAsyncReadyCallback)model_iterate_children_cb, + async); +} + +static GList * +get_parent_files (GeditFileBrowserStore * model, GFile * file) +{ + GList * result = NULL; + + result = g_list_prepend (result, g_object_ref (file)); + + while ((file = g_file_get_parent (file))) { + if (g_file_equal (file, model->priv->root->file)) { + g_object_unref (file); + break; + } + + result = g_list_prepend (result, file); + } + + return result; +} + +static void +model_fill (GeditFileBrowserStore * model, FileBrowserNode * node, + GtkTreePath ** path) +{ + gboolean free_path = FALSE; + GtkTreeIter iter = {0,}; + GSList *item; + FileBrowserNode *child; + + if (node == NULL) { + node = model->priv->virtual_root; + *path = gtk_tree_path_new (); + free_path = TRUE; + } + + if (*path == NULL) { + *path = + gedit_file_browser_store_get_path_real (model, node); + free_path = TRUE; + } + + if (!model_node_visibility (model, node)) { + if (free_path) + gtk_tree_path_free (*path); + + return; + } + + if (node != model->priv->virtual_root) { + /* Insert node */ + iter.user_data = node; + + row_inserted(model, path, &iter); + } + + if (NODE_IS_DIR (node)) { + /* Go to the first child */ + gtk_tree_path_down (*path); + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + child = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, child)) { + model_fill (model, child, path); + + /* Increase path for next child */ + gtk_tree_path_next (*path); + } + } + + /* Move back up to node path */ + gtk_tree_path_up (*path); + } + + model_check_dummy (model, node); + + if (free_path) + gtk_tree_path_free (*path); +} + +static void +set_virtual_root_from_node (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + FileBrowserNode *next; + FileBrowserNode *prev; + FileBrowserNode *check; + FileBrowserNodeDir *dir; + GSList *item; + GSList *copy; + GtkTreePath *empty = NULL; + + prev = node; + next = prev->parent; + + /* Free all the nodes below that we don't need in cache */ + while (prev != model->priv->root) { + dir = FILE_BROWSER_NODE_DIR (next); + copy = g_slist_copy (dir->children); + + for (item = copy; item; item = item->next) { + check = (FileBrowserNode *) (item->data); + + if (prev == node) { + /* Only free the children, keeping this depth in cache */ + if (check != node) { + file_browser_node_free_children + (model, check); + file_browser_node_unload (model, + check, + FALSE); + } + } else if (check != prev) { + /* Only free when the node is not in the chain */ + dir->children = + g_slist_remove (dir->children, check); + file_browser_node_free (model, check); + } + } + + if (prev != node) + file_browser_node_unload (model, next, FALSE); + + g_slist_free (copy); + prev = next; + next = prev->parent; + } + + /* Free all the nodes up that we don't need in cache */ + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + check = (FileBrowserNode *) (item->data); + + if (NODE_IS_DIR (check)) { + for (copy = + FILE_BROWSER_NODE_DIR (check)->children; copy; + copy = copy->next) { + file_browser_node_free_children (model, + (FileBrowserNode + *) + (copy-> + data)); + file_browser_node_unload (model, + (FileBrowserNode + *) (copy->data), + FALSE); + } + } else if (NODE_IS_DUMMY (check)) { + check->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + } + } + + /* Now finally, set the virtual root, and load it up! */ + model->priv->virtual_root = node; + + /* Notify that the virtual-root has changed before loading up new nodes so that the + "root_changed" signal can be emitted before any "inserted" signals */ + g_object_notify (G_OBJECT (model), "virtual-root"); + + model_fill (model, NULL, &empty); + + if (!NODE_LOADED (node)) + model_load_directory (model, node); +} + +static void +set_virtual_root_from_file (GeditFileBrowserStore * model, + GFile * file) +{ + GList * files; + GList * item; + FileBrowserNode * parent; + GFile * check; + + /* Always clear the model before altering the nodes */ + model_clear (model, FALSE); + + /* Create the node path, get all the uri's */ + files = get_parent_files (model, file); + parent = model->priv->root; + + for (item = files; item; item = item->next) { + check = G_FILE (item->data); + + parent = model_add_node_from_dir (model, parent, check); + g_object_unref (check); + } + + g_list_free (files); + set_virtual_root_from_node (model, parent); +} + +static FileBrowserNode * +model_find_node_children (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GFile * file) +{ + FileBrowserNodeDir *dir; + FileBrowserNode *child; + FileBrowserNode *result; + GSList *children; + + if (!NODE_IS_DIR (parent)) + return NULL; + + dir = FILE_BROWSER_NODE_DIR (parent); + + for (children = dir->children; children; children = children->next) { + child = (FileBrowserNode *)(children->data); + + result = model_find_node (model, child, file); + + if (result) + return result; + } + + return NULL; +} + +static FileBrowserNode * +model_find_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GFile * file) +{ + if (node == NULL) + node = model->priv->root; + + if (node->file && g_file_equal (node->file, file)) + return node; + + if (NODE_IS_DIR (node) && g_file_has_prefix (file, node->file)) + return model_find_node_children (model, node, file); + + return NULL; +} + +static GQuark +gedit_file_browser_store_error_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) { + quark = g_quark_from_string ("gedit_file_browser_store_error"); + } + + return quark; +} + +static GFile * +unique_new_name (GFile * directory, gchar const * name) +{ + GFile * newuri = NULL; + guint num = 0; + gchar * newname; + + while (newuri == NULL || g_file_query_exists (newuri, NULL)) { + if (newuri != NULL) + g_object_unref (newuri); + + if (num == 0) + newname = g_strdup (name); + else + newname = g_strdup_printf ("%s(%d)", name, num); + + newuri = g_file_get_child (directory, newname); + g_free (newname); + + ++num; + } + + return newuri; +} + +static GeditFileBrowserStoreResult +model_root_mounted (GeditFileBrowserStore * model, gchar const * virtual_root) +{ + model_check_dummy (model, model->priv->root); + g_object_notify (G_OBJECT (model), "root"); + + if (virtual_root != NULL) + return + gedit_file_browser_store_set_virtual_root_from_string + (model, virtual_root); + else + set_virtual_root_from_node (model, + model->priv->root); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +static void +handle_root_error (GeditFileBrowserStore * model, GError *error) +{ + FileBrowserNode * root; + + g_signal_emit (model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + error->message); + + /* Set the virtual root to the root */ + root = model->priv->root; + model->priv->virtual_root = root; + + /* Set the root to be loaded */ + root->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + + /* Check the dummy */ + model_check_dummy (model, root); + + g_object_notify (G_OBJECT (model), "root"); + g_object_notify (G_OBJECT (model), "virtual-root"); +} + +static void +mount_cb (GFile * file, + GAsyncResult * res, + MountInfo * mount_info) +{ + gboolean mounted; + GError * error = NULL; + GeditFileBrowserStore * model = mount_info->model; + + mounted = g_file_mount_enclosing_volume_finish (file, res, &error); + + if (mount_info->model) + { + model->priv->mount_info = NULL; + model_end_loading (model, model->priv->root); + } + + if (!mount_info->model || g_cancellable_is_cancelled (mount_info->cancellable)) + { + // Reset because it might be reused? + g_cancellable_reset (mount_info->cancellable); + } + else if (mounted) + { + model_root_mounted (model, mount_info->virtual_root); + } + else if (error->code != G_IO_ERROR_CANCELLED) + { + handle_root_error (model, error); + } + + if (error) + g_error_free (error); + + g_object_unref (mount_info->operation); + g_object_unref (mount_info->cancellable); + g_free (mount_info->virtual_root); + + g_free (mount_info); +} + +static GeditFileBrowserStoreResult +model_mount_root (GeditFileBrowserStore * model, gchar const * virtual_root) +{ + GFileInfo * info; + GError * error = NULL; + MountInfo * mount_info; + + info = g_file_query_info (model->priv->root->file, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + + if (!info) { + if (error->code == G_IO_ERROR_NOT_MOUNTED) { + /* Try to mount it */ + FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable = g_cancellable_new (); + + mount_info = g_new(MountInfo, 1); + mount_info->model = model; + mount_info->virtual_root = g_strdup (virtual_root); + + /* FIXME: we should be setting the correct window */ + mount_info->operation = gtk_mount_operation_new (NULL); + mount_info->cancellable = g_object_ref (FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable); + + model_begin_loading (model, model->priv->root); + g_file_mount_enclosing_volume (model->priv->root->file, + G_MOUNT_MOUNT_NONE, + mount_info->operation, + mount_info->cancellable, + (GAsyncReadyCallback)mount_cb, + mount_info); + + model->priv->mount_info = mount_info; + return GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING; + } + else + { + handle_root_error (model, error); + } + + g_error_free (error); + } else { + g_object_unref (info); + + return model_root_mounted (model, virtual_root); + } + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +/* Public */ +GeditFileBrowserStore * +gedit_file_browser_store_new (gchar const *root) +{ + GeditFileBrowserStore *obj = + GEDIT_FILE_BROWSER_STORE (g_object_new + (GEDIT_TYPE_FILE_BROWSER_STORE, + NULL)); + + gedit_file_browser_store_set_root (obj, root); + return obj; +} + +void +gedit_file_browser_store_set_value (GeditFileBrowserStore * tree_model, + GtkTreeIter * iter, gint column, + GValue * value) +{ + gpointer data; + FileBrowserNode *node; + GtkTreePath *path; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (column == + GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM); + g_return_if_fail (G_VALUE_HOLDS_OBJECT (value)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + data = g_value_get_object (value); + + if (data) + g_return_if_fail (GDK_IS_PIXBUF (data)); + + node = (FileBrowserNode *) (iter->user_data); + + if (node->emblem) + g_object_unref (node->emblem); + + if (data) + node->emblem = g_object_ref (GDK_PIXBUF (data)); + else + node->emblem = NULL; + + model_recomposite_icon (tree_model, iter); + + if (model_node_visibility (tree_model, node)) { + path = gedit_file_browser_store_get_path (GTK_TREE_MODEL (tree_model), + iter); + row_changed (tree_model, &path, iter); + gtk_tree_path_free (path); + } +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter != NULL, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter->user_data != NULL, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + model_clear (model, FALSE); + set_virtual_root_from_node (model, + (FileBrowserNode *) (iter->user_data)); + + return TRUE; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_from_string + (GeditFileBrowserStore * model, gchar const *root) { + GFile *file; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + file = g_file_new_for_uri (root); + if (file == NULL) { + g_warning ("Invalid uri (%s)", root); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* Check if uri is already the virtual root */ + if (model->priv->virtual_root && + g_file_equal (model->priv->virtual_root->file, file)) { + g_object_unref (file); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* Check if uri is the root itself */ + if (g_file_equal (model->priv->root->file, file)) { + g_object_unref (file); + + /* Always clear the model before altering the nodes */ + model_clear (model, FALSE); + set_virtual_root_from_node (model, model->priv->root); + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; + } + + if (!g_file_has_prefix (file, model->priv->root->file)) { + gchar *str, *str1; + + str = g_file_get_parse_name (model->priv->root->file); + str1 = g_file_get_parse_name (file); + + g_warning + ("Virtual root (%s) is not below actual root (%s)", + str1, str); + + g_free (str); + g_free (str1); + + g_object_unref (file); + return GEDIT_FILE_BROWSER_STORE_RESULT_ERROR; + } + + set_virtual_root_from_file (model, file); + g_object_unref (file); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore * + model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (model->priv->virtual_root == model->priv->root) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + model_clear (model, FALSE); + set_virtual_root_from_node (model, model->priv->root); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore * + model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (model->priv->virtual_root == model->priv->root) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + model_clear (model, FALSE); + set_virtual_root_from_node (model, + model->priv->virtual_root->parent); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +gboolean +gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore * + model, GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + if (model->priv->virtual_root == NULL) + return FALSE; + + iter->user_data = model->priv->virtual_root; + return TRUE; +} + +gboolean +gedit_file_browser_store_get_iter_root (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + if (model->priv->root == NULL) + return FALSE; + + iter->user_data = model->priv->root; + return TRUE; +} + +gboolean +gedit_file_browser_store_iter_equal (GeditFileBrowserStore * model, + GtkTreeIter * iter1, + GtkTreeIter * iter2) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter1 != NULL, FALSE); + g_return_val_if_fail (iter2 != NULL, FALSE); + g_return_val_if_fail (iter1->user_data != NULL, FALSE); + g_return_val_if_fail (iter2->user_data != NULL, FALSE); + + return (iter1->user_data == iter2->user_data); +} + +void +gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (store)); + + cancel_mount_operation (store); +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore * + model, + gchar const *root, + gchar const *virtual_root) +{ + GFile * file = NULL; + GFile * vfile = NULL; + FileBrowserNode * node; + gboolean equal = FALSE; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (root == NULL && model->priv->root == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + if (root != NULL) { + file = g_file_new_for_uri (root); + } + + if (root != NULL && model->priv->root != NULL) { + equal = g_file_equal (file, model->priv->root->file); + + if (equal && virtual_root == NULL) { + g_object_unref (file); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + } + + if (virtual_root) { + vfile = g_file_new_for_uri (virtual_root); + + if (equal && g_file_equal (vfile, model->priv->virtual_root->file)) { + if (file) + g_object_unref (file); + + g_object_unref (vfile); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + g_object_unref (vfile); + } + + /* make sure to cancel any previous mount operations */ + cancel_mount_operation (model); + + /* Always clear the model before altering the nodes */ + model_clear (model, TRUE); + file_browser_node_free (model, model->priv->root); + + model->priv->root = NULL; + model->priv->virtual_root = NULL; + + if (file != NULL) { + /* Create the root node */ + node = file_browser_node_dir_new (model, file, NULL); + + g_object_unref (file); + + model->priv->root = node; + return model_mount_root (model, virtual_root); + } else { + g_object_notify (G_OBJECT (model), "root"); + g_object_notify (G_OBJECT (model), "virtual-root"); + } + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root (GeditFileBrowserStore * model, + gchar const *root) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + return gedit_file_browser_store_set_root_and_virtual_root (model, + root, + NULL); +} + +gchar * +gedit_file_browser_store_get_root (GeditFileBrowserStore * model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL); + + if (model->priv->root == NULL || model->priv->root->file == NULL) + return NULL; + else + return g_file_get_uri (model->priv->root->file); +} + +gchar * +gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore * model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL); + + if (model->priv->virtual_root == NULL || model->priv->virtual_root->file == NULL) + return NULL; + else + return g_file_get_uri (model->priv->virtual_root->file); +} + +void +_gedit_file_browser_store_iter_expanded (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *) (iter->user_data); + + if (NODE_IS_DIR (node) && !NODE_LOADED (node)) { + /* Load it now */ + model_load_directory (model, node); + } +} + +void +_gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + GSList *item; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *) (iter->user_data); + + if (NODE_IS_DIR (node) && NODE_LOADED (node)) { + /* Unload children of the children, keeping 1 depth in cache */ + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + node = (FileBrowserNode *) (item->data); + + if (NODE_IS_DIR (node) && NODE_LOADED (node)) { + file_browser_node_unload (model, node, + TRUE); + model_check_dummy (model, node); + } + } + } +} + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore * model) +{ + return model->priv->filter_mode; +} + +void +gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterMode + mode) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->filter_mode == mode) + return; + + model->priv->filter_mode = mode; + model_refilter (model); + + g_object_notify (G_OBJECT (model), "filter-mode"); +} + +void +gedit_file_browser_store_set_filter_func (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterFunc + func, gpointer user_data) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + model->priv->filter_func = func; + model->priv->filter_user_data = user_data; + model_refilter (model); +} + +void +gedit_file_browser_store_refilter (GeditFileBrowserStore * model) +{ + model_refilter (model); +} + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_filter_mode_get_default (void) +{ + return GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; +} + +void +gedit_file_browser_store_refresh (GeditFileBrowserStore * model) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->root == NULL || model->priv->virtual_root == NULL) + return; + + /* Clear the model */ + g_signal_emit (model, model_signals[BEGIN_REFRESH], 0); + file_browser_node_unload (model, model->priv->virtual_root, TRUE); + model_load_directory (model, model->priv->virtual_root); + g_signal_emit (model, model_signals[END_REFRESH], 0); +} + +static void +reparent_node (FileBrowserNode * node, gboolean reparent) +{ + FileBrowserNodeDir * dir; + GSList * child; + GFile * parent; + gchar * base; + + if (!node->file) { + return; + } + + if (reparent) { + parent = node->parent->file; + base = g_file_get_basename (node->file); + g_object_unref (node->file); + + node->file = g_file_get_child (parent, base); + g_free (base); + } + + if (NODE_IS_DIR (node)) { + dir = FILE_BROWSER_NODE_DIR (node); + + for (child = dir->children; child; child = child->next) { + reparent_node ((FileBrowserNode *)child->data, TRUE); + } + } +} + +gboolean +gedit_file_browser_store_rename (GeditFileBrowserStore * model, + GtkTreeIter * iter, + const gchar * new_name, + GError ** error) +{ + FileBrowserNode *node; + GFile * file; + GFile * parent; + GFile * previous; + GError * err = NULL; + gchar * olduri; + gchar * newuri; + GtkTreePath *path; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter->user_data != NULL, FALSE); + + node = (FileBrowserNode *) (iter->user_data); + + parent = g_file_get_parent (node->file); + g_return_val_if_fail (parent != NULL, FALSE); + + file = g_file_get_child (parent, new_name); + g_object_unref (parent); + + if (g_file_equal (node->file, file)) { + g_object_unref (file); + return TRUE; + } + + if (g_file_move (node->file, file, G_FILE_COPY_NONE, NULL, NULL, NULL, &err)) { + previous = node->file; + node->file = file; + + /* This makes sure the actual info for the node is requeried */ + file_browser_node_set_name (node); + file_browser_node_set_from_info (model, node, NULL, TRUE); + + reparent_node (node, FALSE); + + if (model_node_visibility (model, node)) { + path = gedit_file_browser_store_get_path_real (model, node); + row_changed (model, &path, iter); + gtk_tree_path_free (path); + + /* Reorder this item */ + model_resort_node (model, node); + } else { + g_object_unref (previous); + + if (error != NULL) + *error = g_error_new_literal (gedit_file_browser_store_error_quark (), + GEDIT_FILE_BROWSER_ERROR_RENAME, + _("The renamed file is currently filtered out. You need to adjust your filter settings to make the file visible")); + return FALSE; + } + + olduri = g_file_get_uri (previous); + newuri = g_file_get_uri (node->file); + + g_signal_emit (model, model_signals[RENAME], 0, olduri, newuri); + + g_object_unref (previous); + g_free (olduri); + g_free (newuri); + + return TRUE; + } else { + g_object_unref (file); + + if (err) { + if (error != NULL) { + *error = + g_error_new_literal + (gedit_file_browser_store_error_quark (), + GEDIT_FILE_BROWSER_ERROR_RENAME, + err->message); + } + + g_error_free (err); + } + + return FALSE; + } +} + +static void +async_data_free (AsyncData * data) +{ + g_object_unref (data->cancellable); + + g_list_foreach (data->files, (GFunc)g_object_unref, NULL); + g_list_free (data->files); + + if (!data->removed) + data->model->priv->async_handles = g_slist_remove (data->model->priv->async_handles, data); + + g_free (data); +} + +static gboolean +emit_no_trash (AsyncData * data) +{ + /* Emit the no trash error */ + gboolean ret; + + g_signal_emit (data->model, model_signals[NO_TRASH], 0, data->files, &ret); + return ret; +} + +typedef struct { + GeditFileBrowserStore * model; + GFile * file; +} IdleDelete; + +static gboolean +file_deleted (IdleDelete * data) +{ + FileBrowserNode * node; + node = model_find_node (data->model, NULL, data->file); + + if (node != NULL) + model_remove_node (data->model, node, NULL, TRUE); + + return FALSE; +} + +static gboolean +delete_files (GIOSchedulerJob * job, + GCancellable * cancellable, + AsyncData * data) +{ + GFile * file; + GError * error = NULL; + gboolean ret; + gint code; + IdleDelete delete; + + /* Check if our job is done */ + if (!data->iter) + return FALSE; + + /* Move a file to the trash */ + file = G_FILE (data->iter->data); + + if (data->trash) + ret = g_file_trash (file, cancellable, &error); + else + ret = g_file_delete (file, cancellable, &error); + + if (ret) { + delete.model = data->model; + delete.file = file; + + /* Remove the file from the model in the main loop */ + g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc)file_deleted, &delete, NULL); + } else if (!ret && error) { + code = error->code; + g_error_free (error); + + if (data->trash && code == G_IO_ERROR_NOT_SUPPORTED) { + /* Trash is not supported on this system ... */ + if (g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc)emit_no_trash, data, NULL)) + { + /* Changes this into a delete job */ + data->trash = FALSE; + data->iter = data->files; + + return TRUE; + } + + /* End the job */ + return FALSE; + } else if (code == G_IO_ERROR_CANCELLED) { + /* Job has been cancelled, just let the job end */ + return FALSE; + } + } + + /* Process the next item */ + data->iter = data->iter->next; + return TRUE; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_delete_all (GeditFileBrowserStore *model, + GList *rows, gboolean trash) +{ + FileBrowserNode * node; + AsyncData * data; + GList * files = NULL; + GList * row; + GtkTreeIter iter; + GtkTreePath * prev = NULL; + GtkTreePath * path; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (rows == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + /* First we sort the paths so that we can later on remove any + files/directories that are actually subfiles/directories of + a directory that's also deleted */ + rows = g_list_sort (g_list_copy (rows), (GCompareFunc)gtk_tree_path_compare); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) + continue; + + /* Skip if the current path is actually a descendant of the + previous path */ + if (prev != NULL && gtk_tree_path_is_descendant (path, prev)) + continue; + + prev = path; + node = (FileBrowserNode *)(iter.user_data); + files = g_list_prepend (files, g_object_ref (node->file)); + } + + data = g_new (AsyncData, 1); + + data->model = model; + data->cancellable = g_cancellable_new (); + data->files = files; + data->trash = trash; + data->iter = files; + data->removed = FALSE; + + model->priv->async_handles = + g_slist_prepend (model->priv->async_handles, data); + + g_io_scheduler_push_job ((GIOSchedulerJobFunc)delete_files, + data, + (GDestroyNotify)async_data_free, + G_PRIORITY_DEFAULT, + data->cancellable); + g_list_free (rows); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_delete (GeditFileBrowserStore * model, + GtkTreeIter * iter, gboolean trash) +{ + FileBrowserNode *node; + GList *rows = NULL; + GeditFileBrowserStoreResult result; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter->user_data != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + node = (FileBrowserNode *) (iter->user_data); + + if (NODE_IS_DUMMY (node)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + rows = g_list_append(NULL, gedit_file_browser_store_get_path_real (model, node)); + result = gedit_file_browser_store_delete_all (model, rows, trash); + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result; +} + +gboolean +gedit_file_browser_store_new_file (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter) +{ + GFile * file; + GFileOutputStream * stream; + FileBrowserNodeDir *parent_node; + gboolean result = FALSE; + FileBrowserNode *node; + GError * error = NULL; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (parent->user_data != NULL, FALSE); + g_return_val_if_fail (NODE_IS_DIR + ((FileBrowserNode *) (parent->user_data)), + FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + parent_node = FILE_BROWSER_NODE_DIR (parent->user_data); + /* Translators: This is the default name of new files created by the file browser pane. */ + file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("file")); + + stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error); + + if (!stream) + { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + error->message); + g_error_free (error); + } else { + g_object_unref (stream); + node = model_add_node_from_file (model, + (FileBrowserNode *)parent_node, + file, + NULL); + + if (model_node_visibility (model, node)) { + iter->user_data = node; + result = TRUE; + } else { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + _ + ("The new file is currently filtered out. You need to adjust your filter settings to make the file visible")); + } + } + + g_object_unref (file); + return result; +} + +gboolean +gedit_file_browser_store_new_directory (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter) +{ + GFile * file; + FileBrowserNodeDir *parent_node; + GError * error = NULL; + FileBrowserNode *node; + gboolean result = FALSE; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (parent->user_data != NULL, FALSE); + g_return_val_if_fail (NODE_IS_DIR + ((FileBrowserNode *) (parent->user_data)), + FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + parent_node = FILE_BROWSER_NODE_DIR (parent->user_data); + /* Translators: This is the default name of new directories created by the file browser pane. */ + file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("directory")); + + if (!g_file_make_directory (file, NULL, &error)) { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY, + error->message); + g_error_free (error); + } else { + node = model_add_node_from_file (model, + (FileBrowserNode *)parent_node, + file, + NULL); + + if (model_node_visibility (model, node)) { + iter->user_data = node; + result = TRUE; + } else { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + _ + ("The new directory is currently filtered out. You need to adjust your filter settings to make the directory visible")); + } + } + + g_object_unref (file); + return result; +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-store.h b/plugins/filebrowser/gedit-file-browser-store.h new file mode 100755 index 00000000..f31da327 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.h @@ -0,0 +1,200 @@ +/* + * gedit-file-browser-store.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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_FILE_BROWSER_STORE_H__ +#define __GEDIT_FILE_BROWSER_STORE_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BROWSER_STORE (gedit_file_browser_store_get_type ()) +#define GEDIT_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore)) +#define GEDIT_FILE_BROWSER_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore const)) +#define GEDIT_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass)) +#define GEDIT_IS_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_STORE)) +#define GEDIT_IS_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_STORE)) +#define GEDIT_FILE_BROWSER_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass)) + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON = 0, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, + GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM, + GEDIT_FILE_BROWSER_STORE_COLUMN_NUM +} GeditFileBrowserStoreColumn; + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY = 1 << 0, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN = 1 << 1, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT = 1 << 2, + GEDIT_FILE_BROWSER_STORE_FLAG_LOADED = 1 << 3, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED = 1 << 4, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY = 1 << 5 +} GeditFileBrowserStoreFlag; + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_RESULT_OK, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE, + GEDIT_FILE_BROWSER_STORE_RESULT_ERROR, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_TRASH, + GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING, + GEDIT_FILE_BROWSER_STORE_RESULT_NUM +} GeditFileBrowserStoreResult; + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_NONE = 0, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN = 1 << 0, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY = 1 << 1 +} GeditFileBrowserStoreFilterMode; + +#define FILE_IS_DIR(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY) +#define FILE_IS_HIDDEN(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN) +#define FILE_IS_TEXT(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT) +#define FILE_LOADED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_LOADED) +#define FILE_IS_FILTERED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED) +#define FILE_IS_DUMMY(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY) + +typedef struct _GeditFileBrowserStore GeditFileBrowserStore; +typedef struct _GeditFileBrowserStoreClass GeditFileBrowserStoreClass; +typedef struct _GeditFileBrowserStorePrivate GeditFileBrowserStorePrivate; + +typedef gboolean (*GeditFileBrowserStoreFilterFunc) (GeditFileBrowserStore + * model, + GtkTreeIter * iter, + gpointer user_data); + +struct _GeditFileBrowserStore +{ + GObject parent; + + GeditFileBrowserStorePrivate *priv; +}; + +struct _GeditFileBrowserStoreClass { + GObjectClass parent_class; + + /* Signals */ + void (*begin_loading) (GeditFileBrowserStore * model, + GtkTreeIter * iter); + void (*end_loading) (GeditFileBrowserStore * model, + GtkTreeIter * iter); + void (*error) (GeditFileBrowserStore * model, + guint code, + gchar * message); + gboolean (*no_trash) (GeditFileBrowserStore * model, + GList * files); + void (*rename) (GeditFileBrowserStore * model, + const gchar * olduri, + const gchar * newuri); + void (*begin_refresh) (GeditFileBrowserStore * model); + void (*end_refresh) (GeditFileBrowserStore * model); + void (*unload) (GeditFileBrowserStore * model, + const gchar * uri); +}; + +GType gedit_file_browser_store_get_type (void) G_GNUC_CONST; +GType gedit_file_browser_store_register_type (GTypeModule * module); + +GeditFileBrowserStore *gedit_file_browser_store_new (gchar const *root); + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore * model, + gchar const *root, + gchar const *virtual_root); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root (GeditFileBrowserStore * model, + gchar const *root); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore * model, + GtkTreeIter * iter); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_from_string (GeditFileBrowserStore * model, + gchar const *root); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore * model); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore * model); + +gboolean +gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore * model, + GtkTreeIter * iter); +gboolean gedit_file_browser_store_get_iter_root (GeditFileBrowserStore * model, + GtkTreeIter * iter); +gchar * gedit_file_browser_store_get_root (GeditFileBrowserStore * model); +gchar * gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore * model); + +gboolean gedit_file_browser_store_iter_equal (GeditFileBrowserStore * model, + GtkTreeIter * iter1, + GtkTreeIter * iter2); + +void gedit_file_browser_store_set_value (GeditFileBrowserStore * tree_model, + GtkTreeIter * iter, + gint column, + GValue * value); + +void _gedit_file_browser_store_iter_expanded (GeditFileBrowserStore * model, + GtkTreeIter * iter); +void _gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore * model, + GtkTreeIter * iter); + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore * model); +void gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterMode mode); +void gedit_file_browser_store_set_filter_func (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterFunc func, + gpointer user_data); +void gedit_file_browser_store_refilter (GeditFileBrowserStore * model); +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_filter_mode_get_default (void); + +void gedit_file_browser_store_refresh (GeditFileBrowserStore * model); +gboolean gedit_file_browser_store_rename (GeditFileBrowserStore * model, + GtkTreeIter * iter, + gchar const *new_name, + GError ** error); +GeditFileBrowserStoreResult +gedit_file_browser_store_delete (GeditFileBrowserStore * model, + GtkTreeIter * iter, + gboolean trash); +GeditFileBrowserStoreResult +gedit_file_browser_store_delete_all (GeditFileBrowserStore * model, + GList *rows, + gboolean trash); + +gboolean gedit_file_browser_store_new_file (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter); +gboolean gedit_file_browser_store_new_directory (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter); + +void gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_STORE_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-utils.c b/plugins/filebrowser/gedit-file-browser-utils.c new file mode 100755 index 00000000..d8f4028a --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.c @@ -0,0 +1,198 @@ +/* + * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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-file-browser-utils.h" +#include <gedit/gedit-utils.h> + +static GdkPixbuf * +process_icon_pixbuf (GdkPixbuf * pixbuf, + gchar const * name, + gint size, + GError * error) +{ + GdkPixbuf * scale; + + if (error != NULL) { + g_warning ("Could not load theme icon %s: %s", + name, + error->message); + g_error_free (error); + } + + if (pixbuf && gdk_pixbuf_get_width (pixbuf) > size) { + scale = gdk_pixbuf_scale_simple (pixbuf, + size, + size, + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + pixbuf = scale; + } + + return pixbuf; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_theme (gchar const * name, + GtkIconSize size) +{ + gint width; + GError *error = NULL; + GdkPixbuf *pixbuf; + + gtk_icon_size_lookup (size, &width, NULL); + + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + name, + width, + 0, + &error); + + pixbuf = process_icon_pixbuf (pixbuf, name, width, error); + + return pixbuf; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_icon (GIcon * icon, + GtkIconSize size) +{ + GdkPixbuf * ret = NULL; + GtkIconTheme *theme; + GtkIconInfo *info; + gint width; + + if (!icon) + return NULL; + + theme = gtk_icon_theme_get_default (); + gtk_icon_size_lookup (size, &width, NULL); + + info = gtk_icon_theme_lookup_by_gicon (theme, + icon, + width, + GTK_ICON_LOOKUP_USE_BUILTIN); + + if (!info) + return NULL; + + ret = gtk_icon_info_load_icon (info, NULL); + gtk_icon_info_free (info); + + return ret; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_file (GFile * file, + GtkIconSize size) +{ + GIcon * icon; + GFileInfo * info; + GdkPixbuf * ret = NULL; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_ICON, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (!info) + return NULL; + + icon = g_file_info_get_icon (info); + if (icon != NULL) + ret = gedit_file_browser_utils_pixbuf_from_icon (icon, size); + + g_object_unref (info); + + return ret; +} + +gchar * +gedit_file_browser_utils_file_basename (GFile * file) +{ + gchar *uri; + gchar *ret; + + uri = g_file_get_uri (file); + ret = gedit_file_browser_utils_uri_basename (uri); + g_free (uri); + + return ret; +} + +gchar * +gedit_file_browser_utils_uri_basename (gchar const * uri) +{ + return gedit_utils_basename_for_display (uri); +} + +gboolean +gedit_file_browser_utils_confirmation_dialog (GeditWindow * window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const * button_stock, + gchar const * button_label) +{ + GtkWidget *dlg; + gint ret; + GtkWidget *button; + + dlg = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + type, + GTK_BUTTONS_NONE, "%s", message); + + if (secondary) + gtk_message_dialog_format_secondary_text + (GTK_MESSAGE_DIALOG (dlg), "%s", secondary); + + /* Add a cancel button */ + button = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_widget_show (button); + + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_dialog_add_action_widget (GTK_DIALOG (dlg), + button, + GTK_RESPONSE_CANCEL); + + /* Add custom button */ + button = gtk_button_new_from_stock (button_stock); + + if (button_label) { + gtk_button_set_use_stock (GTK_BUTTON (button), FALSE); + gtk_button_set_label (GTK_BUTTON (button), button_label); + } + + gtk_widget_show (button); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_dialog_add_action_widget (GTK_DIALOG (dlg), + button, + GTK_RESPONSE_OK); + + ret = gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); + + return (ret == GTK_RESPONSE_OK); +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-utils.h b/plugins/filebrowser/gedit-file-browser-utils.h new file mode 100755 index 00000000..fc9acbce --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.h @@ -0,0 +1,27 @@ +#ifndef __GEDIT_FILE_BROWSER_UTILS_H__ +#define __GEDIT_FILE_BROWSER_UTILS_H__ + +#include <gedit/gedit-window.h> +#include <gio/gio.h> + +GdkPixbuf *gedit_file_browser_utils_pixbuf_from_theme (gchar const *name, + GtkIconSize size); + +GdkPixbuf *gedit_file_browser_utils_pixbuf_from_icon (GIcon * icon, + GtkIconSize size); +GdkPixbuf *gedit_file_browser_utils_pixbuf_from_file (GFile * file, + GtkIconSize size); + +gchar * gedit_file_browser_utils_file_basename (GFile * file); +gchar * gedit_file_browser_utils_uri_basename (gchar const * uri); + +gboolean gedit_file_browser_utils_confirmation_dialog (GeditWindow * window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const * button_stock, + gchar const * button_label); + +#endif /* __GEDIT_FILE_BROWSER_UTILS_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-view.c b/plugins/filebrowser/gedit-file-browser-view.c new file mode 100755 index 00000000..05733da1 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-view.c @@ -0,0 +1,1256 @@ +/* + * gedit-file-browser-view.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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 <string.h> +#include <gio/gio.h> +#include <gedit/gedit-plugin.h> +#include <gdk/gdkkeysyms.h> + +#include "gedit-file-browser-store.h" +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-view.h" +#include "gedit-file-browser-marshal.h" +#include "gedit-file-browser-enum-types.h" + +#define GEDIT_FILE_BROWSER_VIEW_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewPrivate)) + +struct _GeditFileBrowserViewPrivate +{ + GtkTreeViewColumn *column; + GtkCellRenderer *pixbuf_renderer; + GtkCellRenderer *text_renderer; + + GtkTreeModel *model; + GtkTreeRowReference *editable; + + GdkCursor *busy_cursor; + + /* CLick policy */ + GeditFileBrowserViewClickPolicy click_policy; + GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */ + GtkTreePath *hover_path; + GdkCursor *hand_cursor; + gboolean ignore_release; + gboolean selected_on_button_down; + gint drag_button; + gboolean drag_started; + + gboolean restore_expand_state; + gboolean is_refresh; + GHashTable * expand_state; +}; + +/* Properties */ +enum +{ + PROP_0, + + PROP_CLICK_POLICY, + PROP_RESTORE_EXPAND_STATE +}; + +/* Signals */ +enum +{ + ERROR, + FILE_ACTIVATED, + DIRECTORY_ACTIVATED, + BOOKMARK_ACTIVATED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0 }; + +static const GtkTargetEntry drag_source_targets[] = { + { "text/uri-list", 0, 0 } +}; + +GEDIT_PLUGIN_DEFINE_TYPE (GeditFileBrowserView, gedit_file_browser_view, + GTK_TYPE_TREE_VIEW) + +static void on_cell_edited (GtkCellRendererText * cell, + gchar * path, + gchar * new_text, + GeditFileBrowserView * tree_view); + +static void on_begin_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view); +static void on_end_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view); + +static void on_unload (GeditFileBrowserStore * model, + gchar const * uri, + GeditFileBrowserView * view); + +static void on_row_inserted (GeditFileBrowserStore * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserView * view); + +static void +gedit_file_browser_view_finalize (GObject * object) +{ + GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW(object); + + if (obj->priv->hand_cursor) + gdk_cursor_unref(obj->priv->hand_cursor); + + if (obj->priv->hover_path) + gtk_tree_path_free (obj->priv->hover_path); + + if (obj->priv->expand_state) + { + g_hash_table_destroy (obj->priv->expand_state); + obj->priv->expand_state = NULL; + } + + gdk_cursor_unref (obj->priv->busy_cursor); + + G_OBJECT_CLASS (gedit_file_browser_view_parent_class)-> + finalize (object); +} + +static void +add_expand_state (GeditFileBrowserView * view, + gchar const * uri) +{ + GFile * file; + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + + if (view->priv->expand_state) + g_hash_table_insert (view->priv->expand_state, file, file); + else + g_object_unref (file); +} + +static void +remove_expand_state (GeditFileBrowserView * view, + gchar const * uri) +{ + GFile * file; + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + + if (view->priv->expand_state) + g_hash_table_remove (view->priv->expand_state, file); + + g_object_unref (file); +} + +static void +row_expanded (GtkTreeView * tree_view, + GtkTreeIter * iter, + GtkTreePath * path) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view); + gchar * uri; + + if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded) + GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded (tree_view, iter, path); + + if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + return; + + if (view->priv->restore_expand_state) + { + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + add_expand_state (view, uri); + g_free (uri); + } + + _gedit_file_browser_store_iter_expanded (GEDIT_FILE_BROWSER_STORE (view->priv->model), + iter); +} + +static void +row_collapsed (GtkTreeView * tree_view, + GtkTreeIter * iter, + GtkTreePath * path) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view); + gchar * uri; + + if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed) + GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed (tree_view, iter, path); + + if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + return; + + if (view->priv->restore_expand_state) + { + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + remove_expand_state (view, uri); + g_free (uri); + } + + _gedit_file_browser_store_iter_collapsed (GEDIT_FILE_BROWSER_STORE (view->priv->model), + iter); +} + +static gboolean +leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && + view->priv->hover_path != NULL) { + gtk_tree_path_free (view->priv->hover_path); + view->priv->hover_path = NULL; + } + + // Chainup + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->leave_notify_event (widget, event); +} + +static gboolean +enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + if (view->priv->hover_path != NULL) + gtk_tree_path_free (view->priv->hover_path); + + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->priv->hover_path, + NULL, NULL, NULL); + + if (view->priv->hover_path != NULL) + gdk_window_set_cursor (gtk_widget_get_window (widget), + view->priv->hand_cursor); + } + + // Chainup + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->enter_notify_event (widget, event); +} + +static gboolean +motion_notify_event (GtkWidget * widget, + GdkEventMotion * event) +{ + GtkTreePath *old_hover_path; + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + old_hover_path = view->priv->hover_path; + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->priv->hover_path, + NULL, NULL, NULL); + + if ((old_hover_path != NULL) != (view->priv->hover_path != NULL)) { + if (view->priv->hover_path != NULL) + gdk_window_set_cursor (gtk_widget_get_window (widget), + view->priv->hand_cursor); + else + gdk_window_set_cursor (gtk_widget_get_window (widget), + NULL); + } + + if (old_hover_path != NULL) + gtk_tree_path_free (old_hover_path); + } + + // Chainup + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->motion_notify_event (widget, event); +} + +static void +set_click_policy_property (GeditFileBrowserView *obj, + GeditFileBrowserViewClickPolicy click_policy) +{ + GtkTreeIter iter; + GdkDisplay *display; + GdkWindow *win; + + obj->priv->click_policy = click_policy; + + if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + if (obj->priv->hand_cursor == NULL) + obj->priv->hand_cursor = gdk_cursor_new(GDK_HAND2); + } else if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE) { + if (obj->priv->hover_path != NULL) { + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (obj->priv->model), + &iter, obj->priv->hover_path)) + gtk_tree_model_row_changed (GTK_TREE_MODEL (obj->priv->model), + obj->priv->hover_path, &iter); + + gtk_tree_path_free (obj->priv->hover_path); + obj->priv->hover_path = NULL; + } + + if (GTK_WIDGET_REALIZED (GTK_WIDGET (obj))) { + win = gtk_widget_get_window (GTK_WIDGET (obj)); + gdk_window_set_cursor (win, NULL); + + display = gtk_widget_get_display (GTK_WIDGET (obj)); + + if (display != NULL) + gdk_display_flush (display); + } + + if (obj->priv->hand_cursor) { + gdk_cursor_unref (obj->priv->hand_cursor); + obj->priv->hand_cursor = NULL; + } + } +} + +static void +directory_activated (GeditFileBrowserView *view, + GtkTreeIter *iter) +{ + gedit_file_browser_store_set_virtual_root (GEDIT_FILE_BROWSER_STORE (view->priv->model), iter); +} + +static void +activate_selected_files (GeditFileBrowserView *view) { + GtkTreeView *tree_view = GTK_TREE_VIEW (view); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + GList *rows, *row; + GtkTreePath *directory = NULL; + GtkTreePath *path; + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + + rows = gtk_tree_selection_get_selected_rows (selection, &view->priv->model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + /* Get iter from path */ + if (!gtk_tree_model_get_iter (view->priv->model, &iter, path)) + continue; + + gtk_tree_model_get (view->priv->model, &iter, GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1); + + if (FILE_IS_DIR (flags)) { + if (directory == NULL) + directory = path; + + } else if (!FILE_IS_DUMMY (flags)) { + g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter); + } + } + + if (directory != NULL) { + if (gtk_tree_model_get_iter (view->priv->model, &iter, directory)) + g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter); + } + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); +} + +static void +activate_selected_bookmark (GeditFileBrowserView *view) { + GtkTreeView *tree_view = GTK_TREE_VIEW (view); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, &view->priv->model, &iter)) + g_signal_emit (view, signals[BOOKMARK_ACTIVATED], 0, &iter); +} + +static void +activate_selected_items (GeditFileBrowserView *view) +{ + if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + activate_selected_files (view); + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (view->priv->model)) + activate_selected_bookmark (view); +} + +static void +toggle_hidden_filter (GeditFileBrowserView *view) +{ + GeditFileBrowserStoreFilterMode mode; + + if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + { + mode = gedit_file_browser_store_get_filter_mode + (GEDIT_FILE_BROWSER_STORE (view->priv->model)); + mode ^= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + gedit_file_browser_store_set_filter_mode + (GEDIT_FILE_BROWSER_STORE (view->priv->model), mode); + } +} + +static gboolean +button_event_modifies_selection (GdkEventButton *event) +{ + return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; +} + +static void +drag_begin (GtkWidget *widget, + GdkDragContext *context) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + view->priv->drag_button = 0; + view->priv->drag_started = TRUE; + + /* Chain up */ + GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->drag_begin (widget, context); +} + +static void +did_not_drag (GeditFileBrowserView *view, + GdkEventButton *event) +{ + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreePath *path; + + tree_view = GTK_TREE_VIEW (view); + selection = gtk_tree_view_get_selection (tree_view); + + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) { + if ((view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) + && !button_event_modifies_selection(event) + && (event->button == 1 || event->button == 2)) { + /* Activate all selected items, and leave them selected */ + activate_selected_items (view); + } else if ((event->button == 1 || event->button == 2) + && ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0) + && view->priv->selected_on_button_down) { + if (!button_event_modifies_selection (event)) { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + } else { + gtk_tree_selection_unselect_path (selection, path); + } + } + + gtk_tree_path_free (path); + } +} + +static gboolean +button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (event->button == view->priv->drag_button) { + view->priv->drag_button = 0; + + if (!view->priv->drag_started && + !view->priv->ignore_release) + did_not_drag (view, event); + } + + /* Chain up */ + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->button_release_event (widget, event); +} + +static gboolean +button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + int double_click_time; + static int click_count = 0; + static guint32 last_click_time = 0; + GeditFileBrowserView *view; + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreePath *path; + int expander_size; + int horizontal_separator; + gboolean on_expander; + gboolean call_parent; + gboolean selected; + GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS(gedit_file_browser_view_parent_class); + + tree_view = GTK_TREE_VIEW (widget); + view = GEDIT_FILE_BROWSER_VIEW (widget); + selection = gtk_tree_view_get_selection (tree_view); + + /* Get double click time */ + g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), + "gtk-double-click-time", &double_click_time, + NULL); + + /* Determine click count */ + if (event->time - last_click_time < double_click_time) + click_count++; + else + click_count = 0; + + last_click_time = event->time; + + /* Ignore double click if we are in single click mode */ + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && + click_count >= 2) { + return TRUE; + } + + view->priv->ignore_release = FALSE; + call_parent = TRUE; + + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) { + /* Keep track of path of last click so double clicks only happen + * on the same item */ + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { + if (view->priv->double_click_path[1]) + gtk_tree_path_free (view->priv->double_click_path[1]); + + view->priv->double_click_path[1] = view->priv->double_click_path[0]; + view->priv->double_click_path[0] = gtk_tree_path_copy (path); + } + + if (event->type == GDK_2BUTTON_PRESS) { + if (view->priv->double_click_path[1] && + gtk_tree_path_compare (view->priv->double_click_path[0], view->priv->double_click_path[1]) == 0) + activate_selected_items (view); + + /* Chain up */ + widget_parent->button_press_event (widget, event); + } else { + /* We're going to filter out some situations where + * we can't let the default code run because all + * but one row would be would be deselected. We don't + * want that; we want the right click menu or single + * click to apply to everything that's currently selected. */ + selected = gtk_tree_selection_path_is_selected (selection, path); + + if (event->button == 3 && selected) + call_parent = FALSE; + + if ((event->button == 1 || event->button == 2) && + ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0)) { + gtk_widget_style_get (widget, + "expander-size", &expander_size, + "horizontal-separator", &horizontal_separator, + NULL); + on_expander = (event->x <= horizontal_separator / 2 + + gtk_tree_path_get_depth (path) * expander_size); + + view->priv->selected_on_button_down = selected; + + if (selected) { + call_parent = on_expander || gtk_tree_selection_count_selected_rows (selection) == 1; + view->priv->ignore_release = call_parent && view->priv->click_policy != GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE; + } else if ((event->state & GDK_CONTROL_MASK) != 0) { + call_parent = FALSE; + gtk_tree_selection_select_path (selection, path); + } else { + view->priv->ignore_release = on_expander; + } + } + + if (call_parent) { + /* Chain up */ + widget_parent->button_press_event (widget, event); + } else if (selected) { + gtk_widget_grab_focus (widget); + } + + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { + view->priv->drag_started = FALSE; + view->priv->drag_button = event->button; + } + } + + gtk_tree_path_free (path); + } else { + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { + if (view->priv->double_click_path[1]) + gtk_tree_path_free (view->priv->double_click_path[1]); + + view->priv->double_click_path[1] = view->priv->double_click_path[0]; + view->priv->double_click_path[0] = NULL; + } + + gtk_tree_selection_unselect_all (selection); + /* Chain up */ + widget_parent->button_press_event (widget, event); + } + + /* We already chained up if nescessary, so just return TRUE */ + return TRUE; +} + +static gboolean +key_press_event (GtkWidget *widget, + GdkEventKey *event) +{ + GeditFileBrowserView *view; + guint modifiers; + gboolean handled; + + view = GEDIT_FILE_BROWSER_VIEW (widget); + handled = FALSE; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + switch (event->keyval) { + case GDK_space: + if (event->state & GDK_CONTROL_MASK) { + handled = FALSE; + break; + } + if (!GTK_WIDGET_HAS_FOCUS (widget)) { + handled = FALSE; + break; + } + + activate_selected_items (view); + handled = TRUE; + break; + + case GDK_Return: + case GDK_KP_Enter: + activate_selected_items (view); + handled = TRUE; + break; + + case GDK_h: + if ((event->state & modifiers) == GDK_CONTROL_MASK) { + toggle_hidden_filter (view); + handled = TRUE; + break; + } + + default: + handled = FALSE; + } + + /* Chain up */ + if (!handled) + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->key_press_event (widget, event); + + return TRUE; +} + +static void +fill_expand_state (GeditFileBrowserView * view, GtkTreeIter * iter) +{ + GtkTreePath * path; + GtkTreeIter child; + gchar * uri; + + if (!gtk_tree_model_iter_has_child (view->priv->model, iter)) + return; + + path = gtk_tree_model_get_path (view->priv->model, iter); + + if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (view), path)) + { + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + add_expand_state (view, uri); + g_free (uri); + } + + if (gtk_tree_model_iter_children (view->priv->model, &child, iter)) + { + do { + fill_expand_state (view, &child); + } while (gtk_tree_model_iter_next (view->priv->model, &child)); + } + + gtk_tree_path_free (path); +} + +static void +uninstall_restore_signals (GeditFileBrowserView * tree_view, + GtkTreeModel * model) +{ + g_signal_handlers_disconnect_by_func (model, + on_begin_refresh, + tree_view); + + g_signal_handlers_disconnect_by_func (model, + on_end_refresh, + tree_view); + + g_signal_handlers_disconnect_by_func (model, + on_unload, + tree_view); + + g_signal_handlers_disconnect_by_func (model, + on_row_inserted, + tree_view); +} + +static void +install_restore_signals (GeditFileBrowserView * tree_view, + GtkTreeModel * model) +{ + g_signal_connect (model, + "begin-refresh", + G_CALLBACK (on_begin_refresh), + tree_view); + + g_signal_connect (model, + "end-refresh", + G_CALLBACK (on_end_refresh), + tree_view); + + g_signal_connect (model, + "unload", + G_CALLBACK (on_unload), + tree_view); + + g_signal_connect_after (model, + "row-inserted", + G_CALLBACK (on_row_inserted), + tree_view); +} + +static void +set_restore_expand_state (GeditFileBrowserView * view, + gboolean state) +{ + if (state == view->priv->restore_expand_state) + return; + + if (view->priv->expand_state) + { + g_hash_table_destroy (view->priv->expand_state); + view->priv->expand_state = NULL; + } + + if (state) + { + view->priv->expand_state = g_hash_table_new_full (g_file_hash, + (GEqualFunc)g_file_equal, + g_object_unref, + NULL); + + if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + { + fill_expand_state (view, NULL); + + install_restore_signals (view, view->priv->model); + } + } + else if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + { + uninstall_restore_signals (view, view->priv->model); + } + + view->priv->restore_expand_state = state; +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object); + + switch (prop_id) + { + case PROP_CLICK_POLICY: + g_value_set_enum (value, obj->priv->click_policy); + break; + case PROP_RESTORE_EXPAND_STATE: + g_value_set_boolean (value, obj->priv->restore_expand_state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object); + + switch (prop_id) + { + case PROP_CLICK_POLICY: + set_click_policy_property (obj, g_value_get_enum (value)); + break; + case PROP_RESTORE_EXPAND_STATE: + set_restore_expand_state (obj, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_view_class_init (GeditFileBrowserViewClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gedit_file_browser_view_finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + /* Event handlers */ + widget_class->motion_notify_event = motion_notify_event; + widget_class->enter_notify_event = enter_notify_event; + widget_class->leave_notify_event = leave_notify_event; + widget_class->button_press_event = button_press_event; + widget_class->button_release_event = button_release_event; + widget_class->drag_begin = drag_begin; + widget_class->key_press_event = key_press_event; + + /* Tree view handlers */ + tree_view_class->row_expanded = row_expanded; + tree_view_class->row_collapsed = row_collapsed; + + /* Default handlers */ + klass->directory_activated = directory_activated; + + g_object_class_install_property (object_class, PROP_CLICK_POLICY, + g_param_spec_enum ("click-policy", + "Click Policy", + "The click policy", + GEDIT_TYPE_FILE_BROWSER_VIEW_CLICK_POLICY, + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RESTORE_EXPAND_STATE, + g_param_spec_boolean ("restore-expand-state", + "Restore Expand State", + "Restore expanded state of loaded directories", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + signals[ERROR] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + error), NULL, NULL, + gedit_file_browser_marshal_VOID__UINT_STRING, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + signals[FILE_ACTIVATED] = + g_signal_new ("file-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + file_activated), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + signals[DIRECTORY_ACTIVATED] = + g_signal_new ("directory-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + directory_activated), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + signals[BOOKMARK_ACTIVATED] = + g_signal_new ("bookmark-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + bookmark_activated), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserViewPrivate)); +} + +static void +cell_data_cb (GtkTreeViewColumn * tree_column, GtkCellRenderer * cell, + GtkTreeModel * tree_model, GtkTreeIter * iter, + GeditFileBrowserView * obj) +{ + GtkTreePath *path; + PangoUnderline underline = PANGO_UNDERLINE_NONE; + gboolean editable = FALSE; + + path = gtk_tree_model_get_path (tree_model, iter); + + if (obj->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + if (obj->priv->hover_path != NULL && + gtk_tree_path_compare (path, obj->priv->hover_path) == 0) + underline = PANGO_UNDERLINE_SINGLE; + } + + if (GEDIT_IS_FILE_BROWSER_STORE (tree_model)) + { + if (obj->priv->editable != NULL && + gtk_tree_row_reference_valid (obj->priv->editable)) + { + GtkTreePath *edpath = gtk_tree_row_reference_get_path (obj->priv->editable); + + editable = edpath && gtk_tree_path_compare (path, edpath) == 0; + } + } + + gtk_tree_path_free (path); + g_object_set (cell, "editable", editable, "underline", underline, NULL); +} + +static void +gedit_file_browser_view_init (GeditFileBrowserView * obj) +{ + obj->priv = GEDIT_FILE_BROWSER_VIEW_GET_PRIVATE (obj); + + obj->priv->column = gtk_tree_view_column_new (); + + obj->priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (obj->priv->column, + obj->priv->pixbuf_renderer, + FALSE); + gtk_tree_view_column_add_attribute (obj->priv->column, + obj->priv->pixbuf_renderer, + "pixbuf", + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON); + + obj->priv->text_renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (obj->priv->column, + obj->priv->text_renderer, TRUE); + gtk_tree_view_column_add_attribute (obj->priv->column, + obj->priv->text_renderer, + "text", + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME); + + g_signal_connect (obj->priv->text_renderer, "edited", + G_CALLBACK (on_cell_edited), obj); + + gtk_tree_view_append_column (GTK_TREE_VIEW (obj), + obj->priv->column); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (obj), FALSE); + + gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj), + GDK_BUTTON1_MASK, + drag_source_targets, + G_N_ELEMENTS (drag_source_targets), + GDK_ACTION_COPY); + + obj->priv->busy_cursor = gdk_cursor_new (GDK_WATCH); +} + +static gboolean +bookmarks_separator_func (GtkTreeModel * model, GtkTreeIter * iter, + gpointer user_data) +{ + guint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &flags, -1); + + return (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR); +} + +/* Public */ +GtkWidget * +gedit_file_browser_view_new (void) +{ + GeditFileBrowserView *obj = + GEDIT_FILE_BROWSER_VIEW (g_object_new + (GEDIT_TYPE_FILE_BROWSER_VIEW, NULL)); + + return GTK_WIDGET (obj); +} + +void +gedit_file_browser_view_set_model (GeditFileBrowserView * tree_view, + GtkTreeModel * model) +{ + GtkTreeSelection *selection; + + if (tree_view->priv->model == model) + return; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + + if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW + (tree_view), + bookmarks_separator_func, + NULL, NULL); + gtk_tree_view_column_set_cell_data_func (tree_view->priv-> + column, + tree_view->priv-> + text_renderer, + (GtkTreeCellDataFunc) + cell_data_cb, + tree_view, NULL); + } else { + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW + (tree_view), NULL, + NULL, NULL); + gtk_tree_view_column_set_cell_data_func (tree_view->priv-> + column, + tree_view->priv-> + text_renderer, + (GtkTreeCellDataFunc) + cell_data_cb, + tree_view, NULL); + + if (tree_view->priv->restore_expand_state) + install_restore_signals (tree_view, model); + + } + + if (tree_view->priv->hover_path != NULL) { + gtk_tree_path_free (tree_view->priv->hover_path); + tree_view->priv->hover_path = NULL; + } + + if (GEDIT_IS_FILE_BROWSER_STORE (tree_view->priv->model)) { + if (tree_view->priv->restore_expand_state) + uninstall_restore_signals (tree_view, + tree_view->priv->model); + } + + tree_view->priv->model = model; + gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model); +} + +void +gedit_file_browser_view_start_rename (GeditFileBrowserView * tree_view, + GtkTreeIter * iter) +{ + guint flags; + GtkTreeRowReference *rowref; + GtkTreePath *path; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view)); + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE + (tree_view->priv->model)); + g_return_if_fail (iter != NULL); + + gtk_tree_model_get (tree_view->priv->model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags))) + return; + + path = gtk_tree_model_get_path (tree_view->priv->model, iter); + rowref = gtk_tree_row_reference_new (tree_view->priv->model, path); + + /* Start editing */ + gtk_widget_grab_focus (GTK_WIDGET (tree_view)); + + if (gtk_tree_path_up (path)) + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree_view), + path); + + gtk_tree_path_free (path); + tree_view->priv->editable = rowref; + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), + gtk_tree_row_reference_get_path (tree_view->priv->editable), + tree_view->priv->column, TRUE); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), + gtk_tree_row_reference_get_path (tree_view->priv->editable), + tree_view->priv->column, + FALSE, 0.0, 0.0); +} + +void +gedit_file_browser_view_set_click_policy (GeditFileBrowserView *tree_view, + GeditFileBrowserViewClickPolicy policy) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view)); + + set_click_policy_property (tree_view, policy); + + g_object_notify (G_OBJECT (tree_view), "click-policy"); +} + +void +gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView * tree_view, + gboolean restore_expand_state) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view)); + + set_restore_expand_state (tree_view, restore_expand_state); + g_object_notify (G_OBJECT (tree_view), "restore-expand-state"); +} + +/* Signal handlers */ +static void +on_cell_edited (GtkCellRendererText * cell, gchar * path, gchar * new_text, + GeditFileBrowserView * tree_view) +{ + GtkTreePath * treepath; + GtkTreeIter iter; + gboolean ret; + GError * error = NULL; + + gtk_tree_row_reference_free (tree_view->priv->editable); + tree_view->priv->editable = NULL; + + if (new_text == NULL || *new_text == '\0') + return; + + treepath = gtk_tree_path_new_from_string (path); + ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath); + gtk_tree_path_free (treepath); + + if (ret) { + if (gedit_file_browser_store_rename (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model), + &iter, new_text, &error)) { + treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_view->priv->model), &iter); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), + treepath, NULL, + FALSE, 0.0, 0.0); + gtk_tree_path_free (treepath); + } + else { + if (error) { + g_signal_emit (tree_view, signals[ERROR], 0, + error->code, error->message); + g_error_free (error); + } + } + } +} + +static void +on_begin_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view) +{ + /* Store the refresh state, so we can handle unloading of nodes while + refreshing properly */ + view->priv->is_refresh = TRUE; +} + +static void +on_end_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view) +{ + /* Store the refresh state, so we can handle unloading of nodes while + refreshing properly */ + view->priv->is_refresh = FALSE; +} + +static void +on_unload (GeditFileBrowserStore * model, + gchar const * uri, + GeditFileBrowserView * view) +{ + /* Don't remove the expand state if we are refreshing */ + if (!view->priv->restore_expand_state || view->priv->is_refresh) + return; + + remove_expand_state (view, uri); +} + +static void +restore_expand_state (GeditFileBrowserView * view, + GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + gchar * uri; + GFile * file; + GtkTreePath * path; + + gtk_tree_model_get (GTK_TREE_MODEL (model), + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + + if (g_hash_table_lookup (view->priv->expand_state, file)) + { + gtk_tree_view_expand_row (GTK_TREE_VIEW (view), + path, + FALSE); + } + + gtk_tree_path_free (path); + + g_object_unref (file); + g_free (uri); +} + +static void +on_row_inserted (GeditFileBrowserStore * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserView * view) +{ + GtkTreeIter parent; + GtkTreePath * copy; + + if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (model), iter)) + restore_expand_state (view, model, iter); + + copy = gtk_tree_path_copy (path); + + if (gtk_tree_path_up (copy) && + (gtk_tree_path_get_depth (copy) != 0) && + gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &parent, copy)) + { + restore_expand_state (view, model, &parent); + } + + gtk_tree_path_free (copy); +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-view.h b/plugins/filebrowser/gedit-file-browser-view.h new file mode 100755 index 00000000..a5ada255 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-view.h @@ -0,0 +1,84 @@ +/* + * gedit-file-browser-view.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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_FILE_BROWSER_VIEW_H__ +#define __GEDIT_FILE_BROWSER_VIEW_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BROWSER_VIEW (gedit_file_browser_view_get_type ()) +#define GEDIT_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView)) +#define GEDIT_FILE_BROWSER_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView const)) +#define GEDIT_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass)) +#define GEDIT_IS_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW)) +#define GEDIT_IS_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW)) +#define GEDIT_FILE_BROWSER_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass)) + +typedef struct _GeditFileBrowserView GeditFileBrowserView; +typedef struct _GeditFileBrowserViewClass GeditFileBrowserViewClass; +typedef struct _GeditFileBrowserViewPrivate GeditFileBrowserViewPrivate; + +typedef enum { + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE, + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE +} GeditFileBrowserViewClickPolicy; + +struct _GeditFileBrowserView +{ + GtkTreeView parent; + + GeditFileBrowserViewPrivate *priv; +}; + +struct _GeditFileBrowserViewClass +{ + GtkTreeViewClass parent_class; + + /* Signals */ + void (*error) (GeditFileBrowserView * filetree, + guint code, + gchar const *message); + void (*file_activated) (GeditFileBrowserView * filetree, + GtkTreeIter *iter); + void (*directory_activated) (GeditFileBrowserView * filetree, + GtkTreeIter *iter); + void (*bookmark_activated) (GeditFileBrowserView * filetree, + GtkTreeIter *iter); +}; + +GType gedit_file_browser_view_get_type (void) G_GNUC_CONST; +GType gedit_file_browser_view_register_type (GTypeModule * module); + +GtkWidget *gedit_file_browser_view_new (void); +void gedit_file_browser_view_set_model (GeditFileBrowserView * tree_view, + GtkTreeModel * model); +void gedit_file_browser_view_start_rename (GeditFileBrowserView * tree_view, + GtkTreeIter * iter); +void gedit_file_browser_view_set_click_policy (GeditFileBrowserView * tree_view, + GeditFileBrowserViewClickPolicy policy); +void gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView * tree_view, + gboolean restore_expand_state); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_VIEW_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-widget-ui.xml b/plugins/filebrowser/gedit-file-browser-widget-ui.xml new file mode 100755 index 00000000..472fd185 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget-ui.xml @@ -0,0 +1,54 @@ +<ui> + <toolbar name="ToolBar"> + <placeholder name="Tool_Opt1"/> + <toolitem action="DirectoryUp"/> + <separator/> + <toolitem action="DirectoryRefresh"/> + <separator/> + <placeholder name="Tool_Opt2"/> + <separator/> + <toolitem action="FilterHidden"/> + <separator/> + <placeholder name="Tool_Opt3"/> + </toolbar> + + <popup name="FilePopup" action="FilePopupAction"> + <menuitem action="FileOpen" /> + <placeholder name="FilePopup_Opt1" /> + <separator/> + <menuitem action="DirectoryNew" /> + <menuitem action="FileNew" /> + <separator/> + <placeholder name="FilePopup_Opt2" /> + <separator/> + <menuitem action="FileRename"/> + <menuitem action="FileMoveToTrash"/> + <menuitem action="FileDelete"/> + <separator/> + <placeholder name="FilePopup_Opt3" /> + <separator/> + <menuitem action="DirectoryRefresh"/> + <menuitem action="DirectoryOpen"/> + <placeholder name="FilePopup_Opt4" /> + <separator/> + <placeholder name="FilePopup_Opt5" /> + <separator/> + <menu name="FilterMenu" action="FilterMenuAction"> + <menuitem action="FilterHidden"/> + <menuitem action="FilterBinary"/> + </menu> + <placeholder name="FilePopup_Opt6" /> + <separator/> + </popup> + + <popup name="BookmarkPopup" action="BookmarkPopupAction"> + <placeholder name="BookmarkPopup_Opt1" /> + <separator/> + <placeholder name="BookmarkPopup_Opt2" /> + <separator/> + <menuitem action="BookmarkOpen"/> + <placeholder name="BookmarkPopup_Opt3" /> + <separator/> + <placeholder name="BookmarkPopup_Opt4" /> + </popup> +</ui> diff --git a/plugins/filebrowser/gedit-file-browser-widget.c b/plugins/filebrowser/gedit-file-browser-widget.c new file mode 100755 index 00000000..e8a73cce --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.c @@ -0,0 +1,3143 @@ +/* + * gedit-file-browser-widget.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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 <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <glib.h> +#include <glib/gi18n-lib.h> +#include <gdk/gdkkeysyms.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-plugin.h> + +#include "gedit-file-browser-utils.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-widget.h" +#include "gedit-file-browser-view.h" +#include "gedit-file-browser-store.h" +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-marshal.h" +#include "gedit-file-browser-enum-types.h" + +#define GEDIT_FILE_BROWSER_WIDGET_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GEDIT_TYPE_FILE_BROWSER_WIDGET, \ + GeditFileBrowserWidgetPrivate)) + +#define XML_UI_FILE "gedit-file-browser-widget-ui.xml" +#define LOCATION_DATA_KEY "gedit-file-browser-widget-location" + +enum +{ + BOOKMARKS_ID, + SEPARATOR_CUSTOM_ID, + SEPARATOR_ID, + PATH_ID, + NUM_DEFAULT_IDS +}; + +enum +{ + COLUMN_INDENT, + COLUMN_ICON, + COLUMN_NAME, + COLUMN_FILE, + COLUMN_ID, + N_COLUMNS +}; + +/* Properties */ +enum +{ + PROP_0, + + PROP_FILTER_PATTERN, + PROP_ENABLE_DELETE +}; + +/* Signals */ +enum +{ + URI_ACTIVATED, + ERROR, + CONFIRM_DELETE, + CONFIRM_NO_TRASH, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0 }; + +typedef struct _SignalNode +{ + GObject *object; + gulong id; +} SignalNode; + +typedef struct +{ + gulong id; + GeditFileBrowserWidgetFilterFunc func; + gpointer user_data; + GDestroyNotify destroy_notify; +} FilterFunc; + +typedef struct +{ + GFile *root; + GFile *virtual_root; +} Location; + +typedef struct +{ + gchar *name; + GdkPixbuf *icon; +} NameIcon; + +struct _GeditFileBrowserWidgetPrivate +{ + GeditFileBrowserView *treeview; + GeditFileBrowserStore *file_store; + GeditFileBookmarksStore *bookmarks_store; + + GHashTable *bookmarks_hash; + + GtkWidget *combo; + GtkTreeStore *combo_model; + + GtkWidget *filter_expander; + GtkWidget *filter_entry; + + GtkUIManager *manager; + GtkActionGroup *action_group; + GtkActionGroup *action_group_selection; + GtkActionGroup *action_group_file_selection; + GtkActionGroup *action_group_single_selection; + GtkActionGroup *action_group_single_most_selection; + GtkActionGroup *action_group_sensitive; + GtkActionGroup *bookmark_action_group; + + GSList *signal_pool; + + GSList *filter_funcs; + gulong filter_id; + gulong glob_filter_id; + GPatternSpec *filter_pattern; + gchar *filter_pattern_str; + + GList *locations; + GList *current_location; + gboolean changing_location; + GtkWidget *location_previous_menu; + GtkWidget *location_next_menu; + GtkWidget *current_location_menu_item; + + gboolean enable_delete; + + GCancellable *cancellable; + + GdkCursor *busy_cursor; +}; + +static void set_enable_delete (GeditFileBrowserWidget *obj, + gboolean enable); +static void on_model_set (GObject * gobject, + GParamSpec * arg1, + GeditFileBrowserWidget * obj); +static void on_treeview_error (GeditFileBrowserView * tree_view, + guint code, + gchar * message, + GeditFileBrowserWidget * obj); +static void on_file_store_error (GeditFileBrowserStore * store, + guint code, + gchar * message, + GeditFileBrowserWidget * obj); +static gboolean on_file_store_no_trash (GeditFileBrowserStore * store, + GList * files, + GeditFileBrowserWidget * obj); +static void on_combo_changed (GtkComboBox * combo, + GeditFileBrowserWidget * obj); +static gboolean on_treeview_popup_menu (GeditFileBrowserView * treeview, + GeditFileBrowserWidget * obj); +static gboolean on_treeview_button_press_event (GeditFileBrowserView * treeview, + GdkEventButton * event, + GeditFileBrowserWidget * obj); +static gboolean on_treeview_key_press_event (GeditFileBrowserView * treeview, + GdkEventKey * event, + GeditFileBrowserWidget * obj); +static void on_selection_changed (GtkTreeSelection * selection, + GeditFileBrowserWidget * obj); + +static void on_virtual_root_changed (GeditFileBrowserStore * model, + GParamSpec *param, + GeditFileBrowserWidget * obj); + +static gboolean on_entry_filter_activate (GeditFileBrowserWidget * obj); +static void on_location_jump_activate (GtkMenuItem * item, + GeditFileBrowserWidget * obj); +static void on_bookmarks_row_changed (GtkTreeModel * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserWidget * obj); +static void on_bookmarks_row_deleted (GtkTreeModel * model, + GtkTreePath * path, + GeditFileBrowserWidget * obj); +static void on_filter_mode_changed (GeditFileBrowserStore * model, + GParamSpec * param, + GeditFileBrowserWidget * obj); +static void on_action_directory_previous (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_next (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_up (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_new (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_open (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_new (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_rename (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_delete (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_move_to_trash (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_refresh (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_open (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_filter_hidden (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_filter_binary (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_bookmark_open (GtkAction * action, + GeditFileBrowserWidget * obj); + +GEDIT_PLUGIN_DEFINE_TYPE (GeditFileBrowserWidget, gedit_file_browser_widget, + GTK_TYPE_VBOX) + +static void +free_name_icon (gpointer data) +{ + NameIcon * item; + + if (data == NULL) + return; + + item = (NameIcon *)(data); + + g_free (item->name); + + if (item->icon) + g_object_unref (item->icon); + + g_free (item); +} + +static FilterFunc * +filter_func_new (GeditFileBrowserWidget * obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + FilterFunc *result; + + result = g_new (FilterFunc, 1); + + result->id = ++obj->priv->filter_id; + result->func = func; + result->user_data = user_data; + result->destroy_notify = notify; + return result; +} + +static void +location_free (Location * loc) +{ + if (loc->root) + g_object_unref (loc->root); + + if (loc->virtual_root) + g_object_unref (loc->virtual_root); + + g_free (loc); +} + +static gboolean +combo_find_by_id (GeditFileBrowserWidget * obj, guint id, + GtkTreeIter * iter) +{ + guint checkid; + GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->combo_model); + + if (iter == NULL) + return FALSE; + + if (gtk_tree_model_get_iter_first (model, iter)) { + do { + gtk_tree_model_get (model, iter, COLUMN_ID, + &checkid, -1); + + if (checkid == id) + return TRUE; + } while (gtk_tree_model_iter_next (model, iter)); + } + + return FALSE; +} + +static void +remove_path_items (GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + + while (combo_find_by_id (obj, PATH_ID, &iter)) + gtk_tree_store_remove (obj->priv->combo_model, &iter); +} + +static void +cancel_async_operation (GeditFileBrowserWidget *widget) +{ + if (!widget->priv->cancellable) + return; + + g_cancellable_cancel (widget->priv->cancellable); + g_object_unref (widget->priv->cancellable); + + widget->priv->cancellable = NULL; +} + +static void +gedit_file_browser_widget_finalize (GObject * object) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + GList *loc; + + remove_path_items (obj); + gedit_file_browser_store_set_filter_func (obj->priv->file_store, + NULL, NULL); + + g_object_unref (obj->priv->manager); + g_object_unref (obj->priv->file_store); + g_object_unref (obj->priv->bookmarks_store); + g_object_unref (obj->priv->combo_model); + + g_slist_foreach (obj->priv->filter_funcs, (GFunc) g_free, NULL); + g_slist_free (obj->priv->filter_funcs); + + for (loc = obj->priv->locations; loc; loc = loc->next) + location_free ((Location *) (loc->data)); + + if (obj->priv->current_location_menu_item) + g_object_unref (obj->priv->current_location_menu_item); + + g_list_free (obj->priv->locations); + + g_hash_table_destroy (obj->priv->bookmarks_hash); + + cancel_async_operation (obj); + + gdk_cursor_unref (obj->priv->busy_cursor); + + G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->finalize (object); +} + +static void +gedit_file_browser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + + switch (prop_id) + { + case PROP_FILTER_PATTERN: + g_value_set_string (value, obj->priv->filter_pattern_str); + break; + case PROP_ENABLE_DELETE: + g_value_set_boolean (value, obj->priv->enable_delete); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + + switch (prop_id) + { + case PROP_FILTER_PATTERN: + gedit_file_browser_widget_set_filter_pattern (obj, + g_value_get_string (value)); + break; + case PROP_ENABLE_DELETE: + set_enable_delete (obj, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_widget_class_init (GeditFileBrowserWidgetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_file_browser_widget_finalize; + + object_class->get_property = gedit_file_browser_widget_get_property; + object_class->set_property = gedit_file_browser_widget_set_property; + + g_object_class_install_property (object_class, PROP_FILTER_PATTERN, + g_param_spec_string ("filter-pattern", + "Filter Pattern", + "The filter pattern", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_ENABLE_DELETE, + g_param_spec_boolean ("enable-delete", + "Enable delete", + "Enable permanently deleting items", + TRUE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + signals[URI_ACTIVATED] = + g_signal_new ("uri-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + uri_activated), NULL, NULL, + g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, + G_TYPE_STRING); + signals[ERROR] = + g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + error), NULL, NULL, + gedit_file_browser_marshal_VOID__UINT_STRING, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + + signals[CONFIRM_DELETE] = + g_signal_new ("confirm-delete", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + confirm_delete), + g_signal_accumulator_true_handled, + NULL, + gedit_file_browser_marshal_BOOL__OBJECT_POINTER, + G_TYPE_BOOLEAN, + 2, + G_TYPE_OBJECT, + G_TYPE_POINTER); + + signals[CONFIRM_NO_TRASH] = + g_signal_new ("confirm-no-trash", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + confirm_no_trash), + g_signal_accumulator_true_handled, + NULL, + gedit_file_browser_marshal_BOOL__POINTER, + G_TYPE_BOOLEAN, + 1, + G_TYPE_POINTER); + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserWidgetPrivate)); +} + +static void +add_signal (GeditFileBrowserWidget * obj, gpointer object, gulong id) +{ + SignalNode *node = g_new (SignalNode, 1); + + node->object = G_OBJECT (object); + node->id = id; + + obj->priv->signal_pool = + g_slist_prepend (obj->priv->signal_pool, node); +} + +static void +clear_signals (GeditFileBrowserWidget * obj) +{ + GSList *item; + SignalNode *node; + + for (item = obj->priv->signal_pool; item; item = item->next) { + node = (SignalNode *) (item->data); + + g_signal_handler_disconnect (node->object, node->id); + g_free (item->data); + } + + g_slist_free (obj->priv->signal_pool); + obj->priv->signal_pool = NULL; +} + +static gboolean +separator_func (GtkTreeModel * model, GtkTreeIter * iter, gpointer data) +{ + guint id; + + gtk_tree_model_get (model, iter, COLUMN_ID, &id, -1); + + return (id == SEPARATOR_ID); +} + +static gboolean +get_from_bookmark_file (GeditFileBrowserWidget * obj, GFile * file, + gchar ** name, GdkPixbuf ** icon) +{ + gpointer data; + NameIcon * item; + + data = g_hash_table_lookup (obj->priv->bookmarks_hash, file); + + if (data == NULL) + return FALSE; + + item = (NameIcon *)data; + + *name = g_strdup (item->name); + *icon = item->icon; + + if (item->icon != NULL) + { + g_object_ref (item->icon); + } + + return TRUE; +} + +static void +insert_path_item (GeditFileBrowserWidget * obj, + GFile * file, + GtkTreeIter * after, + GtkTreeIter * iter, + guint indent) +{ + gchar * unescape; + GdkPixbuf * icon = NULL; + + /* Try to get the icon and name from the bookmarks hash */ + if (!get_from_bookmark_file (obj, file, &unescape, &icon)) { + /* It's not a bookmark, fetch the name and the icon ourselves */ + unescape = gedit_file_browser_utils_file_basename (file); + + /* Get the icon */ + icon = gedit_file_browser_utils_pixbuf_from_file (file, GTK_ICON_SIZE_MENU); + } + + gtk_tree_store_insert_after (obj->priv->combo_model, iter, NULL, + after); + + gtk_tree_store_set (obj->priv->combo_model, + iter, + COLUMN_INDENT, indent, + COLUMN_ICON, icon, + COLUMN_NAME, unescape, + COLUMN_FILE, file, + COLUMN_ID, PATH_ID, + -1); + + if (icon) + g_object_unref (icon); + + g_free (unescape); +} + +static void +insert_separator_item (GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + + gtk_tree_store_insert (obj->priv->combo_model, &iter, NULL, 1); + gtk_tree_store_set (obj->priv->combo_model, &iter, + COLUMN_ICON, NULL, + COLUMN_NAME, NULL, + COLUMN_ID, SEPARATOR_ID, -1); +} + +static void +combo_set_active_by_id (GeditFileBrowserWidget * obj, guint id) +{ + GtkTreeIter iter; + + if (combo_find_by_id (obj, id, &iter)) + gtk_combo_box_set_active_iter (GTK_COMBO_BOX + (obj->priv->combo), &iter); +} + +static guint +uri_num_parents (GFile * from, GFile * to) +{ + /* Determine the number of 'levels' to get from #from to #to. */ + guint parents = 0; + GFile * parent; + + if (from == NULL) + return 0; + + g_object_ref (from); + + while ((parent = g_file_get_parent (from)) && !(to && g_file_equal (from, to))) { + g_object_unref (from); + from = parent; + + ++parents; + } + + g_object_unref (from); + return parents; +} + +static void +insert_location_path (GeditFileBrowserWidget * obj) +{ + Location *loc; + GFile *current = NULL; + GFile * tmp; + GtkTreeIter separator; + GtkTreeIter iter; + guint indent; + + if (!obj->priv->current_location) { + g_message ("insert_location_path: no current location"); + return; + } + + loc = (Location *) (obj->priv->current_location->data); + + current = loc->virtual_root; + combo_find_by_id (obj, SEPARATOR_ID, &separator); + + indent = uri_num_parents (loc->virtual_root, loc->root); + + while (current != NULL) { + insert_path_item (obj, current, &separator, &iter, indent--); + + if (current == loc->virtual_root) { + g_signal_handlers_block_by_func (obj->priv->combo, + on_combo_changed, + obj); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX + (obj->priv->combo), + &iter); + g_signal_handlers_unblock_by_func (obj->priv-> + combo, + on_combo_changed, + obj); + } + + if (g_file_equal (current, loc->root) || !gedit_utils_file_has_parent (current)) { + if (current != loc->virtual_root) + g_object_unref (current); + break; + } + + tmp = g_file_get_parent (current); + + if (current != loc->virtual_root) + g_object_unref (current); + + current = tmp; + } +} + +static void +check_current_item (GeditFileBrowserWidget * obj, gboolean show_path) +{ + GtkTreeIter separator; + gboolean has_sep; + + remove_path_items (obj); + has_sep = combo_find_by_id (obj, SEPARATOR_ID, &separator); + + if (show_path) { + if (!has_sep) + insert_separator_item (obj); + + insert_location_path (obj); + } else if (has_sep) + gtk_tree_store_remove (obj->priv->combo_model, &separator); +} + +static void +fill_combo_model (GeditFileBrowserWidget * obj) +{ + GtkTreeStore *store = obj->priv->combo_model; + GtkTreeIter iter; + GdkPixbuf *icon; + + icon = gedit_file_browser_utils_pixbuf_from_theme (GTK_STOCK_HOME, GTK_ICON_SIZE_MENU); + + gtk_tree_store_append (store, &iter, NULL); + gtk_tree_store_set (store, &iter, + COLUMN_ICON, icon, + COLUMN_NAME, _("Bookmarks"), + COLUMN_ID, BOOKMARKS_ID, -1); + g_object_unref (icon); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (obj->priv->combo), + separator_func, obj, NULL); + gtk_combo_box_set_active (GTK_COMBO_BOX (obj->priv->combo), 0); +} + +static void +indent_cell_data_func (GtkCellLayout * cell_layout, + GtkCellRenderer * cell, + GtkTreeModel * model, + GtkTreeIter * iter, + gpointer data) +{ + gchar * indent; + guint num; + + gtk_tree_model_get (model, iter, COLUMN_INDENT, &num, -1); + + if (num == 0) + g_object_set (cell, "text", "", NULL); + else { + indent = g_strnfill (num * 2, ' '); + + g_object_set (cell, "text", indent, NULL); + g_free (indent); + } +} + +static void +create_combo (GeditFileBrowserWidget * obj) +{ + GtkCellRenderer *renderer; + + obj->priv->combo_model = gtk_tree_store_new (N_COLUMNS, + G_TYPE_UINT, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_FILE, + G_TYPE_UINT); + obj->priv->combo = + gtk_combo_box_new_with_model (GTK_TREE_MODEL + (obj->priv->combo_model)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, FALSE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT + (obj->priv->combo), renderer, + indent_cell_data_func, obj, NULL); + + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, FALSE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, "pixbuf", COLUMN_ICON); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, "text", COLUMN_NAME); + + g_object_set (renderer, "ellipsize-set", TRUE, + "ellipsize", PANGO_ELLIPSIZE_END, NULL); + + gtk_box_pack_start (GTK_BOX (obj), GTK_WIDGET (obj->priv->combo), + FALSE, FALSE, 0); + + fill_combo_model (obj); + g_signal_connect (obj->priv->combo, "changed", + G_CALLBACK (on_combo_changed), obj); + + gtk_widget_show (obj->priv->combo); +} + +static GtkActionEntry toplevel_actions[] = +{ + {"FilterMenuAction", NULL, N_("_Filter")} +}; + +static const GtkActionEntry tree_actions_selection[] = +{ + {"FileMoveToTrash", "mate-stock-trash", N_("_Move to Trash"), NULL, + N_("Move selected file or folder to trash"), + G_CALLBACK (on_action_file_move_to_trash)}, + {"FileDelete", GTK_STOCK_DELETE, N_("_Delete"), NULL, + N_("Delete selected file or folder"), + G_CALLBACK (on_action_file_delete)} +}; + +static const GtkActionEntry tree_actions_file_selection[] = +{ + {"FileOpen", GTK_STOCK_OPEN, NULL, NULL, + N_("Open selected file"), + G_CALLBACK (on_action_file_open)} +}; + +static const GtkActionEntry tree_actions[] = +{ + {"DirectoryUp", GTK_STOCK_GO_UP, N_("Up"), NULL, + N_("Open the parent folder"), G_CALLBACK (on_action_directory_up)} +}; + +static const GtkActionEntry tree_actions_single_most_selection[] = +{ + {"DirectoryNew", GTK_STOCK_ADD, N_("_New Folder"), NULL, + N_("Add new empty folder"), + G_CALLBACK (on_action_directory_new)}, + {"FileNew", GTK_STOCK_NEW, N_("New F_ile"), NULL, + N_("Add new empty file"), G_CALLBACK (on_action_file_new)} +}; + +static const GtkActionEntry tree_actions_single_selection[] = +{ + {"FileRename", NULL, N_("_Rename"), NULL, + N_("Rename selected file or folder"), + G_CALLBACK (on_action_file_rename)} +}; + +static const GtkActionEntry tree_actions_sensitive[] = +{ + {"DirectoryPrevious", GTK_STOCK_GO_BACK, N_("_Previous Location"), + NULL, + N_("Go to the previous visited location"), + G_CALLBACK (on_action_directory_previous)}, + {"DirectoryNext", GTK_STOCK_GO_FORWARD, N_("_Next Location"), NULL, + N_("Go to the next visited location"), G_CALLBACK (on_action_directory_next)}, + {"DirectoryRefresh", GTK_STOCK_REFRESH, N_("Re_fresh View"), NULL, + N_("Refresh the view"), G_CALLBACK (on_action_directory_refresh)}, + {"DirectoryOpen", GTK_STOCK_OPEN, N_("_View Folder"), NULL, + N_("View folder in file manager"), + G_CALLBACK (on_action_directory_open)} +}; + +static const GtkToggleActionEntry tree_actions_toggle[] = +{ + {"FilterHidden", GTK_STOCK_DIALOG_AUTHENTICATION, + N_("Show _Hidden"), NULL, + N_("Show hidden files and folders"), + G_CALLBACK (on_action_filter_hidden), FALSE}, + {"FilterBinary", NULL, N_("Show _Binary"), NULL, + N_("Show binary files"), G_CALLBACK (on_action_filter_binary), + FALSE} +}; + +static const GtkActionEntry bookmark_actions[] = +{ + {"BookmarkOpen", GTK_STOCK_OPEN, N_("_View Folder"), NULL, + N_("View folder in file manager"), G_CALLBACK (on_action_bookmark_open)} +}; + +static void +create_toolbar (GeditFileBrowserWidget * obj, + const gchar *data_dir) +{ + GtkUIManager *manager; + GError *error = NULL; + GtkActionGroup *action_group; + GtkWidget *toolbar; + GtkWidget *widget; + GtkAction *action; + gchar *ui_file; + + manager = gtk_ui_manager_new (); + obj->priv->manager = manager; + + ui_file = g_build_filename (data_dir, XML_UI_FILE, NULL); + gtk_ui_manager_add_ui_from_file (manager, ui_file, &error); + + g_free (ui_file); + + if (error != NULL) { + g_warning ("Error in adding ui from file %s: %s", + XML_UI_FILE, error->message); + g_error_free (error); + return; + } + + action_group = gtk_action_group_new ("FileBrowserWidgetActionGroupToplevel"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + toplevel_actions, + G_N_ELEMENTS (toplevel_actions), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + action_group = gtk_action_group_new ("FileBrowserWidgetActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions, + G_N_ELEMENTS (tree_actions), + obj); + gtk_action_group_add_toggle_actions (action_group, + tree_actions_toggle, + G_N_ELEMENTS (tree_actions_toggle), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_selection, + G_N_ELEMENTS (tree_actions_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetFileSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_file_selection, + G_N_ELEMENTS (tree_actions_file_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_file_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSingleSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_single_selection, + G_N_ELEMENTS (tree_actions_single_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_single_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSingleMostSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_single_most_selection, + G_N_ELEMENTS (tree_actions_single_most_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_single_most_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSensitiveActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_sensitive, + G_N_ELEMENTS (tree_actions_sensitive), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_sensitive = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetBookmarkActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + bookmark_actions, + G_N_ELEMENTS (bookmark_actions), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->bookmark_action_group = action_group; + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryPrevious"); + gtk_action_set_sensitive (action, FALSE); + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryNext"); + gtk_action_set_sensitive (action, FALSE); + + toolbar = gtk_ui_manager_get_widget (manager, "/ToolBar"); + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS); + gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_MENU); + + /* Previous directory menu tool item */ + obj->priv->location_previous_menu = gtk_menu_new (); + gtk_widget_show (obj->priv->location_previous_menu); + + widget = GTK_WIDGET (gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_BACK)); + gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (widget), + obj->priv->location_previous_menu); + + g_object_set (widget, "label", _("Previous location"), NULL); + gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (widget), + _("Go to previous location")); + gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (widget), + _("Go to a previously opened location")); + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryPrevious"); + g_object_set (action, "is_important", TRUE, "short_label", + _("Previous location"), NULL); + gtk_action_connect_proxy (action, widget); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (widget), 0); + + /* Next directory menu tool item */ + obj->priv->location_next_menu = gtk_menu_new (); + gtk_widget_show (obj->priv->location_next_menu); + + widget = GTK_WIDGET (gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD)); + gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (widget), + obj->priv->location_next_menu); + + g_object_set (widget, "label", _("Next location"), NULL); + gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (widget), + _("Go to next location")); + gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (widget), + _("Go to a previously opened location")); + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryNext"); + g_object_set (action, "is_important", TRUE, "short_label", + _("Previous location"), NULL); + gtk_action_connect_proxy (action, widget); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (widget), 1); + + gtk_box_pack_start (GTK_BOX (obj), toolbar, FALSE, FALSE, 0); + gtk_widget_show (toolbar); + + set_enable_delete (obj, obj->priv->enable_delete); +} + +static void +set_enable_delete (GeditFileBrowserWidget *obj, + gboolean enable) +{ + GtkAction *action; + obj->priv->enable_delete = enable; + + if (obj->priv->action_group_selection == NULL) + return; + + action = + gtk_action_group_get_action (obj->priv->action_group_selection, + "FileDelete"); + + g_object_set (action, "visible", enable, "sensitive", enable, NULL); +} + +static gboolean +filter_real (GeditFileBrowserStore * model, GtkTreeIter * iter, + GeditFileBrowserWidget * obj) +{ + GSList *item; + FilterFunc *func; + + for (item = obj->priv->filter_funcs; item; item = item->next) { + func = (FilterFunc *) (item->data); + + if (!func->func (obj, model, iter, func->user_data)) + return FALSE; + } + + return TRUE; +} + +static void +add_bookmark_hash (GeditFileBrowserWidget * obj, + GtkTreeIter * iter) +{ + GtkTreeModel *model; + GdkPixbuf * pixbuf; + gchar * name; + gchar * uri; + GFile * file; + NameIcon * item; + + model = GTK_TREE_MODEL (obj->priv->bookmarks_store); + + uri = gedit_file_bookmarks_store_get_uri (obj->priv-> + bookmarks_store, + iter); + + if (uri == NULL) + return; + + file = g_file_new_for_uri (uri); + g_free (uri); + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, + &pixbuf, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, + &name, -1); + + item = g_new (NameIcon, 1); + item->name = name; + item->icon = pixbuf; + + g_hash_table_insert (obj->priv->bookmarks_hash, + file, + item); +} + +static void +init_bookmarks_hash (GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + GtkTreeModel *model; + + model = GTK_TREE_MODEL (obj->priv->bookmarks_store); + + if (!gtk_tree_model_get_iter_first (model, &iter)) + return; + + do { + add_bookmark_hash (obj, &iter); + } while (gtk_tree_model_iter_next (model, &iter)); + + g_signal_connect (obj->priv->bookmarks_store, + "row-changed", + G_CALLBACK (on_bookmarks_row_changed), + obj); + + g_signal_connect (obj->priv->bookmarks_store, + "row-deleted", + G_CALLBACK (on_bookmarks_row_deleted), + obj); +} + +static void +on_begin_loading (GeditFileBrowserStore *model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)))) + return; + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)), + obj->priv->busy_cursor); +} + +static void +on_end_loading (GeditFileBrowserStore *model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)))) + return; + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)), NULL); +} + +static void +create_tree (GeditFileBrowserWidget * obj) +{ + GtkWidget *sw; + + obj->priv->file_store = gedit_file_browser_store_new (NULL); + obj->priv->bookmarks_store = gedit_file_bookmarks_store_new (); + obj->priv->treeview = + GEDIT_FILE_BROWSER_VIEW (gedit_file_browser_view_new ()); + + gedit_file_browser_view_set_restore_expand_state (obj->priv->treeview, TRUE); + + gedit_file_browser_store_set_filter_mode (obj->priv->file_store, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN + | + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); + gedit_file_browser_store_set_filter_func (obj->priv->file_store, + (GeditFileBrowserStoreFilterFunc) + filter_real, obj); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_container_add (GTK_CONTAINER (sw), + GTK_WIDGET (obj->priv->treeview)); + gtk_box_pack_start (GTK_BOX (obj), sw, TRUE, TRUE, 0); + + g_signal_connect (obj->priv->treeview, "notify::model", + G_CALLBACK (on_model_set), obj); + g_signal_connect (obj->priv->treeview, "error", + G_CALLBACK (on_treeview_error), obj); + g_signal_connect (obj->priv->treeview, "popup-menu", + G_CALLBACK (on_treeview_popup_menu), obj); + g_signal_connect (obj->priv->treeview, "button-press-event", + G_CALLBACK (on_treeview_button_press_event), + obj); + g_signal_connect (obj->priv->treeview, "key-press-event", + G_CALLBACK (on_treeview_key_press_event), obj); + + g_signal_connect (gtk_tree_view_get_selection + (GTK_TREE_VIEW (obj->priv->treeview)), "changed", + G_CALLBACK (on_selection_changed), obj); + g_signal_connect (obj->priv->file_store, "notify::filter-mode", + G_CALLBACK (on_filter_mode_changed), obj); + + g_signal_connect (obj->priv->file_store, "notify::virtual-root", + G_CALLBACK (on_virtual_root_changed), obj); + + g_signal_connect (obj->priv->file_store, "begin-loading", + G_CALLBACK (on_begin_loading), obj); + + g_signal_connect (obj->priv->file_store, "end-loading", + G_CALLBACK (on_end_loading), obj); + + g_signal_connect (obj->priv->file_store, "error", + G_CALLBACK (on_file_store_error), obj); + + init_bookmarks_hash (obj); + + gtk_widget_show (sw); + gtk_widget_show (GTK_WIDGET (obj->priv->treeview)); +} + +static void +create_filter (GeditFileBrowserWidget * obj) +{ + GtkWidget *expander; + GtkWidget *vbox; + GtkWidget *entry; + + expander = gtk_expander_new_with_mnemonic (_("_Match Filename")); + gtk_widget_show (expander); + gtk_box_pack_start (GTK_BOX (obj), expander, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 3); + gtk_widget_show (vbox); + + obj->priv->filter_expander = expander; + + entry = gtk_entry_new (); + gtk_widget_show (entry); + + obj->priv->filter_entry = entry; + + g_signal_connect_swapped (entry, "activate", + G_CALLBACK (on_entry_filter_activate), + obj); + g_signal_connect_swapped (entry, "focus_out_event", + G_CALLBACK (on_entry_filter_activate), + obj); + + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (expander), vbox); +} + +static void +gedit_file_browser_widget_init (GeditFileBrowserWidget * obj) +{ + obj->priv = GEDIT_FILE_BROWSER_WIDGET_GET_PRIVATE (obj); + + obj->priv->bookmarks_hash = g_hash_table_new_full (g_file_hash, + (GEqualFunc)g_file_equal, + g_object_unref, + free_name_icon); + + gtk_box_set_spacing (GTK_BOX (obj), 3); + + obj->priv->busy_cursor = gdk_cursor_new (GDK_WATCH); +} + +/* Private */ + +static void +update_sensitivity (GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkAction *action; + gint mode; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) { + gtk_action_group_set_sensitive (obj->priv->action_group, + TRUE); + gtk_action_group_set_sensitive (obj->priv->bookmark_action_group, + FALSE); + + mode = + gedit_file_browser_store_get_filter_mode + (GEDIT_FILE_BROWSER_STORE (model)); + + action = + gtk_action_group_get_action (obj->priv->action_group, + "FilterHidden"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + !(mode & + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN)); + } else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + gtk_action_group_set_sensitive (obj->priv->action_group, + FALSE); + gtk_action_group_set_sensitive (obj->priv->bookmark_action_group, + TRUE); + + /* Set the filter toggle to normal up state, just for visual pleasure */ + action = + gtk_action_group_get_action (obj->priv->action_group, + "FilterHidden"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + FALSE); + } + + on_selection_changed (gtk_tree_view_get_selection + (GTK_TREE_VIEW (obj->priv->treeview)), obj); +} + +static gboolean +gedit_file_browser_widget_get_first_selected (GeditFileBrowserWidget *obj, + GtkTreeIter *iter) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeModel *model; + GList *rows = gtk_tree_selection_get_selected_rows (selection, &model); + gboolean result; + + if (!rows) + return FALSE; + + result = gtk_tree_model_get_iter(model, iter, (GtkTreePath *)(rows->data)); + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result; +} + +static gboolean +popup_menu (GeditFileBrowserWidget * obj, GdkEventButton * event, GtkTreeModel * model) +{ + GtkWidget *menu; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + menu = gtk_ui_manager_get_widget (obj->priv->manager, "/FilePopup"); + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + menu = gtk_ui_manager_get_widget (obj->priv->manager, "/BookmarkPopup"); + else + return FALSE; + + g_return_val_if_fail (menu != NULL, FALSE); + + if (event != NULL) { + GtkTreeSelection *selection; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (gtk_tree_selection_count_selected_rows (selection) <= 1) { + GtkTreePath *path; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (obj->priv->treeview), + (gint)event->x, (gint)event->y, + &path, NULL, NULL, NULL)) + { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + gtk_tree_path_free (path); + } + } + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + event->button, event->time); + } else { + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, + gedit_utils_menu_position_under_tree_view, + obj->priv->treeview, 0, + gtk_get_current_event_time ()); + gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); + } + + return TRUE; +} + +static gboolean +filter_glob (GeditFileBrowserWidget * obj, GeditFileBrowserStore * store, + GtkTreeIter * iter, gpointer user_data) +{ + gchar *name; + gboolean result; + guint flags; + + if (obj->priv->filter_pattern == NULL) + return TRUE; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (FILE_IS_DIR (flags) || FILE_IS_DUMMY (flags)) + result = TRUE; + else + result = + g_pattern_match_string (obj->priv->filter_pattern, + name); + + g_free (name); + + return result; +} + +static void +rename_selected_file (GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (gedit_file_browser_widget_get_first_selected (obj, &iter)) + gedit_file_browser_view_start_rename (obj->priv->treeview, + &iter); +} + +static GList * +get_deletable_files (GeditFileBrowserWidget *obj) { + GtkTreeSelection *selection; + GtkTreeModel *model; + GList *rows; + GList *row; + GList *paths = NULL; + guint flags; + GtkTreeIter iter; + GtkTreePath *path; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + /* Get all selected files */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (!gtk_tree_model_get_iter (model, &iter, path)) + continue; + + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, + &flags, -1); + + if (FILE_IS_DUMMY (flags)) + continue; + + paths = g_list_append (paths, gtk_tree_path_copy (path)); + } + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return paths; +} + +static gboolean +delete_selected_files (GeditFileBrowserWidget * obj, gboolean trash) +{ + GtkTreeModel *model; + gboolean confirm; + GeditFileBrowserStoreResult result; + GList *rows; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + rows = get_deletable_files (obj); + + if (!rows) + return FALSE; + + if (!trash) { + g_signal_emit (obj, signals[CONFIRM_DELETE], 0, model, rows, &confirm); + + if (!confirm) + return FALSE; + } + + result = gedit_file_browser_store_delete_all (GEDIT_FILE_BROWSER_STORE (model), + rows, trash); + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result == GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +static gboolean +on_file_store_no_trash (GeditFileBrowserStore * store, + GList * files, + GeditFileBrowserWidget * obj) +{ + gboolean confirm = FALSE; + + g_signal_emit (obj, signals[CONFIRM_NO_TRASH], 0, files, &confirm); + + return confirm; +} + +static GFile * +get_topmost_file (GFile * file) +{ + GFile * tmp; + GFile * current; + + current = g_object_ref (file); + + while ((tmp = g_file_get_parent (current)) != NULL) { + g_object_unref (current); + current = tmp; + } + + return current; +} + +static GtkWidget * +create_goto_menu_item (GeditFileBrowserWidget * obj, GList * item, + GdkPixbuf * icon) +{ + GtkWidget *result; + GtkWidget *image; + gchar *unescape; + GdkPixbuf *pixbuf = NULL; + Location *loc; + + loc = (Location *) (item->data); + + if (!get_from_bookmark_file (obj, loc->virtual_root, &unescape, &pixbuf)) { + unescape = gedit_file_browser_utils_file_basename (loc->virtual_root); + + if (icon) + pixbuf = g_object_ref (icon); + } + + if (pixbuf) { + image = gtk_image_new_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + + gtk_widget_show (image); + + result = gtk_image_menu_item_new_with_label (unescape); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (result), + image); + } else { + result = gtk_menu_item_new_with_label (unescape); + } + + g_object_set_data (G_OBJECT (result), LOCATION_DATA_KEY, item); + g_signal_connect (result, "activate", + G_CALLBACK (on_location_jump_activate), obj); + + gtk_widget_show (result); + + g_free (unescape); + + return result; +} + +static GList * +list_next_iterator (GList * list) +{ + if (!list) + return NULL; + + return list->next; +} + +static GList * +list_prev_iterator (GList * list) +{ + if (!list) + return NULL; + + return list->prev; +} + +static void +jump_to_location (GeditFileBrowserWidget * obj, GList * item, + gboolean previous) +{ + Location *loc; + GtkWidget *widget; + GList *children; + GList *child; + GList *(*iter_func) (GList *); + GtkWidget *menu_from; + GtkWidget *menu_to; + gchar *root; + gchar *virtual_root; + + if (!obj->priv->locations) + return; + + if (previous) { + iter_func = list_next_iterator; + menu_from = obj->priv->location_previous_menu; + menu_to = obj->priv->location_next_menu; + } else { + iter_func = list_prev_iterator; + menu_from = obj->priv->location_next_menu; + menu_to = obj->priv->location_previous_menu; + } + + children = gtk_container_get_children (GTK_CONTAINER (menu_from)); + child = children; + + /* This is the menuitem for the current location, which is the first + to be added to the menu */ + widget = obj->priv->current_location_menu_item; + + while (obj->priv->current_location != item) { + if (widget) { + /* Prepend the menu item to the menu */ + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu_to), + widget); + + g_object_unref (widget); + } + + widget = GTK_WIDGET (child->data); + + /* Make sure the widget isn't destroyed when removed */ + g_object_ref (widget); + gtk_container_remove (GTK_CONTAINER (menu_from), widget); + + obj->priv->current_location_menu_item = widget; + + if (obj->priv->current_location == NULL) { + obj->priv->current_location = obj->priv->locations; + + if (obj->priv->current_location == item) + break; + } else { + obj->priv->current_location = + iter_func (obj->priv->current_location); + } + + child = child->next; + } + + g_list_free (children); + + obj->priv->changing_location = TRUE; + + loc = (Location *) (obj->priv->current_location->data); + + /* Set the new root + virtual root */ + root = g_file_get_uri (loc->root); + virtual_root = g_file_get_uri (loc->virtual_root); + + gedit_file_browser_widget_set_root_and_virtual_root (obj, + root, + virtual_root); + + g_free (root); + g_free (virtual_root); + + obj->priv->changing_location = FALSE; +} + +static void +clear_next_locations (GeditFileBrowserWidget * obj) +{ + GList *children; + GList *item; + + if (obj->priv->current_location == NULL) + return; + + while (obj->priv->current_location->prev) { + location_free ((Location *) (obj->priv->current_location-> + prev->data)); + obj->priv->locations = + g_list_remove_link (obj->priv->locations, + obj->priv->current_location->prev); + } + + children = + gtk_container_get_children (GTK_CONTAINER + (obj->priv->location_next_menu)); + + for (item = children; item; item = item->next) { + gtk_container_remove (GTK_CONTAINER + (obj->priv->location_next_menu), + GTK_WIDGET (item->data)); + } + + g_list_free (children); + + gtk_action_set_sensitive (gtk_action_group_get_action + (obj->priv->action_group_sensitive, + "DirectoryNext"), FALSE); +} + +static void +update_filter_mode (GeditFileBrowserWidget * obj, + GtkAction * action, + GeditFileBrowserStoreFilterMode mode) +{ + gboolean active = + gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + gint now; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) { + now = + gedit_file_browser_store_get_filter_mode + (GEDIT_FILE_BROWSER_STORE (model)); + + if (active) + now &= ~mode; + else + now |= mode; + + gedit_file_browser_store_set_filter_mode + (GEDIT_FILE_BROWSER_STORE (model), now); + } +} + +static void +set_filter_pattern_real (GeditFileBrowserWidget * obj, + gchar const * pattern, + gboolean update_entry) +{ + GtkTreeModel *model; + + model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (pattern != NULL && *pattern == '\0') + pattern = NULL; + + if (pattern == NULL && obj->priv->filter_pattern_str == NULL) + return; + + if (pattern != NULL && obj->priv->filter_pattern_str != NULL && + strcmp (pattern, obj->priv->filter_pattern_str) == 0) + return; + + /* Free the old pattern */ + g_free (obj->priv->filter_pattern_str); + obj->priv->filter_pattern_str = g_strdup (pattern); + + if (obj->priv->filter_pattern) { + g_pattern_spec_free (obj->priv->filter_pattern); + obj->priv->filter_pattern = NULL; + } + + if (pattern == NULL) { + if (obj->priv->glob_filter_id != 0) { + gedit_file_browser_widget_remove_filter (obj, + obj-> + priv-> + glob_filter_id); + obj->priv->glob_filter_id = 0; + } + } else { + obj->priv->filter_pattern = g_pattern_spec_new (pattern); + + if (obj->priv->glob_filter_id == 0) + obj->priv->glob_filter_id = + gedit_file_browser_widget_add_filter (obj, + filter_glob, + NULL, + NULL); + } + + if (update_entry) { + if (obj->priv->filter_pattern_str == NULL) + gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry), + ""); + else { + gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry), + obj->priv->filter_pattern_str); + + gtk_expander_set_expanded (GTK_EXPANDER (obj->priv->filter_expander), + TRUE); + } + } + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE + (model)); + + g_object_notify (G_OBJECT (obj), "filter-pattern"); +} + + +/* Public */ + +GtkWidget * +gedit_file_browser_widget_new (const gchar *data_dir) +{ + GeditFileBrowserWidget *obj = + g_object_new (GEDIT_TYPE_FILE_BROWSER_WIDGET, NULL); + + create_toolbar (obj, data_dir); + create_combo (obj); + create_tree (obj); + create_filter (obj); + + gedit_file_browser_widget_show_bookmarks (obj); + + return GTK_WIDGET (obj); +} + +void +gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget * obj) +{ + /* Select bookmarks in the combo box */ + g_signal_handlers_block_by_func (obj->priv->combo, + on_combo_changed, obj); + combo_set_active_by_id (obj, BOOKMARKS_ID); + g_signal_handlers_unblock_by_func (obj->priv->combo, + on_combo_changed, obj); + + check_current_item (obj, FALSE); + + gedit_file_browser_view_set_model (obj->priv->treeview, + GTK_TREE_MODEL (obj->priv-> + bookmarks_store)); +} + +static void +show_files_real (GeditFileBrowserWidget *obj, + gboolean do_root_changed) +{ + gedit_file_browser_view_set_model (obj->priv->treeview, + GTK_TREE_MODEL (obj->priv-> + file_store)); + + if (do_root_changed) + on_virtual_root_changed (obj->priv->file_store, NULL, obj); +} + +void +gedit_file_browser_widget_show_files (GeditFileBrowserWidget * obj) +{ + show_files_real (obj, TRUE); +} + +void +gedit_file_browser_widget_set_root_and_virtual_root (GeditFileBrowserWidget *obj, + gchar const *root, + gchar const *virtual_root) +{ + GeditFileBrowserStoreResult result; + + if (!virtual_root) + result = + gedit_file_browser_store_set_root_and_virtual_root + (obj->priv->file_store, root, root); + else + result = + gedit_file_browser_store_set_root_and_virtual_root + (obj->priv->file_store, root, virtual_root); + + if (result == GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE) + show_files_real (obj, TRUE); +} + +void +gedit_file_browser_widget_set_root (GeditFileBrowserWidget * obj, + gchar const *root, + gboolean virtual_root) +{ + GFile *file; + GFile *parent; + gchar *str; + + if (!virtual_root) { + gedit_file_browser_widget_set_root_and_virtual_root (obj, + root, + NULL); + return; + } + + if (!root) + return; + + file = g_file_new_for_uri (root); + parent = get_topmost_file (file); + str = g_file_get_uri (parent); + + gedit_file_browser_widget_set_root_and_virtual_root + (obj, str, root); + + g_free (str); + + g_object_unref (file); + g_object_unref (parent); +} + +GeditFileBrowserStore * +gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget * obj) +{ + return obj->priv->file_store; +} + +GeditFileBookmarksStore * +gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget * obj) +{ + return obj->priv->bookmarks_store; +} + +GeditFileBrowserView * +gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget * obj) +{ + return obj->priv->treeview; +} + +GtkUIManager * +gedit_file_browser_widget_get_ui_manager (GeditFileBrowserWidget * obj) +{ + return obj->priv->manager; +} + +GtkWidget * +gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget * obj) +{ + return obj->priv->filter_entry; +} + +gulong +gedit_file_browser_widget_add_filter (GeditFileBrowserWidget * obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + FilterFunc *f; + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + f = filter_func_new (obj, func, user_data, notify); + obj->priv->filter_funcs = + g_slist_append (obj->priv->filter_funcs, f); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE + (model)); + + return f->id; +} + +void +gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget * obj, + gulong id) +{ + GSList *item; + FilterFunc *func; + + for (item = obj->priv->filter_funcs; item; item = item->next) + { + func = (FilterFunc *) (item->data); + + if (func->id == id) + { + if (func->destroy_notify) + func->destroy_notify (func->user_data); + + obj->priv->filter_funcs = + g_slist_remove_link (obj->priv->filter_funcs, + item); + g_free (func); + break; + } + } +} + +void +gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget * obj, + gchar const *pattern) +{ + set_filter_pattern_real (obj, pattern, TRUE); +} + +gboolean +gedit_file_browser_widget_get_selected_directory (GeditFileBrowserWidget * obj, + GtkTreeIter * iter) +{ + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeIter parent; + guint flags; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + if (!gedit_file_browser_widget_get_first_selected (obj, iter)) { + if (!gedit_file_browser_store_get_iter_virtual_root + (GEDIT_FILE_BROWSER_STORE (model), iter)) + return FALSE; + } + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DIR (flags)) { + /* Get the parent, because the selection is a file */ + gtk_tree_model_iter_parent (model, &parent, iter); + *iter = parent; + } + + return TRUE; +} + +static guint +gedit_file_browser_widget_get_num_selected_files_or_directories (GeditFileBrowserWidget *obj, + guint *files, + guint *dirs) +{ + GList *rows, *row; + GtkTreePath *path; + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + guint result = 0; + GtkTreeSelection *selection; + GtkTreeModel *model; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + return 0; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + /* Get iter from path */ + if (!gtk_tree_model_get_iter (model, &iter, path)) + continue; + + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags)) { + if (!FILE_IS_DIR (flags)) + ++(*files); + else + ++(*dirs); + + ++result; + } + } + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result; +} + +typedef struct +{ + GeditFileBrowserWidget *widget; + GCancellable *cancellable; +} AsyncData; + +static AsyncData * +async_data_new (GeditFileBrowserWidget *widget) +{ + AsyncData *ret; + + ret = g_new (AsyncData, 1); + ret->widget = widget; + + cancel_async_operation (widget); + widget->priv->cancellable = g_cancellable_new (); + + ret->cancellable = g_object_ref (widget->priv->cancellable); + + return ret; +} + +static void +async_free (AsyncData *async) +{ + g_object_unref (async->cancellable); + g_free (async); +} + +static void +set_busy (GeditFileBrowserWidget *obj, gboolean busy) +{ + GdkCursor *cursor; + GdkWindow *window; + + window = gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)); + + if (!GDK_IS_WINDOW (window)) + return; + + if (busy) + { + cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + } + else + { + gdk_window_set_cursor (window, NULL); + } +} + +static void try_mount_volume (GeditFileBrowserWidget *widget, GVolume *volume); + +static void +activate_mount (GeditFileBrowserWidget *widget, + GVolume *volume, + GMount *mount) +{ + GFile *root; + gchar *uri; + + if (!mount) + { + gchar *message; + gchar *name; + + name = g_volume_get_name (volume); + message = g_strdup_printf (_("No mount object for mounted volume: %s"), name); + + g_signal_emit (widget, + signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + message); + + g_free (name); + g_free (message); + return; + } + + root = g_mount_get_root (mount); + uri = g_file_get_uri (root); + + gedit_file_browser_widget_set_root (widget, uri, FALSE); + + g_free (uri); + g_object_unref (root); +} + +static void +try_activate_drive (GeditFileBrowserWidget *widget, + GDrive *drive) +{ + GList *volumes; + GVolume *volume; + GMount *mount; + + volumes = g_drive_get_volumes (drive); + + volume = G_VOLUME (volumes->data); + mount = g_volume_get_mount (volume); + + if (mount) + { + /* try set the root of the mount */ + activate_mount (widget, volume, mount); + g_object_unref (mount); + } + else + { + /* try to mount it then? */ + try_mount_volume (widget, volume); + } + + g_list_foreach (volumes, (GFunc)g_object_unref, NULL); + g_list_free (volumes); +} + +static void +poll_for_media_cb (GDrive *drive, + GAsyncResult *res, + AsyncData *async) +{ + GError *error = NULL; + + /* check for cancelled state */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_free (async); + return; + } + + /* finish poll operation */ + set_busy (async->widget, FALSE); + + if (g_drive_poll_for_media_finish (drive, res, &error) && + g_drive_has_media (drive) && + g_drive_has_volumes (drive)) + { + try_activate_drive (async->widget, drive); + } + else + { + gchar *message; + gchar *name; + + name = g_drive_get_name (drive); + message = g_strdup_printf (_("Could not open media: %s"), name); + + g_signal_emit (async->widget, + signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + message); + + g_free (name); + g_free (message); + + g_error_free (error); + } + + async_free (async); +} + +static void +mount_volume_cb (GVolume *volume, + GAsyncResult *res, + AsyncData *async) +{ + GError *error = NULL; + + /* check for cancelled state */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_free (async); + return; + } + + if (g_volume_mount_finish (volume, res, &error)) + { + GMount *mount; + + mount = g_volume_get_mount (volume); + activate_mount (async->widget, volume, mount); + + if (mount) + g_object_unref (mount); + } + else + { + gchar *message; + gchar *name; + + name = g_volume_get_name (volume); + message = g_strdup_printf (_("Could not mount volume: %s"), name); + + g_signal_emit (async->widget, + signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + message); + + g_free (name); + g_free (message); + + g_error_free (error); + } + + set_busy (async->widget, FALSE); + async_free (async); +} + +static void +activate_drive (GeditFileBrowserWidget *obj, + GtkTreeIter *iter) +{ + GDrive *drive; + AsyncData *async; + + gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store), iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &drive, -1); + + /* most common use case is a floppy drive, we'll poll for media and + go from there */ + async = async_data_new (obj); + g_drive_poll_for_media (drive, + async->cancellable, + (GAsyncReadyCallback)poll_for_media_cb, + async); + + g_object_unref (drive); + set_busy (obj, TRUE); +} + +static void +try_mount_volume (GeditFileBrowserWidget *widget, + GVolume *volume) +{ + GMountOperation *operation; + AsyncData *async; + + operation = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget)))); + async = async_data_new (widget); + + g_volume_mount (volume, + G_MOUNT_MOUNT_NONE, + operation, + async->cancellable, + (GAsyncReadyCallback)mount_volume_cb, + async); + + g_object_unref (operation); + set_busy (widget, TRUE); +} + +static void +activate_volume (GeditFileBrowserWidget *obj, + GtkTreeIter *iter) +{ + GVolume *volume; + + gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store), iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &volume, -1); + + /* see if we can mount the volume */ + try_mount_volume (obj, volume); + g_object_unref (volume); +} + +void +gedit_file_browser_widget_refresh (GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + gedit_file_browser_store_refresh (GEDIT_FILE_BROWSER_STORE + (model)); + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + g_hash_table_ref (obj->priv->bookmarks_hash); + g_hash_table_destroy (obj->priv->bookmarks_hash); + + gedit_file_bookmarks_store_refresh + (GEDIT_FILE_BOOKMARKS_STORE (model)); + } +} + +void +gedit_file_browser_widget_history_back (GeditFileBrowserWidget *obj) +{ + if (obj->priv->locations) { + if (obj->priv->current_location) + jump_to_location (obj, + obj->priv->current_location-> + next, TRUE); + else { + jump_to_location (obj, obj->priv->locations, TRUE); + } + } +} + +void +gedit_file_browser_widget_history_forward (GeditFileBrowserWidget *obj) +{ + if (obj->priv->locations) + jump_to_location (obj, obj->priv->current_location->prev, + FALSE); +} + +static void +bookmark_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gchar *uri; + gint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &flags, -1); + + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE) + { + /* handle a drive node */ + gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store); + activate_drive (obj, iter); + return; + } + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME) + { + /* handle a volume node */ + gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store); + activate_volume (obj, iter); + return; + } + + uri = + gedit_file_bookmarks_store_get_uri + (GEDIT_FILE_BOOKMARKS_STORE (model), iter); + + if (uri) { + /* here we check if the bookmark is a mount point, or if it + is a remote bookmark. If that's the case, we will set the + root to the uri of the bookmark and not try to set the + topmost parent as root (since that may as well not be the + mount point anymore) */ + if ((flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT) || + (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK)) { + gedit_file_browser_widget_set_root (obj, + uri, + FALSE); + } else { + gedit_file_browser_widget_set_root (obj, + uri, + TRUE); + } + } else { + g_warning ("No uri!"); + } + + g_free (uri); +} + +static void +file_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gchar *uri; + gint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + if (!FILE_IS_DIR (flags) && !FILE_IS_DUMMY (flags)) { + g_signal_emit (obj, signals[URI_ACTIVATED], 0, uri); + } + + g_free (uri); +} + +static gboolean +directory_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gboolean result = FALSE; + GError *error = NULL; + gchar *uri = NULL; + GeditFileBrowserStoreFlag flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + if (FILE_IS_DIR (flags)) { + result = TRUE; + + if (!gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (obj)), uri, GDK_CURRENT_TIME, &error)) { + g_signal_emit (obj, signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY, + error->message); + + g_error_free (error); + error = NULL; + } + } + + g_free (uri); + + return result; +} + +static void +on_bookmark_activated (GeditFileBrowserView *tree_view, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + + bookmark_open (obj, model, iter); +} + +static void +on_file_activated (GeditFileBrowserView *tree_view, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + + file_open (obj, model, iter); +} + +static gboolean +virtual_root_is_root (GeditFileBrowserWidget * obj, + GeditFileBrowserStore * model) +{ + GtkTreeIter root; + GtkTreeIter virtual_root; + + if (!gedit_file_browser_store_get_iter_root (model, &root)) + return TRUE; + + if (!gedit_file_browser_store_get_iter_virtual_root (model, &virtual_root)) + return TRUE; + + return gedit_file_browser_store_iter_equal (model, &root, &virtual_root); +} + +static void +on_virtual_root_changed (GeditFileBrowserStore * model, + GParamSpec * param, + GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + gchar *uri; + gchar *root_uri; + GtkTreeIter root; + GtkAction *action; + Location *loc; + GdkPixbuf *pixbuf; + + if (gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)) != + GTK_TREE_MODEL (obj->priv->file_store)) + { + show_files_real (obj, FALSE); + } + + if (gedit_file_browser_store_get_iter_virtual_root (model, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, -1); + + if (gedit_file_browser_store_get_iter_root (model, &root)) { + if (!obj->priv->changing_location) { + /* Remove all items from obj->priv->current_location on */ + if (obj->priv->current_location) + clear_next_locations (obj); + + root_uri = + gedit_file_browser_store_get_root + (model); + + loc = g_new (Location, 1); + loc->root = g_file_new_for_uri (root_uri); + loc->virtual_root = g_file_new_for_uri (uri); + g_free (root_uri); + + if (obj->priv->current_location) { + /* Add current location to the menu so we can go back + to it later */ + gtk_menu_shell_prepend + (GTK_MENU_SHELL + (obj->priv-> + location_previous_menu), + obj->priv-> + current_location_menu_item); + } + + obj->priv->locations = + g_list_prepend (obj->priv->locations, + loc); + + gtk_tree_model_get (GTK_TREE_MODEL (model), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON, + &pixbuf, -1); + + obj->priv->current_location = + obj->priv->locations; + obj->priv->current_location_menu_item = + create_goto_menu_item (obj, + obj->priv-> + current_location, + pixbuf); + + g_object_ref_sink (obj->priv-> + current_location_menu_item); + + if (pixbuf) + g_object_unref (pixbuf); + + } + + action = + gtk_action_group_get_action (obj->priv-> + action_group, + "DirectoryUp"); + gtk_action_set_sensitive (action, + !virtual_root_is_root (obj, model)); + + action = + gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryPrevious"); + gtk_action_set_sensitive (action, + obj->priv-> + current_location != NULL + && obj->priv-> + current_location->next != + NULL); + + action = + gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryNext"); + gtk_action_set_sensitive (action, + obj->priv-> + current_location != NULL + && obj->priv-> + current_location->prev != + NULL); + } + + check_current_item (obj, TRUE); + g_free (uri); + } else { + g_message ("NO!"); + } +} + +static void +on_model_set (GObject * gobject, GParamSpec * arg1, + GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gobject)); + + clear_signals (obj); + + if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + clear_next_locations (obj); + + /* Add the current location to the back menu */ + if (obj->priv->current_location) { + GtkAction *action; + + gtk_menu_shell_prepend (GTK_MENU_SHELL (obj->priv->location_previous_menu), + obj->priv->current_location_menu_item); + + g_object_unref (obj->priv->current_location_menu_item); + obj->priv->current_location = NULL; + obj->priv->current_location_menu_item = NULL; + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryPrevious"); + gtk_action_set_sensitive (action, TRUE); + } + + gtk_widget_set_sensitive (obj->priv->filter_expander, FALSE); + + add_signal (obj, gobject, + g_signal_connect (gobject, "bookmark-activated", + G_CALLBACK + (on_bookmark_activated), obj)); + } else if (GEDIT_IS_FILE_BROWSER_STORE (model)) { + /* make sure any async operation is cancelled */ + cancel_async_operation (obj); + + add_signal (obj, gobject, + g_signal_connect (gobject, "file-activated", + G_CALLBACK + (on_file_activated), obj)); + + add_signal (obj, model, + g_signal_connect (model, "no-trash", + G_CALLBACK + (on_file_store_no_trash), obj)); + + gtk_widget_set_sensitive (obj->priv->filter_expander, TRUE); + } + + update_sensitivity (obj); +} + +static void +on_file_store_error (GeditFileBrowserStore * store, guint code, + gchar * message, GeditFileBrowserWidget * obj) +{ + g_signal_emit (obj, signals[ERROR], 0, code, message); +} + +static void +on_treeview_error (GeditFileBrowserView * tree_view, guint code, + gchar * message, GeditFileBrowserWidget * obj) +{ + g_signal_emit (obj, signals[ERROR], 0, code, message); +} + +static void +on_combo_changed (GtkComboBox * combo, GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + guint id; + gchar * uri; + GFile * file; + + if (!gtk_combo_box_get_active_iter (combo, &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->combo_model), &iter, + COLUMN_ID, &id, -1); + + switch (id) { + case BOOKMARKS_ID: + gedit_file_browser_widget_show_bookmarks (obj); + break; + + case PATH_ID: + gtk_tree_model_get (GTK_TREE_MODEL + (obj->priv->combo_model), &iter, + COLUMN_FILE, &file, -1); + + uri = g_file_get_uri (file); + gedit_file_browser_store_set_virtual_root_from_string + (obj->priv->file_store, uri); + + g_free (uri); + g_object_unref (file); + break; + } +} + +static gboolean +on_treeview_popup_menu (GeditFileBrowserView * treeview, + GeditFileBrowserWidget * obj) +{ + return popup_menu (obj, NULL, gtk_tree_view_get_model (GTK_TREE_VIEW (treeview))); +} + +static gboolean +on_treeview_button_press_event (GeditFileBrowserView * treeview, + GdkEventButton * event, + GeditFileBrowserWidget * obj) +{ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + return popup_menu (obj, event, + gtk_tree_view_get_model (GTK_TREE_VIEW (treeview))); + } + + return FALSE; +} + +static gboolean +do_change_directory (GeditFileBrowserWidget * obj, + GdkEventKey * event) +{ + GtkAction * action = NULL; + + if ((event->state & + (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK & ~GDK_MOD1_MASK)) == + event->state && event->keyval == GDK_BackSpace) + action = gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryPrevious"); + else if (!((event->state & GDK_MOD1_MASK) && + (event->state & (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK)) == event->state)) + return FALSE; + + switch (event->keyval) { + case GDK_Left: + action = gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryPrevious"); + break; + case GDK_Right: + action = gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryNext"); + break; + case GDK_Up: + action = gtk_action_group_get_action (obj->priv-> + action_group, + "DirectoryUp"); + break; + default: + break; + } + + if (action != NULL) { + gtk_action_activate (action); + return TRUE; + } + + return FALSE; +} + +static gboolean +on_treeview_key_press_event (GeditFileBrowserView * treeview, + GdkEventKey * event, + GeditFileBrowserWidget * obj) +{ + guint modifiers; + + if (do_change_directory (obj, event)) + return TRUE; + + if (!GEDIT_IS_FILE_BROWSER_STORE + (gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)))) + return FALSE; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + if (event->keyval == GDK_Delete + || event->keyval == GDK_KP_Delete) { + + if ((event->state & modifiers) == GDK_SHIFT_MASK) { + if (obj->priv->enable_delete) { + delete_selected_files (obj, FALSE); + return TRUE; + } + } else if ((event->state & modifiers) == 0) { + delete_selected_files (obj, TRUE); + return TRUE; + } + } + + if ((event->keyval == GDK_F2) + && (event->state & modifiers) == 0) { + rename_selected_file (obj); + + return TRUE; + } + + return FALSE; +} + +static void +on_selection_changed (GtkTreeSelection * selection, + GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + guint selected = 0; + guint files = 0; + guint dirs = 0; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + { + selected = gedit_file_browser_widget_get_num_selected_files_or_directories (obj, + &files, + &dirs); + } + + gtk_action_group_set_sensitive (obj->priv->action_group_selection, + selected > 0); + gtk_action_group_set_sensitive (obj->priv->action_group_file_selection, + (selected > 0) && (selected == files)); + gtk_action_group_set_sensitive (obj->priv->action_group_single_selection, + selected == 1); + gtk_action_group_set_sensitive (obj->priv->action_group_single_most_selection, + selected <= 1); +} + +static gboolean +on_entry_filter_activate (GeditFileBrowserWidget * obj) +{ + gchar const *text; + + text = gtk_entry_get_text (GTK_ENTRY (obj->priv->filter_entry)); + set_filter_pattern_real (obj, text, FALSE); + + return FALSE; +} + +static void +on_location_jump_activate (GtkMenuItem * item, + GeditFileBrowserWidget * obj) +{ + GList *location; + + location = g_object_get_data (G_OBJECT (item), LOCATION_DATA_KEY); + + if (obj->priv->current_location) { + jump_to_location (obj, location, + g_list_position (obj->priv->locations, + location) > + g_list_position (obj->priv->locations, + obj->priv-> + current_location)); + } else { + jump_to_location (obj, location, TRUE); + } + +} + +static void +on_bookmarks_row_changed (GtkTreeModel * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserWidget *obj) +{ + add_bookmark_hash (obj, iter); +} + +static void +on_bookmarks_row_deleted (GtkTreeModel * model, + GtkTreePath * path, + GeditFileBrowserWidget *obj) +{ + GtkTreeIter iter; + gchar * uri; + GFile * file; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + + uri = gedit_file_bookmarks_store_get_uri (obj->priv->bookmarks_store, &iter); + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + g_hash_table_remove (obj->priv->bookmarks_hash, file); + + g_object_unref (file); + g_free (uri); +} + +static void +on_filter_mode_changed (GeditFileBrowserStore * model, + GParamSpec * param, + GeditFileBrowserWidget * obj) +{ + gint mode; + GtkToggleAction * action; + gboolean active; + + mode = gedit_file_browser_store_get_filter_mode (model); + + action = GTK_TOGGLE_ACTION (gtk_action_group_get_action (obj->priv->action_group, + "FilterHidden")); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); + + if (active != gtk_toggle_action_get_active (action)) + gtk_toggle_action_set_active (action, active); + + action = GTK_TOGGLE_ACTION (gtk_action_group_get_action (obj->priv->action_group, + "FilterBinary")); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); + + if (active != gtk_toggle_action_get_active (action)) + gtk_toggle_action_set_active (action, active); +} + +static void +on_action_directory_next (GtkAction * action, GeditFileBrowserWidget * obj) +{ + gedit_file_browser_widget_history_forward (obj); +} + +static void +on_action_directory_previous (GtkAction * action, + GeditFileBrowserWidget * obj) +{ + gedit_file_browser_widget_history_back (obj); +} + +static void +on_action_directory_up (GtkAction * action, + GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + gedit_file_browser_store_set_virtual_root_up (GEDIT_FILE_BROWSER_STORE (model)); +} + +static void +on_action_directory_new (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeIter parent; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (obj, &parent)) + return; + + if (gedit_file_browser_store_new_directory + (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) { + gedit_file_browser_view_start_rename (obj->priv->treeview, + &iter); + } +} + +static void +on_action_file_open (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GList *rows; + GList *row; + GtkTreeIter iter; + GtkTreePath *path; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + file_open (obj, model, &iter); + + gtk_tree_path_free (path); + } + + g_list_free (rows); +} + +static void +on_action_file_new (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeIter parent; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (obj, &parent)) + return; + + if (gedit_file_browser_store_new_file + (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) { + gedit_file_browser_view_start_rename (obj->priv->treeview, + &iter); + } +} + +static void +on_action_file_rename (GtkAction * action, GeditFileBrowserWidget * obj) +{ + rename_selected_file (obj); +} + +static void +on_action_file_delete (GtkAction * action, GeditFileBrowserWidget * obj) +{ + delete_selected_files (obj, FALSE); +} + +static void +on_action_file_move_to_trash (GtkAction * action, GeditFileBrowserWidget * obj) +{ + delete_selected_files (obj, TRUE); +} + +static void +on_action_directory_refresh (GtkAction * action, + GeditFileBrowserWidget * obj) +{ + gedit_file_browser_widget_refresh (obj); +} + +static void +on_action_directory_open (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GList *rows; + GList *row; + gboolean directory_opened = FALSE; + GtkTreeIter iter; + GtkTreePath *path; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + directory_opened |= directory_open (obj, model, &iter); + + gtk_tree_path_free (path); + } + + if (!directory_opened) { + if (gedit_file_browser_widget_get_selected_directory (obj, &iter)) + directory_open (obj, model, &iter); + } + + g_list_free (rows); +} + +static void +on_action_filter_hidden (GtkAction * action, GeditFileBrowserWidget * obj) +{ + update_filter_mode (obj, + action, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); +} + +static void +on_action_filter_binary (GtkAction * action, GeditFileBrowserWidget * obj) +{ + update_filter_mode (obj, + action, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); +} + +static void +on_action_bookmark_open (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + return; + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + bookmark_open (obj, model, &iter); +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-widget.h b/plugins/filebrowser/gedit-file-browser-widget.h new file mode 100755 index 00000000..e9cc2a0e --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.h @@ -0,0 +1,121 @@ +/* + * gedit-file-browser-widget.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <[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, 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_FILE_BROWSER_WIDGET_H__ +#define __GEDIT_FILE_BROWSER_WIDGET_H__ + +#include <gtk/gtk.h> +#include "gedit-file-browser-store.h" +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-view.h" + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BROWSER_WIDGET (gedit_file_browser_widget_get_type ()) +#define GEDIT_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget)) +#define GEDIT_FILE_BROWSER_WIDGET_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget const)) +#define GEDIT_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass)) +#define GEDIT_IS_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET)) +#define GEDIT_IS_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET)) +#define GEDIT_FILE_BROWSER_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass)) + +typedef struct _GeditFileBrowserWidget GeditFileBrowserWidget; +typedef struct _GeditFileBrowserWidgetClass GeditFileBrowserWidgetClass; +typedef struct _GeditFileBrowserWidgetPrivate GeditFileBrowserWidgetPrivate; + +typedef +gboolean (*GeditFileBrowserWidgetFilterFunc) (GeditFileBrowserWidget * obj, + GeditFileBrowserStore * + model, GtkTreeIter * iter, + gpointer user_data); + +struct _GeditFileBrowserWidget +{ + GtkVBox parent; + + GeditFileBrowserWidgetPrivate *priv; +}; + +struct _GeditFileBrowserWidgetClass +{ + GtkVBoxClass parent_class; + + /* Signals */ + void (*uri_activated) (GeditFileBrowserWidget * widget, + gchar const *uri); + void (*error) (GeditFileBrowserWidget * widget, + guint code, + gchar const *message); + gboolean (*confirm_delete) (GeditFileBrowserWidget * widget, + GeditFileBrowserStore * model, + GList *list); + gboolean (*confirm_no_trash) (GeditFileBrowserWidget * widget, + GList *list); +}; + +GType gedit_file_browser_widget_get_type (void) G_GNUC_CONST; +GType gedit_file_browser_widget_register_type (GTypeModule * module); + +GtkWidget *gedit_file_browser_widget_new (const gchar *data_dir); + +void gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget * obj); +void gedit_file_browser_widget_show_files (GeditFileBrowserWidget * obj); + +void gedit_file_browser_widget_set_root (GeditFileBrowserWidget * obj, + gchar const *root, + gboolean virtual_root); +void +gedit_file_browser_widget_set_root_and_virtual_root (GeditFileBrowserWidget * obj, + gchar const *root, + gchar const *virtual_root); + +gboolean +gedit_file_browser_widget_get_selected_directory (GeditFileBrowserWidget * obj, + GtkTreeIter * iter); + +GeditFileBrowserStore * +gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget * obj); +GeditFileBookmarksStore * +gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget * obj); +GeditFileBrowserView * +gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget * obj); +GtkWidget * +gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget * obj); + +GtkUIManager * +gedit_file_browser_widget_get_ui_manager (GeditFileBrowserWidget * obj); + +gulong gedit_file_browser_widget_add_filter (GeditFileBrowserWidget * obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify); +void gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget * obj, + gulong id); +void gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget * obj, + gchar const *pattern); + +void gedit_file_browser_widget_refresh (GeditFileBrowserWidget * obj); +void gedit_file_browser_widget_history_back (GeditFileBrowserWidget * obj); +void gedit_file_browser_widget_history_forward (GeditFileBrowserWidget * obj); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_WIDGET_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser.schemas.in b/plugins/filebrowser/gedit-file-browser.schemas.in new file mode 100755 index 00000000..c80c8eec --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser.schemas.in @@ -0,0 +1,97 @@ +<mateconfschemafile> + <schemalist> + <schema> + <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/tree_view</key> + <applyto>/apps/gedit-2/plugins/filebrowser/on_load/tree_view</applyto> + <owner>gedit</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Open With Tree View</short> + <long>Open the tree view when the file browser plugin gets loaded instead of the bookmarks view</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/root</key> + <applyto>/apps/gedit-2/plugins/filebrowser/on_load/root</applyto> + <owner>gedit</owner> + <type>string</type> + <default></default> + <locale name="C"> + <short>File Browser Root Directory</short> + <long>The file browser root directory to use when loading the file + browser plugin and onload/tree_view is TRUE.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/virtual_root</key> + <applyto>/apps/gedit-2/plugins/filebrowser/on_load/virtual_root</applyto> + <owner>gedit</owner> + <type>string</type> + <default></default> + <locale name="C"> + <short>File Browser Virtual Root Directory</short> + <long>The file browser virtual root directory to use when loading the + file browser plugin when onload/tree_view is TRUE. The virtual root + must always be below the actual root.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/enable_remote</key> + <applyto>/apps/gedit-2/plugins/filebrowser/on_load/enable_remote</applyto> + <owner>gedit</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Enable Restore of Remote Locations</short> + <long>Sets whether to enable restoring of remote locations.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/gedit-2/plugins/filebrowser/open_at_first_doc</key> + <applyto>/apps/gedit-2/plugins/filebrowser/open_at_first_doc</applyto> + <owner>gedit</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Set Location to First Document</short> + <long>If TRUE the file browser plugin will view the directory of + the first opened document given that the file browser hasn't been + used yet. (Thus this generally applies to opening a document from + the command line or opening it with Caja, etc.)</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/gedit-2/plugins/filebrowser/filter_mode</key> + <applyto>/apps/gedit-2/plugins/filebrowser/filter_mode</applyto> + <owner>gedit</owner> + <type>string</type> + <default>hidden_and_binary</default> + <locale name="C"> + <short>File Browser Filter Mode</short> + <long>This value determines what files get filtered from the file + browser. Valid values are: none (filter nothing), + hidden (filter hidden files), binary (filter binary files) and + hidden_and_binary (filter both hidden and binary files).</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/gedit-2/plugins/filebrowser/filter_pattern</key> + <applyto>/apps/gedit-2/plugins/filebrowser/filter_pattern</applyto> + <owner>gedit</owner> + <type>string</type> + <default></default> + <locale name="C"> + <short>File Browser Filter Pattern</short> + <long>The filter pattern to filter the file browser with. This filter + works on top of the filter_mode.</long> + </locale> + </schema> + </schemalist> +</mateconfschemafile> diff --git a/plugins/modelines/Makefile.am b/plugins/modelines/Makefile.am new file mode 100755 index 00000000..ddcfccc8 --- /dev/null +++ b/plugins/modelines/Makefile.am @@ -0,0 +1,38 @@ +# Modelines Plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +modelinesdir = $(GEDIT_PLUGINS_DATA_DIR)/modelines +modelines_DATA = \ + language-mappings + +plugin_LTLIBRARIES = libmodelines.la + +libmodelines_la_SOURCES = \ + gedit-modeline-plugin.h \ + gedit-modeline-plugin.c \ + modeline-parser.h \ + modeline-parser.c + +libmodelines_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libmodelines_la_LIBADD = $(GEDIT_LIBS) + +plugin_in_files = modelines.gedit-plugin.desktop.in +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = \ + $(plugin_in_files) \ + $(modelines_DATA) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/modelines/gedit-modeline-plugin.c b/plugins/modelines/gedit-modeline-plugin.c new file mode 100755 index 00000000..49fc2f69 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.c @@ -0,0 +1,248 @@ +/* + * gedit-modeline-plugin.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <[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, 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-lib.h> +#include <gmodule.h> +#include "gedit-modeline-plugin.h" +#include "modeline-parser.h" + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> + +#define WINDOW_DATA_KEY "GeditModelinePluginWindowData" +#define DOCUMENT_DATA_KEY "GeditModelinePluginDocumentData" + +typedef struct +{ + gulong tab_added_handler_id; + gulong tab_removed_handler_id; +} WindowData; + +typedef struct +{ + gulong document_loaded_handler_id; + gulong document_saved_handler_id; +} DocumentData; + +static void gedit_modeline_plugin_activate (GeditPlugin *plugin, GeditWindow *window); +static void gedit_modeline_plugin_deactivate (GeditPlugin *plugin, GeditWindow *window); +static GObject *gedit_modeline_plugin_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_param); +static void gedit_modeline_plugin_finalize (GObject *object); + +GEDIT_PLUGIN_REGISTER_TYPE(GeditModelinePlugin, gedit_modeline_plugin) + +static void +window_data_free (WindowData *wdata) +{ + g_slice_free (WindowData, wdata); +} + +static void +document_data_free (DocumentData *ddata) +{ + g_slice_free (DocumentData, ddata); +} + +static void +gedit_modeline_plugin_class_init (GeditModelinePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->constructor = gedit_modeline_plugin_constructor; + object_class->finalize = gedit_modeline_plugin_finalize; + + plugin_class->activate = gedit_modeline_plugin_activate; + plugin_class->deactivate = gedit_modeline_plugin_deactivate; +} + +static GObject * +gedit_modeline_plugin_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_param) +{ + GObject *object; + gchar *data_dir; + + object = G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->constructor (type, + n_construct_properties, + construct_param); + + data_dir = gedit_plugin_get_data_dir (GEDIT_PLUGIN (object)); + + modeline_parser_init (data_dir); + + g_free (data_dir); + + return object; +} + +static void +gedit_modeline_plugin_init (GeditModelinePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin initializing"); +} + +static void +gedit_modeline_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin finalizing"); + + modeline_parser_shutdown (); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->finalize (object); +} + +static void +on_document_loaded_or_saved (GeditDocument *document, + const GError *error, + GtkSourceView *view) +{ + modeline_parser_apply_modeline (view); +} + +static void +connect_handlers (GeditView *view) +{ + DocumentData *data; + GtkTextBuffer *doc; + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + data = g_slice_new (DocumentData); + + data->document_loaded_handler_id = + g_signal_connect (doc, "loaded", + G_CALLBACK (on_document_loaded_or_saved), + view); + data->document_saved_handler_id = + g_signal_connect (doc, "saved", + G_CALLBACK (on_document_loaded_or_saved), + view); + + g_object_set_data_full (G_OBJECT (doc), DOCUMENT_DATA_KEY, + data, (GDestroyNotify) document_data_free); +} + +static void +disconnect_handlers (GeditView *view) +{ + DocumentData *data; + GtkTextBuffer *doc; + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + data = g_object_steal_data (G_OBJECT (doc), DOCUMENT_DATA_KEY); + + if (data) + { + g_signal_handler_disconnect (doc, data->document_loaded_handler_id); + g_signal_handler_disconnect (doc, data->document_saved_handler_id); + + document_data_free (data); + } + else + { + g_warning ("Modeline handlers not found"); + } +} + +static void +on_window_tab_added (GeditWindow *window, + GeditTab *tab, + gpointer user_data) +{ + connect_handlers (gedit_tab_get_view (tab)); +} + +static void +on_window_tab_removed (GeditWindow *window, + GeditTab *tab, + gpointer user_data) +{ + disconnect_handlers (gedit_tab_get_view (tab)); +} + +static void +gedit_modeline_plugin_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *wdata; + GList *views; + GList *l; + + gedit_debug (DEBUG_PLUGINS); + + views = gedit_window_get_views (window); + for (l = views; l != NULL; l = l->next) + { + connect_handlers (GEDIT_VIEW (l->data)); + modeline_parser_apply_modeline (GTK_SOURCE_VIEW (l->data)); + } + g_list_free (views); + + wdata = g_slice_new (WindowData); + + wdata->tab_added_handler_id = + g_signal_connect (window, "tab-added", + G_CALLBACK (on_window_tab_added), NULL); + + wdata->tab_removed_handler_id = + g_signal_connect (window, "tab-removed", + G_CALLBACK (on_window_tab_removed), NULL); + + g_object_set_data_full (G_OBJECT (window), WINDOW_DATA_KEY, + wdata, (GDestroyNotify) window_data_free); +} + +static void +gedit_modeline_plugin_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *wdata; + GList *views; + GList *l; + + gedit_debug (DEBUG_PLUGINS); + + wdata = g_object_steal_data (G_OBJECT (window), WINDOW_DATA_KEY); + + g_signal_handler_disconnect (window, wdata->tab_added_handler_id); + g_signal_handler_disconnect (window, wdata->tab_removed_handler_id); + + window_data_free (wdata); + + views = gedit_window_get_views (window); + + for (l = views; l != NULL; l = l->next) + { + disconnect_handlers (GEDIT_VIEW (l->data)); + + modeline_parser_deactivate (GTK_SOURCE_VIEW (l->data)); + } + + g_list_free (views); +} + diff --git a/plugins/modelines/gedit-modeline-plugin.h b/plugins/modelines/gedit-modeline-plugin.h new file mode 100755 index 00000000..92b01e70 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.h @@ -0,0 +1,48 @@ +/* + * gedit-modeline-plugin.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <[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, 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_MODELINE_PLUGIN_H__ +#define __GEDIT_MODELINE_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_MODELINE_PLUGIN (gedit_modeline_plugin_get_type ()) +#define GEDIT_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePlugin)) +#define GEDIT_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass)) +#define GEDIT_IS_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_MODELINE_PLUGIN)) +#define GEDIT_IS_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_MODELINE_PLUGIN)) +#define GEDIT_MODELINE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass)) + +/* Private structure type */ +typedef GeditPluginClass GeditModelinePluginClass; +typedef GeditPlugin GeditModelinePlugin; + +GType gedit_modeline_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_MODELINE_PLUGIN_H__ */ diff --git a/plugins/modelines/language-mappings b/plugins/modelines/language-mappings new file mode 100755 index 00000000..47d00296 --- /dev/null +++ b/plugins/modelines/language-mappings @@ -0,0 +1,14 @@ +[vim] +cs=c-sharp +docbk=docbook +javascript=js +lhaskell=haskell-literate +spec=rpmspec +tex=latex +xhtml=html + +[emacs] +c++=cpp + +[kate] + diff --git a/plugins/modelines/modeline-parser.c b/plugins/modelines/modeline-parser.c new file mode 100755 index 00000000..6feafc55 --- /dev/null +++ b/plugins/modelines/modeline-parser.c @@ -0,0 +1,852 @@ +/* + * modeline-parser.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <[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, 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 <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <gedit/gedit-language-manager.h> +#include <gedit/gedit-prefs-manager.h> +#include <gedit/gedit-debug.h> +#include "modeline-parser.h" + +#define MODELINES_LANGUAGE_MAPPINGS_FILE "language-mappings" + +/* base dir to lookup configuration files */ +static gchar *modelines_data_dir; + +/* Mappings: language name -> Gedit language ID */ +static GHashTable *vim_languages; +static GHashTable *emacs_languages; +static GHashTable *kate_languages; + +typedef enum +{ + MODELINE_SET_NONE = 0, + MODELINE_SET_TAB_WIDTH = 1 << 0, + MODELINE_SET_INDENT_WIDTH = 1 << 1, + MODELINE_SET_WRAP_MODE = 1 << 2, + MODELINE_SET_SHOW_RIGHT_MARGIN = 1 << 3, + MODELINE_SET_RIGHT_MARGIN_POSITION = 1 << 4, + MODELINE_SET_LANGUAGE = 1 << 5, + MODELINE_SET_INSERT_SPACES = 1 << 6 +} ModelineSet; + +typedef struct _ModelineOptions +{ + gchar *language_id; + + /* these options are similar to the GtkSourceView properties of the + * same names. + */ + gboolean insert_spaces; + guint tab_width; + guint indent_width; + GtkWrapMode wrap_mode; + gboolean display_right_margin; + guint right_margin_position; + + ModelineSet set; +} ModelineOptions; + +#define MODELINE_OPTIONS_DATA_KEY "ModelineOptionsDataKey" + +static gboolean +has_option (ModelineOptions *options, + ModelineSet set) +{ + return options->set & set; +} + +void +modeline_parser_init (const gchar *data_dir) +{ + modelines_data_dir = g_strdup (data_dir); +} + +void +modeline_parser_shutdown () +{ + if (vim_languages != NULL) + g_hash_table_destroy (vim_languages); + + if (emacs_languages != NULL) + g_hash_table_destroy (emacs_languages); + + if (kate_languages != NULL) + g_hash_table_destroy (kate_languages); + + vim_languages = NULL; + emacs_languages = NULL; + kate_languages = NULL; + + g_free (modelines_data_dir); +} + +static GHashTable * +load_language_mappings_group (GKeyFile *key_file, const gchar *group) +{ + GHashTable *table; + gchar **keys; + gsize length = 0; + int i; + + table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + keys = g_key_file_get_keys (key_file, group, &length, NULL); + + gedit_debug_message (DEBUG_PLUGINS, + "%" G_GSIZE_FORMAT " mappings in group %s", + length, group); + + for (i = 0; i < length; i++) + { + gchar *name = keys[i]; + gchar *id = g_key_file_get_string (key_file, group, name, NULL); + g_hash_table_insert (table, name, id); + } + g_free (keys); + + return table; +} + +/* lazy loading of language mappings */ +static void +load_language_mappings (void) +{ + gchar *fname; + GKeyFile *mappings; + GError *error = NULL; + + fname = g_build_filename (modelines_data_dir, + MODELINES_LANGUAGE_MAPPINGS_FILE, + NULL); + + mappings = g_key_file_new (); + + if (g_key_file_load_from_file (mappings, fname, 0, &error)) + { + gedit_debug_message (DEBUG_PLUGINS, + "Loaded language mappings from %s", + fname); + + vim_languages = load_language_mappings_group (mappings, "vim"); + emacs_languages = load_language_mappings_group (mappings, "emacs"); + kate_languages = load_language_mappings_group (mappings, "kate"); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, + "Failed to loaded language mappings from %s: %s", + fname, error->message); + + g_error_free (error); + } + + g_key_file_free (mappings); + g_free (fname); +} + +static gchar * +get_language_id (const gchar *language_name, GHashTable *mapping) +{ + gchar *name; + gchar *language_id; + + name = g_ascii_strdown (language_name, -1); + + language_id = g_hash_table_lookup (mapping, name); + + if (language_id != NULL) + { + g_free (name); + return g_strdup (language_id); + } + else + { + /* by default assume that the gtksourcevuew id is the same */ + return name; + } +} + +static gchar * +get_language_id_vim (const gchar *language_name) +{ + if (vim_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, vim_languages); +} + +static gchar * +get_language_id_emacs (const gchar *language_name) +{ + if (emacs_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, emacs_languages); +} + +static gchar * +get_language_id_kate (const gchar *language_name) +{ + if (kate_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, kate_languages); +} + +static gboolean +skip_whitespaces (gchar **s) +{ + while (**s != '\0' && g_ascii_isspace (**s)) + (*s)++; + return **s != '\0'; +} + +/* Parse vi(m) modelines. + * Vi(m) modelines looks like this: + * - first form: [text]{white}{vi:|vim:|ex:}[white]{options} + * - second form: [text]{white}{vi:|vim:|ex:}[white]se[t] {options}:[text] + * They can happen on the three first or last lines. + */ +static gchar * +parse_vim_modeline (gchar *s, + ModelineOptions *options) +{ + gboolean in_set = FALSE; + gboolean neg; + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0' && !(in_set && *s == ':')) + { + while (*s != '\0' && (*s == ':' || g_ascii_isspace (*s))) + s++; + + if (*s == '\0') + break; + + if (strncmp (s, "set ", 4) == 0 || + strncmp (s, "se ", 3) == 0) + { + s = strchr(s, ' ') + 1; + in_set = TRUE; + } + + neg = FALSE; + if (strncmp (s, "no", 2) == 0) + { + neg = TRUE; + s += 2; + } + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ':' && *s != '=' && + !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (*s == '=') + { + s++; + while (*s != '\0' && *s != ':' && + !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + } + + if (strcmp (key->str, "ft") == 0 || + strcmp (key->str, "filetype") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_vim (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "et") == 0 || + strcmp (key->str, "expandtab") == 0) + { + options->insert_spaces = !neg; + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "ts") == 0 || + strcmp (key->str, "tabstop") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "sw") == 0 || + strcmp (key->str, "shiftwidth") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->indent_width = intval; + options->set |= MODELINE_SET_INDENT_WIDTH; + } + } + else if (strcmp (key->str, "wrap") == 0) + { + options->wrap_mode = neg ? GTK_WRAP_NONE : GTK_WRAP_WORD; + + options->set |= MODELINE_SET_WRAP_MODE; + } + else if (strcmp (key->str, "textwidth") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->right_margin_position = intval; + options->display_right_margin = TRUE; + + options->set |= MODELINE_SET_SHOW_RIGHT_MARGIN | + MODELINE_SET_RIGHT_MARGIN_POSITION; + + } + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return s; +} + +/* Parse emacs modelines. + * Emacs modelines looks like this: "-*- key1: value1; key2: value2 -*-" + * They can happen on the first line, or on the second one if the first line is + * a shebang (#!) + * See http://www.delorie.com/gnu/docs/emacs/emacs_486.html + */ +static gchar * +parse_emacs_modeline (gchar *s, + ModelineOptions *options) +{ + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0') + { + while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s))) + s++; + if (*s == '\0' || strncmp (s, "-*-", 3) == 0) + break; + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ':' && *s != ';' && + !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (!skip_whitespaces (&s)) + break; + + if (*s != ':') + continue; + s++; + + if (!skip_whitespaces (&s)) + break; + + while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + + gedit_debug_message (DEBUG_PLUGINS, + "Emacs modeline bit: %s = %s", + key->str, value->str); + + /* "Mode" key is case insenstive */ + if (g_ascii_strcasecmp (key->str, "Mode") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_emacs (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "tab-width") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "indent-offset") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->indent_width = intval; + options->set |= MODELINE_SET_INDENT_WIDTH; + } + } + else if (strcmp (key->str, "indent-tabs-mode") == 0) + { + intval = strcmp (value->str, "nil") == 0; + options->insert_spaces = intval; + + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "autowrap") == 0) + { + intval = strcmp (value->str, "nil") != 0; + options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE; + + options->set |= MODELINE_SET_WRAP_MODE; + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return *s == '\0' ? s : s + 2; +} + +/* + * Parse kate modelines. + * Kate modelines are of the form "kate: key1 value1; key2 value2;" + * These can happen on the 10 first or 10 last lines of the buffer. + * See http://wiki.kate-editor.org/index.php/Modelines + */ +static gchar * +parse_kate_modeline (gchar *s, + ModelineOptions *options) +{ + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0') + { + while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s))) + s++; + if (*s == '\0') + break; + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (!skip_whitespaces (&s)) + break; + if (*s == ';') + continue; + + while (*s != '\0' && *s != ';' && + !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + + gedit_debug_message (DEBUG_PLUGINS, + "Kate modeline bit: %s = %s", + key->str, value->str); + + if (strcmp (key->str, "hl") == 0 || + strcmp (key->str, "syntax") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_kate (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "tab-width") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "indent-width") == 0) + { + intval = atoi (value->str); + if (intval) options->indent_width = intval; + } + else if (strcmp (key->str, "space-indent") == 0) + { + intval = strcmp (value->str, "on") == 0 || + strcmp (value->str, "true") == 0 || + strcmp (value->str, "1") == 0; + + options->insert_spaces = intval; + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "word-wrap") == 0) + { + intval = strcmp (value->str, "on") == 0 || + strcmp (value->str, "true") == 0 || + strcmp (value->str, "1") == 0; + + options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE; + + options->set |= MODELINE_SET_WRAP_MODE; + } + else if (strcmp (key->str, "word-wrap-column") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->right_margin_position = intval; + options->display_right_margin = TRUE; + + options->set |= MODELINE_SET_RIGHT_MARGIN_POSITION | + MODELINE_SET_SHOW_RIGHT_MARGIN; + } + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return s; +} + +/* Scan a line for vi(m)/emacs/kate modelines. + * Line numbers are counted starting at one. + */ +static void +parse_modeline (gchar *s, + gint line_number, + gint line_count, + ModelineOptions *options) +{ + gchar prev; + + /* look for the beginning of a modeline */ + for (prev = ' '; (s != NULL) && (*s != '\0'); prev = *(s++)) + { + if (!g_ascii_isspace (prev)) + continue; + + if ((line_number <= 3 || line_number > line_count - 3) && + (strncmp (s, "ex:", 3) == 0 || + strncmp (s, "vi:", 3) == 0 || + strncmp (s, "vim:", 4) == 0)) + { + gedit_debug_message (DEBUG_PLUGINS, "Vim modeline on line %d", line_number); + + while (*s != ':') s++; + s = parse_vim_modeline (s + 1, options); + } + else if (line_number <= 2 && strncmp (s, "-*-", 3) == 0) + { + gedit_debug_message (DEBUG_PLUGINS, "Emacs modeline on line %d", line_number); + + s = parse_emacs_modeline (s + 3, options); + } + else if ((line_number <= 10 || line_number > line_count - 10) && + strncmp (s, "kate:", 5) == 0) + { + gedit_debug_message (DEBUG_PLUGINS, "Kate modeline on line %d", line_number); + + s = parse_kate_modeline (s + 5, options); + } + } +} + +static gboolean +check_previous (GtkSourceView *view, + ModelineOptions *previous, + ModelineSet set) +{ + GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + + /* Do not restore default when this is the first time */ + if (!previous) + return FALSE; + + /* Do not restore default when previous was not set */ + if (!(previous->set & set)) + return FALSE; + + /* Only restore default when setting has not changed */ + switch (set) + { + case MODELINE_SET_INSERT_SPACES: + return gtk_source_view_get_insert_spaces_instead_of_tabs (view) == + previous->insert_spaces; + break; + case MODELINE_SET_TAB_WIDTH: + return gtk_source_view_get_tab_width (view) == previous->tab_width; + break; + case MODELINE_SET_INDENT_WIDTH: + return gtk_source_view_get_indent_width (view) == previous->indent_width; + break; + case MODELINE_SET_WRAP_MODE: + return gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view)) == + previous->wrap_mode; + break; + case MODELINE_SET_RIGHT_MARGIN_POSITION: + return gtk_source_view_get_right_margin_position (view) == + previous->right_margin_position; + break; + case MODELINE_SET_SHOW_RIGHT_MARGIN: + return gtk_source_view_get_show_right_margin (view) == + previous->display_right_margin; + break; + case MODELINE_SET_LANGUAGE: + { + GtkSourceLanguage *language = gtk_source_buffer_get_language (buffer); + + return (language == NULL && previous->language_id == NULL) || + (language != NULL && g_strcmp0 (gtk_source_language_get_id (language), + previous->language_id) == 0); + } + break; + default: + return FALSE; + break; + } +} + +static void +free_modeline_options (ModelineOptions *options) +{ + g_free (options->language_id); + g_slice_free (ModelineOptions, options); +} + +void +modeline_parser_apply_modeline (GtkSourceView *view) +{ + ModelineOptions options; + GtkTextBuffer *buffer; + GtkTextIter iter, liter; + gint line_count; + + options.language_id = NULL; + options.set = MODELINE_SET_NONE; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + gtk_text_buffer_get_start_iter (buffer, &iter); + + line_count = gtk_text_buffer_get_line_count (buffer); + + /* Parse the modelines on the 10 first lines... */ + while ((gtk_text_iter_get_line (&iter) < 10) && + !gtk_text_iter_is_end (&iter)) + { + gchar *line; + + liter = iter; + gtk_text_iter_forward_to_line_end (&iter); + line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE); + + parse_modeline (line, + 1 + gtk_text_iter_get_line (&iter), + line_count, + &options); + + gtk_text_iter_forward_line (&iter); + + g_free (line); + } + + /* ...and on the 10 last ones (modelines are not allowed in between) */ + if (!gtk_text_iter_is_end (&iter)) + { + gint cur_line; + guint remaining_lines; + + /* we are on the 11th line (count from 0) */ + cur_line = gtk_text_iter_get_line (&iter); + /* g_assert (10 == cur_line); */ + + remaining_lines = line_count - cur_line - 1; + + if (remaining_lines > 10) + { + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_iter_backward_lines (&iter, 9); + } + } + + while (!gtk_text_iter_is_end (&iter)) + { + gchar *line; + + liter = iter; + gtk_text_iter_forward_to_line_end (&iter); + line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE); + + parse_modeline (line, + 1 + gtk_text_iter_get_line (&iter), + line_count, + &options); + + gtk_text_iter_forward_line (&iter); + + g_free (line); + } + + /* Try to set language */ + if (has_option (&options, MODELINE_SET_LANGUAGE) && options.language_id) + { + GtkSourceLanguageManager *manager; + GtkSourceLanguage *language; + + manager = gedit_get_language_manager (); + language = gtk_source_language_manager_get_language + (manager, options.language_id); + + if (language != NULL) + { + gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), + language); + } + } + + ModelineOptions *previous = g_object_get_data (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY); + + /* Apply the options we got from modelines and restore defaults if + we set them before */ + if (has_option (&options, MODELINE_SET_INSERT_SPACES)) + { + gtk_source_view_set_insert_spaces_instead_of_tabs + (view, options.insert_spaces); + } + else if (check_previous (view, previous, MODELINE_SET_INSERT_SPACES)) + { + gtk_source_view_set_insert_spaces_instead_of_tabs + (view, + gedit_prefs_manager_get_insert_spaces ()); + } + + if (has_option (&options, MODELINE_SET_TAB_WIDTH)) + { + gtk_source_view_set_tab_width (view, options.tab_width); + } + else if (check_previous (view, previous, MODELINE_SET_TAB_WIDTH)) + { + gtk_source_view_set_tab_width (view, + gedit_prefs_manager_get_tabs_size ()); + } + + if (has_option (&options, MODELINE_SET_INDENT_WIDTH)) + { + gtk_source_view_set_indent_width (view, options.indent_width); + } + else if (check_previous (view, previous, MODELINE_SET_INDENT_WIDTH)) + { + gtk_source_view_set_indent_width (view, -1); + } + + if (has_option (&options, MODELINE_SET_WRAP_MODE)) + { + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), options.wrap_mode); + } + else if (check_previous (view, previous, MODELINE_SET_WRAP_MODE)) + { + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), + gedit_prefs_manager_get_wrap_mode ()); + } + + if (has_option (&options, MODELINE_SET_RIGHT_MARGIN_POSITION)) + { + gtk_source_view_set_right_margin_position (view, options.right_margin_position); + } + else if (check_previous (view, previous, MODELINE_SET_RIGHT_MARGIN_POSITION)) + { + gtk_source_view_set_right_margin_position (view, + gedit_prefs_manager_get_right_margin_position ()); + } + + if (has_option (&options, MODELINE_SET_SHOW_RIGHT_MARGIN)) + { + gtk_source_view_set_show_right_margin (view, options.display_right_margin); + } + else if (check_previous (view, previous, MODELINE_SET_SHOW_RIGHT_MARGIN)) + { + gtk_source_view_set_show_right_margin (view, + gedit_prefs_manager_get_display_right_margin ()); + } + + if (previous) + { + *previous = options; + previous->language_id = g_strdup (options.language_id); + } + else + { + previous = g_slice_new (ModelineOptions); + *previous = options; + previous->language_id = g_strdup (options.language_id); + + g_object_set_data_full (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY, + previous, + (GDestroyNotify)free_modeline_options); + } + + g_free (options.language_id); +} + +void +modeline_parser_deactivate (GtkSourceView *view) +{ + g_object_set_data (G_OBJECT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))), + MODELINE_OPTIONS_DATA_KEY, + NULL); +} + +/* vi:ts=8 */ diff --git a/plugins/modelines/modeline-parser.h b/plugins/modelines/modeline-parser.h new file mode 100755 index 00000000..2e8559e4 --- /dev/null +++ b/plugins/modelines/modeline-parser.h @@ -0,0 +1,37 @@ +/* + * modelie-parser.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <[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, 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 __MODELINE_PARSER_H__ +#define __MODELINE_PARSER_H__ + +#include <glib.h> +#include <gtksourceview/gtksourceview.h> + +G_BEGIN_DECLS + +void modeline_parser_init (const gchar *data_dir); +void modeline_parser_shutdown (void); +void modeline_parser_apply_modeline (GtkSourceView *view); +void modeline_parser_deactivate (GtkSourceView *view); + +G_END_DECLS + +#endif /* __MODELINE_PARSER_H__ */ diff --git a/plugins/modelines/modelines.gedit-plugin.desktop.in b/plugins/modelines/modelines.gedit-plugin.desktop.in new file mode 100755 index 00000000..c72f0199 --- /dev/null +++ b/plugins/modelines/modelines.gedit-plugin.desktop.in @@ -0,0 +1,8 @@ +[Gedit Plugin] +Module=modelines +IAge=2 +_Name=Modelines +_Description=Emacs, Kate and Vim-style modelines support for gedit. +Authors=Steve Frécinaux <[email protected]> +Copyright=Copyright © 2005 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/pythonconsole/Makefile.am b/plugins/pythonconsole/Makefile.am new file mode 100755 index 00000000..c27227f3 --- /dev/null +++ b/plugins/pythonconsole/Makefile.am @@ -0,0 +1,15 @@ +# Python Console Plugin +SUBDIRS = pythonconsole +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = pythonconsole.gedit-plugin.desktop.in +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in b/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in new file mode 100755 index 00000000..8cc65648 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in @@ -0,0 +1,10 @@ +[Gedit Plugin] +Loader=python +Module=pythonconsole +IAge=2 +_Name=Python Console +_Description=Interactive Python console standing in the bottom panel +Icon=mate-mime-text-x-python +Authors=Steve Frécinaux <[email protected]> +Copyright=Copyright © 2006 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/pythonconsole/pythonconsole/Makefile.am b/plugins/pythonconsole/pythonconsole/Makefile.am new file mode 100755 index 00000000..7aa91fe9 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/Makefile.am @@ -0,0 +1,17 @@ +# Python console plugin + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/pythonconsole +plugin_PYTHON = \ + __init__.py \ + console.py \ + config.py + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/pythonconsole/ui +ui_DATA = config.ui + +EXTRA_DIST = $(ui_DATA) + +CLEANFILES = +DISTCLEANFILES = + +-include $(top_srcdir)/git.mk diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py new file mode 100755 index 00000000..60f70e9f --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/__init__.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +# __init__.py -- plugin object +# +# Copyright (C) 2006 - 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, 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. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge <[email protected]> +# Copyright (C), 2005 Adam Hooper <[email protected]> +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import gtk +import gedit + +from console import PythonConsole +from config import PythonConsoleConfigDialog +from config import PythonConsoleConfig + +PYTHON_ICON = 'mate-mime-text-x-python' + +class PythonConsolePlugin(gedit.Plugin): + def __init__(self): + gedit.Plugin.__init__(self) + self.dlg = None + + def activate(self, window): + console = PythonConsole(namespace = {'__builtins__' : __builtins__, + 'gedit' : gedit, + 'window' : window}) + console.eval('print "You can access the main window through ' \ + '\'window\' :\\n%s" % window', False) + bottom = window.get_bottom_panel() + image = gtk.Image() + image.set_from_icon_name(PYTHON_ICON, gtk.ICON_SIZE_MENU) + bottom.add_item(console, _('Python Console'), image) + window.set_data('PythonConsolePluginInfo', console) + + def deactivate(self, window): + console = window.get_data("PythonConsolePluginInfo") + console.stop() + window.set_data("PythonConsolePluginInfo", None) + bottom = window.get_bottom_panel() + bottom.remove_item(console) + +def create_configure_dialog(self): + + if not self.dlg: + self.dlg = PythonConsoleConfigDialog(self.get_data_dir()) + + dialog = self.dlg.dialog() + window = gedit.app_get_default().get_active_window() + if window: + dialog.set_transient_for(window) + + return dialog + +# Here we dynamically insert create_configure_dialog based on if configuration +# is enabled. This has to be done like this because gedit checks if a plugin +# is configurable solely on the fact that it has this member defined or not +if PythonConsoleConfig.enabled(): + PythonConsolePlugin.create_configure_dialog = create_configure_dialog + +# ex:et:ts=4: diff --git a/plugins/pythonconsole/pythonconsole/config.py b/plugins/pythonconsole/pythonconsole/config.py new file mode 100755 index 00000000..fed4699b --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- + +# config.py -- Config dialog +# +# Copyright (C) 2008 - B. Clausius +# +# 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, 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. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge <[email protected]> +# Copyright (C), 2005 Adam Hooper <[email protected]> +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import os +import gtk + +__all__ = ('PythonConsoleConfig', 'PythonConsoleConfigDialog') + +MATECONF_KEY_BASE = '/apps/gedit-2/plugins/pythonconsole' +MATECONF_KEY_COMMAND_COLOR = MATECONF_KEY_BASE + '/command-color' +MATECONF_KEY_ERROR_COLOR = MATECONF_KEY_BASE + '/error-color' + +DEFAULT_COMMAND_COLOR = '#314e6c' # Blue Shadow +DEFAULT_ERROR_COLOR = '#990000' # Accent Red Dark + +class PythonConsoleConfig(object): + try: + import mateconf + except ImportError: + mateconf = None + + def __init__(self): + pass + + @staticmethod + def enabled(): + return PythonConsoleConfig.mateconf != None + + @staticmethod + def add_handler(handler): + if PythonConsoleConfig.mateconf: + PythonConsoleConfig.mateconf.client_get_default().notify_add(MATECONF_KEY_BASE, handler) + + color_command = property( + lambda self: self.mateconf_get_str(MATECONF_KEY_COMMAND_COLOR, DEFAULT_COMMAND_COLOR), + lambda self, value: self.mateconf_set_str(MATECONF_KEY_COMMAND_COLOR, value)) + + color_error = property( + lambda self: self.mateconf_get_str(MATECONF_KEY_ERROR_COLOR, DEFAULT_ERROR_COLOR), + lambda self, value: self.mateconf_set_str(MATECONF_KEY_ERROR_COLOR, value)) + + @staticmethod + def mateconf_get_str(key, default=''): + if not PythonConsoleConfig.mateconf: + return default + + val = PythonConsoleConfig.mateconf.client_get_default().get(key) + if val is not None and val.type == mateconf.VALUE_STRING: + return val.get_string() + else: + return default + + @staticmethod + def mateconf_set_str(key, value): + if not PythonConsoleConfig.mateconf: + return + + v = PythonConsoleConfig.mateconf.Value(mateconf.VALUE_STRING) + v.set_string(value) + PythonConsoleConfig.mateconf.client_get_default().set(key, v) + +class PythonConsoleConfigDialog(object): + + def __init__(self, datadir): + object.__init__(self) + self._dialog = None + self._ui_path = os.path.join(datadir, 'ui', 'config.ui') + self.config = PythonConsoleConfig() + + def dialog(self): + if self._dialog is None: + self._ui = gtk.Builder() + self._ui.add_from_file(self._ui_path) + + self.set_colorbutton_color(self._ui.get_object('colorbutton-command'), + self.config.color_command) + self.set_colorbutton_color(self._ui.get_object('colorbutton-error'), + self.config.color_error) + + self._ui.connect_signals(self) + + self._dialog = self._ui.get_object('dialog-config') + self._dialog.show_all() + else: + self._dialog.present() + + return self._dialog + + @staticmethod + def set_colorbutton_color(colorbutton, value): + try: + color = gtk.gdk.color_parse(value) + except ValueError: + pass # Default color in config.ui used + else: + colorbutton.set_color(color) + + def on_dialog_config_response(self, dialog, response_id): + self._dialog.destroy() + + def on_dialog_config_destroy(self, dialog): + self._dialog = None + self._ui = None + + def on_colorbutton_command_color_set(self, colorbutton): + self.config.color_command = colorbutton.get_color().to_string() + + def on_colorbutton_error_color_set(self, colorbutton): + self.config.color_error = colorbutton.get_color().to_string() + +# ex:et:ts=4: diff --git a/plugins/pythonconsole/pythonconsole/config.ui b/plugins/pythonconsole/pythonconsole/config.ui new file mode 100755 index 00000000..4ffe26f6 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.ui @@ -0,0 +1,107 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy toplevel-contextual --> + <object class="GtkDialog" id="dialog-config"> + <property name="window_position">center-on-parent</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">dialog</property> + <property name="has_separator">False</property> + <signal name="destroy" handler="on_dialog_config_destroy"/> + <signal name="response" handler="on_dialog_config_response"/> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <child> + <object class="GtkTable" id="table2"> + <property name="visible">True</property> + <property name="border_width">6</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="label-command"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">C_ommand color:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">colorbutton-command</property> + </object> + </child> + <child> + <object class="GtkLabel" id="label-error"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Error color:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">colorbutton-error</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="colorbutton-command"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="color">#31314e4e6c6c</property> + <signal name="color_set" handler="on_colorbutton_command_color_set"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="colorbutton-error"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="color">#999900000000</property> + <signal name="color_set" handler="on_colorbutton_error_color_set"/> + </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> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button1"> + <property name="label">gtk-close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="position">0</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="-7">button1</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/pythonconsole/pythonconsole/console.py b/plugins/pythonconsole/pythonconsole/console.py new file mode 100755 index 00000000..e9d7a331 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/console.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- + +# pythonconsole.py -- Console widget +# +# Copyright (C) 2006 - 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, 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. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge <[email protected]> +# Copyright (C), 2005 Adam Hooper <[email protected]> +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import string +import sys +import re +import traceback +import gobject +import gtk +import pango + +from config import PythonConsoleConfig + +__all__ = ('PythonConsole', 'OutFile') + +class PythonConsole(gtk.ScrolledWindow): + + __gsignals__ = { + 'grab-focus' : 'override', + } + + def __init__(self, namespace = {}): + gtk.ScrolledWindow.__init__(self) + + self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.set_shadow_type(gtk.SHADOW_IN) + self.view = gtk.TextView() + self.view.modify_font(pango.FontDescription('Monospace')) + self.view.set_editable(True) + self.view.set_wrap_mode(gtk.WRAP_WORD_CHAR) + self.add(self.view) + self.view.show() + + buffer = self.view.get_buffer() + self.normal = buffer.create_tag("normal") + self.error = buffer.create_tag("error") + self.command = buffer.create_tag("command") + + PythonConsoleConfig.add_handler(self.apply_preferences) + self.apply_preferences() + + self.__spaces_pattern = re.compile(r'^\s+') + self.namespace = namespace + + self.block_command = False + + # Init first line + buffer.create_mark("input-line", buffer.get_end_iter(), True) + buffer.insert(buffer.get_end_iter(), ">>> ") + buffer.create_mark("input", buffer.get_end_iter(), True) + + # Init history + self.history = [''] + self.history_pos = 0 + self.current_command = '' + self.namespace['__history__'] = self.history + + # Set up hooks for standard output. + self.stdout = OutFile(self, sys.stdout.fileno(), self.normal) + self.stderr = OutFile(self, sys.stderr.fileno(), self.error) + + # Signals + self.view.connect("key-press-event", self.__key_press_event_cb) + buffer.connect("mark-set", self.__mark_set_cb) + + def do_grab_focus(self): + self.view.grab_focus() + + def apply_preferences(self, *args): + config = PythonConsoleConfig() + self.error.set_property("foreground", config.color_error) + self.command.set_property("foreground", config.color_command) + + def stop(self): + self.namespace = None + + def __key_press_event_cb(self, view, event): + modifier_mask = gtk.accelerator_get_default_mod_mask() + event_state = event.state & modifier_mask + + if event.keyval == gtk.keysyms.d and event_state == gtk.gdk.CONTROL_MASK: + self.destroy() + + elif event.keyval == gtk.keysyms.Return and event_state == gtk.gdk.CONTROL_MASK: + # Get the command + buffer = view.get_buffer() + inp_mark = buffer.get_mark("input") + inp = buffer.get_iter_at_mark(inp_mark) + cur = buffer.get_end_iter() + line = buffer.get_text(inp, cur) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Prepare the new line + cur = buffer.get_end_iter() + buffer.insert(cur, "\n... ") + cur = buffer.get_end_iter() + buffer.move_mark(inp_mark, cur) + + # Keep indentation of precendent line + spaces = re.match(self.__spaces_pattern, line) + if spaces is not None: + buffer.insert(cur, line[spaces.start() : spaces.end()]) + cur = buffer.get_end_iter() + + buffer.place_cursor(cur) + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.Return: + # Get the marks + buffer = view.get_buffer() + lin_mark = buffer.get_mark("input-line") + inp_mark = buffer.get_mark("input") + + # Get the command line + inp = buffer.get_iter_at_mark(inp_mark) + cur = buffer.get_end_iter() + line = buffer.get_text(inp, cur) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Make the line blue + lin = buffer.get_iter_at_mark(lin_mark) + buffer.apply_tag(self.command, lin, cur) + buffer.insert(cur, "\n") + + cur_strip = self.current_command.rstrip() + + if cur_strip.endswith(":") \ + or (self.current_command[-2:] != "\n\n" and self.block_command): + # Unfinished block command + self.block_command = True + com_mark = "... " + elif cur_strip.endswith("\\"): + com_mark = "... " + else: + # Eval the command + self.__run(self.current_command) + self.current_command = '' + self.block_command = False + com_mark = ">>> " + + # Prepare the new line + cur = buffer.get_end_iter() + buffer.move_mark(lin_mark, cur) + buffer.insert(cur, com_mark) + cur = buffer.get_end_iter() + buffer.move_mark(inp_mark, cur) + buffer.place_cursor(cur) + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.KP_Down or event.keyval == gtk.keysyms.Down: + # Next entry from history + view.emit_stop_by_name("key_press_event") + self.history_down() + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.KP_Up or event.keyval == gtk.keysyms.Up: + # Previous entry from history + view.emit_stop_by_name("key_press_event") + self.history_up() + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.KP_Left or event.keyval == gtk.keysyms.Left or \ + event.keyval == gtk.keysyms.BackSpace: + buffer = view.get_buffer() + inp = buffer.get_iter_at_mark(buffer.get_mark("input")) + cur = buffer.get_iter_at_mark(buffer.get_insert()) + if inp.compare(cur) == 0: + if not event_state: + buffer.place_cursor(inp) + return True + return False + + # For the console we enable smart/home end behavior incoditionally + # since it is useful when editing python + + elif (event.keyval == gtk.keysyms.KP_Home or event.keyval == gtk.keysyms.Home) and \ + event_state == event_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK): + # Go to the begin of the command instead of the begin of the line + buffer = view.get_buffer() + iter = buffer.get_iter_at_mark(buffer.get_mark("input")) + ins = buffer.get_iter_at_mark(buffer.get_insert()) + + while iter.get_char().isspace(): + iter.forward_char() + + if iter.equal(ins): + iter = buffer.get_iter_at_mark(buffer.get_mark("input")) + + if event_state & gtk.gdk.SHIFT_MASK: + buffer.move_mark_by_name("insert", iter) + else: + buffer.place_cursor(iter) + return True + + elif (event.keyval == gtk.keysyms.KP_End or event.keyval == gtk.keysyms.End) and \ + event_state == event_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK): + + buffer = view.get_buffer() + iter = buffer.get_end_iter() + ins = buffer.get_iter_at_mark(buffer.get_insert()) + + iter.backward_char() + + while iter.get_char().isspace(): + iter.backward_char() + + iter.forward_char() + + if iter.equal(ins): + iter = buffer.get_end_iter() + + if event_state & gtk.gdk.SHIFT_MASK: + buffer.move_mark_by_name("insert", iter) + else: + buffer.place_cursor(iter) + return True + + def __mark_set_cb(self, buffer, iter, name): + input = buffer.get_iter_at_mark(buffer.get_mark("input")) + pos = buffer.get_iter_at_mark(buffer.get_insert()) + self.view.set_editable(pos.compare(input) != -1) + + def get_command_line(self): + buffer = self.view.get_buffer() + inp = buffer.get_iter_at_mark(buffer.get_mark("input")) + cur = buffer.get_end_iter() + return buffer.get_text(inp, cur) + + def set_command_line(self, command): + buffer = self.view.get_buffer() + mark = buffer.get_mark("input") + inp = buffer.get_iter_at_mark(mark) + cur = buffer.get_end_iter() + buffer.delete(inp, cur) + buffer.insert(inp, command) + self.view.grab_focus() + + def history_add(self, line): + if line.strip() != '': + self.history_pos = len(self.history) + self.history[self.history_pos - 1] = line + self.history.append('') + + def history_up(self): + if self.history_pos > 0: + self.history[self.history_pos] = self.get_command_line() + self.history_pos = self.history_pos - 1 + self.set_command_line(self.history[self.history_pos]) + + def history_down(self): + if self.history_pos < len(self.history) - 1: + self.history[self.history_pos] = self.get_command_line() + self.history_pos = self.history_pos + 1 + self.set_command_line(self.history[self.history_pos]) + + def scroll_to_end(self): + iter = self.view.get_buffer().get_end_iter() + self.view.scroll_to_iter(iter, 0.0) + return False + + def write(self, text, tag = None): + buffer = self.view.get_buffer() + if tag is None: + buffer.insert(buffer.get_end_iter(), text) + else: + buffer.insert_with_tags(buffer.get_end_iter(), text, tag) + gobject.idle_add(self.scroll_to_end) + + def eval(self, command, display_command = False): + buffer = self.view.get_buffer() + lin = buffer.get_mark("input-line") + buffer.delete(buffer.get_iter_at_mark(lin), + buffer.get_end_iter()) + + if isinstance(command, list) or isinstance(command, tuple): + for c in command: + if display_command: + self.write(">>> " + c + "\n", self.command) + self.__run(c) + else: + if display_command: + self.write(">>> " + c + "\n", self.command) + self.__run(command) + + cur = buffer.get_end_iter() + buffer.move_mark_by_name("input-line", cur) + buffer.insert(cur, ">>> ") + cur = buffer.get_end_iter() + buffer.move_mark_by_name("input", cur) + self.view.scroll_to_iter(buffer.get_end_iter(), 0.0) + + def __run(self, command): + sys.stdout, self.stdout = self.stdout, sys.stdout + sys.stderr, self.stderr = self.stderr, sys.stderr + + # eval and exec are broken in how they deal with utf8-encoded + # strings so we have to explicitly decode the command before + # passing it along + command = command.decode('utf8') + + try: + try: + r = eval(command, self.namespace, self.namespace) + if r is not None: + print `r` + except SyntaxError: + exec command in self.namespace + except: + if hasattr(sys, 'last_type') and sys.last_type == SystemExit: + self.destroy() + else: + traceback.print_exc() + + sys.stdout, self.stdout = self.stdout, sys.stdout + sys.stderr, self.stderr = self.stderr, sys.stderr + + def destroy(self): + pass + #gtk.ScrolledWindow.destroy(self) + +class OutFile: + """A fake output file object. It sends output to a TK test widget, + and if asked for a file number, returns one set on instance creation""" + def __init__(self, console, fn, tag): + self.fn = fn + self.console = console + self.tag = tag + def close(self): pass + def flush(self): pass + def fileno(self): return self.fn + def isatty(self): return 0 + def read(self, a): return '' + def readline(self): return '' + def readlines(self): return [] + def write(self, s): self.console.write(s, self.tag) + def writelines(self, l): self.console.write(l, self.tag) + def seek(self, a): raise IOError, (29, 'Illegal seek') + def tell(self): raise IOError, (29, 'Illegal seek') + truncate = tell + +# ex:et:ts=4: diff --git a/plugins/quickopen/Makefile.am b/plugins/quickopen/Makefile.am new file mode 100755 index 00000000..4b5faf00 --- /dev/null +++ b/plugins/quickopen/Makefile.am @@ -0,0 +1,15 @@ +# Quick Open Plugin +SUBDIRS = quickopen +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = quickopen.gedit-plugin.desktop.in +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/quickopen/quickopen.gedit-plugin.desktop.in b/plugins/quickopen/quickopen.gedit-plugin.desktop.in new file mode 100755 index 00000000..40f7f040 --- /dev/null +++ b/plugins/quickopen/quickopen.gedit-plugin.desktop.in @@ -0,0 +1,10 @@ +[Gedit Plugin] +Loader=python +Module=quickopen +IAge=2 +_Name=Quick Open +_Description=Quickly open files +Icon=gtk-open +Authors=Jesse van den Kieboom <[email protected]> +Copyright=Copyright © 2009 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/quickopen/quickopen/Makefile.am b/plugins/quickopen/quickopen/Makefile.am new file mode 100755 index 00000000..88882fdf --- /dev/null +++ b/plugins/quickopen/quickopen/Makefile.am @@ -0,0 +1,13 @@ +# Quick Open Plugin + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/quickopen +plugin_PYTHON = \ + __init__.py \ + popup.py \ + virtualdirs.py \ + windowhelper.py + +CLEANFILES = +DISTCLEANFILES = + +-include $(top_srcdir)/git.mk diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py new file mode 100755 index 00000000..a41c9400 --- /dev/null +++ b/plugins/quickopen/quickopen/__init__.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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. + +import gedit +from windowhelper import WindowHelper + +class QuickOpenPlugin(gedit.Plugin): + def __init__(self): + gedit.Plugin.__init__(self) + + self._popup_size = (450, 300) + self._helpers = {} + + def activate(self, window): + self._helpers[window] = WindowHelper(window, self) + + def deactivate(self, window): + self._helpers[window].deactivate() + del self._helpers[window] + + def update_ui(self, window): + self._helpers[window].update_ui() + + def get_popup_size(self): + return self._popup_size + + def set_popup_size(self, size): + self._popup_size = size + +# ex:ts=8:et: diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py new file mode 100755 index 00000000..a80caf31 --- /dev/null +++ b/plugins/quickopen/quickopen/popup.py @@ -0,0 +1,534 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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. + +import gtk +import gtk.gdk +import gobject +import os +import gio +import pango +import glib +import fnmatch +import gedit +import xml.sax.saxutils +from virtualdirs import VirtualDirectory + +class Popup(gtk.Dialog): + def __init__(self, window, paths, handler): + gtk.Dialog.__init__(self, + title=_('Quick Open'), + parent=window, + flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR | gtk.DIALOG_MODAL) + + self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + self._open_button = self.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT) + + self._handler = handler + self._build_ui() + + self._dirs = [] + self._cache = {} + self._theme = None + self._cursor = None + self._shift_start = None + + accel_group = gtk.AccelGroup() + accel_group.connect_group(gtk.keysyms.l, gtk.gdk.CONTROL_MASK, 0, self.on_focus_entry) + + self.add_accel_group(accel_group) + + unique = [] + + for path in paths: + if not path.get_uri() in unique: + self._dirs.append(path) + unique.append(path.get_uri()) + + def _build_ui(self): + vbox = self.get_content_area() + vbox.set_spacing(3) + + self._entry = gtk.Entry() + + self._entry.connect('changed', self.on_changed) + self._entry.connect('key-press-event', self.on_key_press_event) + + sw = gtk.ScrolledWindow(None, None) + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.set_shadow_type(gtk.SHADOW_OUT) + + tv = gtk.TreeView() + tv.set_headers_visible(False) + + self._store = gtk.ListStore(gio.Icon, str, object, int) + tv.set_model(self._store) + + self._treeview = tv + tv.connect('row-activated', self.on_row_activated) + + renderer = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn() + column.pack_start(renderer, False) + column.set_attributes(renderer, gicon=0) + + renderer = gtk.CellRendererText() + column.pack_start(renderer, True) + column.set_attributes(renderer, markup=1) + + column.set_cell_data_func(renderer, self.on_cell_data_cb) + + tv.append_column(column) + sw.add(tv) + + selection = tv.get_selection() + selection.connect('changed', self.on_selection_changed) + selection.set_mode(gtk.SELECTION_MULTIPLE) + + vbox.pack_start(self._entry, False, False, 0) + vbox.pack_start(sw, True, True, 0) + + lbl = gtk.Label() + lbl.set_alignment(0, 0.5) + lbl.set_ellipsize(pango.ELLIPSIZE_MIDDLE) + self._info_label = lbl + + vbox.pack_start(lbl, False, False, 0) + + # Initial selection + self.on_selection_changed(tv.get_selection()) + vbox.show_all() + + def on_cell_data_cb(self, column, cell, model, piter): + path = model.get_path(piter) + + if self._cursor and path == self._cursor.get_path(): + style = self._treeview.get_style() + bg = style.bg[gtk.STATE_PRELIGHT] + + cell.set_property('cell-background-gdk', bg) + cell.set_property('style', pango.STYLE_ITALIC) + else: + cell.set_property('cell-background-set', False) + cell.set_property('style-set', False) + + def _icon_from_stock(self, stock): + theme = gtk.icon_theme_get_default() + size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU) + pixbuf = theme.load_icon(stock, size[0], gtk.ICON_LOOKUP_USE_BUILTIN) + + return pixbuf + + def _list_dir(self, gfile): + entries = [] + + try: + entries = gfile.enumerate_children("standard::*") + except glib.GError: + pass + + children = [] + + for entry in entries: + if isinstance(gfile, VirtualDirectory): + child, entry = entry + else: + child = gfile.get_child(entry.get_name()) + + children.append((child, entry.get_name(), entry.get_file_type(), entry.get_icon())) + + return children + + def _compare_entries(self, a, b, lpart): + if lpart in a: + if lpart in b: + return cmp(a.index(lpart), b.index(lpart)) + else: + return -1 + elif lpart in b: + return 1 + else: + return 0 + + def _match_glob(self, s, glob): + if glob: + glob += '*' + + return fnmatch.fnmatch(s, glob) + + def do_search_dir(self, parts, d): + if not parts or not d: + return [] + + if not d in self._cache: + entries = self._list_dir(d) + entries.sort(lambda x, y: cmp(x[1].lower(), y[1].lower())) + + self._cache[d] = entries + else: + entries = self._cache[d] + + found = [] + newdirs = [] + + lpart = parts[0].lower() + + for entry in entries: + if not entry: + continue + + lentry = entry[1].lower() + + if not lpart or lpart in lentry or self._match_glob(lentry, lpart): + if entry[2] == gio.FILE_TYPE_DIRECTORY: + if len(parts) > 1: + newdirs.append(entry[0]) + else: + found.append(entry) + elif entry[2] == gio.FILE_TYPE_REGULAR and \ + (not lpart or len(parts) == 1): + found.append(entry) + + found.sort(lambda a, b: self._compare_entries(a[1].lower(), b[1].lower(), lpart)) + + if lpart == '..': + newdirs.append(d.get_parent()) + + for dd in newdirs: + found.extend(self.do_search_dir(parts[1:], dd)) + + return found + + def _replace_insensitive(self, s, find, rep): + out = '' + l = s.lower() + find = find.lower() + last = 0 + + if len(find) == 0: + return xml.sax.saxutils.escape(s) + + while True: + m = l.find(find, last) + + if m == -1: + break + else: + out += xml.sax.saxutils.escape(s[last:m]) + rep % (xml.sax.saxutils.escape(s[m:m + len(find)]),) + last = m + len(find) + + return out + xml.sax.saxutils.escape(s[last:]) + + + def make_markup(self, parts, path): + out = [] + + for i in range(0, len(parts)): + out.append(self._replace_insensitive(path[i], parts[i], "<b>%s</b>")) + + return os.sep.join(out) + + def _get_icon(self, f): + query = f.query_info(gio.FILE_ATTRIBUTE_STANDARD_ICON) + + if not query: + return None + else: + return query.get_icon() + + def _make_parts(self, parent, child, pp): + parts = [] + + # We went from parent, to child, using pp + idx = len(pp) - 1 + + while idx >= 0: + if pp[idx] == '..': + parts.insert(0, '..') + else: + parts.insert(0, child.get_basename()) + child = child.get_parent() + + idx -= 1 + + return parts + + def normalize_relative(self, parts): + if not parts: + return [] + + out = self.normalize_relative(parts[:-1]) + + if parts[-1] == '..': + if not out or (out[-1] == '..') or len(out) == 1: + out.append('..') + else: + del out[-1] + else: + out.append(parts[-1]) + + return out + + def _append_to_store(self, item): + if not item in self._stored_items: + self._store.append(item) + self._stored_items[item] = True + + def _clear_store(self): + self._store.clear() + self._stored_items = {} + + def _show_virtuals(self): + for d in self._dirs: + if isinstance(d, VirtualDirectory): + for entry in d.enumerate_children("standard::*"): + self._append_to_store((entry[1].get_icon(), xml.sax.saxutils.escape(entry[1].get_name()), entry[0], entry[1].get_file_type())) + + def _remove_cursor(self): + if self._cursor: + path = self._cursor.get_path() + self._cursor = None + + self._store.row_changed(path, self._store.get_iter(path)) + + def do_search(self): + self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self._remove_cursor() + + text = self._entry.get_text().strip() + self._clear_store() + + if text == '': + self._show_virtuals() + else: + parts = self.normalize_relative(text.split(os.sep)) + files = [] + + for d in self._dirs: + for entry in self.do_search_dir(parts, d): + pathparts = self._make_parts(d, entry[0], parts) + self._append_to_store((entry[3], self.make_markup(parts, pathparts), entry[0], entry[2])) + + piter = self._store.get_iter_first() + + if piter: + self._treeview.get_selection().select_path(self._store.get_path(piter)) + + self.window.set_cursor(None) + + def do_show(self): + gtk.Window.do_show(self) + + self._entry.grab_focus() + self._entry.set_text("") + + self.do_search() + + def on_changed(self, editable): + self.do_search() + self.on_selection_changed(self._treeview.get_selection()) + + def _shift_extend(self, towhere): + selection = self._treeview.get_selection() + + if not self._shift_start: + model, rows = selection.get_selected_rows() + start = rows[0] + + self._shift_start = gtk.TreeRowReference(self._store, start) + else: + start = self._shift_start.get_path() + + selection.unselect_all() + selection.select_range(start, towhere) + + def _select_index(self, idx, hasctrl, hasshift): + path = (idx,) + + if not (hasctrl or hasshift): + self._treeview.get_selection().unselect_all() + + if hasshift: + self._shift_extend(path) + else: + self._shift_start = None + + if not hasctrl: + self._treeview.get_selection().select_path(path) + + self._treeview.scroll_to_cell(path, None, True, 0.5, 0) + self._remove_cursor() + + if hasctrl or hasshift: + self._cursor = gtk.TreeRowReference(self._store, path) + + piter = self._store.get_iter(path) + self._store.row_changed(path, piter) + + def _move_selection(self, howmany, hasctrl, hasshift): + num = self._store.iter_n_children(None) + + if num == 0: + return True + + # Test for cursor + path = None + + if self._cursor: + path = self._cursor.get_path() + else: + model, rows = self._treeview.get_selection().get_selected_rows() + + if len(rows) == 1: + path = rows[0] + + if not path: + if howmany > 0: + self._select_index(0, hasctrl, hasshift) + else: + self._select_index(num - 1, hasctrl, hasshift) + else: + idx = path[0] + + if idx + howmany < 0: + self._select_index(0, hasctrl, hasshift) + elif idx + howmany >= num: + self._select_index(num - 1, hasctrl, hasshift) + else: + self._select_index(idx + howmany, hasctrl, hasshift) + + return True + + def _direct_file(self): + uri = self._entry.get_text() + gfile = None + + if gedit.utils.uri_is_valid(uri): + gfile = gio.File(uri) + elif os.path.isabs(uri): + f = gio.File(uri) + + if f.query_exists(): + gfile = f + + return gfile + + def _activate(self): + model, rows = self._treeview.get_selection().get_selected_rows() + ret = True + + for row in rows: + s = model.get_iter(row) + info = model.get(s, 2, 3) + + if info[1] != gio.FILE_TYPE_DIRECTORY: + ret = ret and self._handler(info[0]) + else: + text = self._entry.get_text() + + for i in range(len(text) - 1, -1, -1): + if text[i] == os.sep: + break + + self._entry.set_text(os.path.join(text[:i], os.path.basename(info[0].get_uri())) + os.sep) + self._entry.set_position(-1) + self._entry.grab_focus() + return True + + if rows and ret: + self.destroy() + + if not rows: + gfile = self._direct_file() + + if gfile and self._handler(gfile): + self.destroy() + else: + ret = False + else: + ret = False + + return ret + + def toggle_cursor(self): + if not self._cursor: + return + + path = self._cursor.get_path() + selection = self._treeview.get_selection() + + if selection.path_is_selected(path): + selection.unselect_path(path) + else: + selection.select_path(path) + + def on_key_press_event(self, widget, event): + move_mapping = { + gtk.keysyms.Down: 1, + gtk.keysyms.Up: -1, + gtk.keysyms.Page_Down: 5, + gtk.keysyms.Page_Up: -5 + } + + if event.keyval == gtk.keysyms.Escape: + self.destroy() + return True + elif event.keyval in move_mapping: + return self._move_selection(move_mapping[event.keyval], event.state & gtk.gdk.CONTROL_MASK, event.state & gtk.gdk.SHIFT_MASK) + elif event.keyval in [gtk.keysyms.Return, gtk.keysyms.KP_Enter, gtk.keysyms.Tab, gtk.keysyms.ISO_Left_Tab]: + return self._activate() + elif event.keyval == gtk.keysyms.space and event.state & gtk.gdk.CONTROL_MASK: + self.toggle_cursor() + + return False + + def on_row_activated(self, view, path, column): + self._activate() + + def do_response(self, response): + if response != gtk.RESPONSE_ACCEPT or not self._activate(): + self.destroy() + + def on_selection_changed(self, selection): + model, rows = selection.get_selected_rows() + + gfile = None + fname = None + + if not rows: + gfile = self._direct_file() + elif len(rows) == 1: + gfile = model.get(model.get_iter(rows[0]), 2)[0] + else: + fname = '' + + if gfile: + if gfile.is_native(): + fname = xml.sax.saxutils.escape(gfile.get_path()) + else: + fname = xml.sax.saxutils.escape(gfile.get_uri()) + + self._open_button.set_sensitive(fname != None) + self._info_label.set_markup(fname or '') + + def on_focus_entry(self, group, accel, keyval, modifier): + self._entry.grab_focus() + +gobject.type_register(Popup) + +# ex:ts=8:et: diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py new file mode 100755 index 00000000..ef0b8dc4 --- /dev/null +++ b/plugins/quickopen/quickopen/virtualdirs.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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. + +import gtk +import gio + +class VirtualDirectory: + def __init__(self, name): + self._name = name + self._children = [] + + def get_uri(self): + return 'virtual://' + self._name + + def get_parent(self): + return None + + def enumerate_children(self, attr): + return self._children + + def append(self, child): + if not child.is_native(): + return + + try: + info = child.query_info("standard::*") + + if info: + self._children.append((child, info)) + except: + pass + +class RecentDocumentsDirectory(VirtualDirectory): + def __init__(self, maxitems=10, screen=None): + VirtualDirectory.__init__(self, 'recent') + + self._maxitems = maxitems + self.fill(screen) + + def fill(self, screen): + if screen: + manager = gtk.recent_manager_get_for_screen(screen) + else: + manager = gtk.recent_manager_get_default() + + items = manager.get_items() + items.sort(lambda a, b: cmp(b.get_visited(), a.get_visited())) + + added = 0 + + for item in items: + if item.has_group('gedit'): + self.append(gio.File(item.get_uri())) + added += 1 + + if added >= self._maxitems: + break + +class CurrentDocumentsDirectory(VirtualDirectory): + def __init__(self, window): + VirtualDirectory.__init__(self, 'documents') + + self.fill(window) + + def fill(self, window): + for doc in window.get_documents(): + location = doc.get_location() + if location: + self.append(location) + +# ex:ts=8:et: diff --git a/plugins/quickopen/quickopen/windowhelper.py b/plugins/quickopen/quickopen/windowhelper.py new file mode 100755 index 00000000..70ea26f0 --- /dev/null +++ b/plugins/quickopen/quickopen/windowhelper.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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. + +import gedit +import gtk +from popup import Popup +import os +import gedit.commands +import gio +import glib +from virtualdirs import RecentDocumentsDirectory +from virtualdirs import CurrentDocumentsDirectory + +ui_str = """<ui> + <menubar name="MenuBar"> + <menu name="FileMenu" action="File"> + <placeholder name="FileOps_2"> + <menuitem name="QuickOpen" action="QuickOpen"/> + </placeholder> + </menu> + </menubar> +</ui> +""" + +class WindowHelper: + def __init__(self, window, plugin): + self._window = window + self._plugin = plugin + + self._popup = None + self._install_menu() + + def deactivate(self): + self._uninstall_menu() + self._window = None + self._plugin = None + + def update_ui(self): + pass + + def _uninstall_menu(self): + manager = self._window.get_ui_manager() + + manager.remove_ui(self._ui_id) + manager.remove_action_group(self._action_group) + + manager.ensure_update() + + def _install_menu(self): + manager = self._window.get_ui_manager() + self._action_group = gtk.ActionGroup("GeditQuickOpenPluginActions") + self._action_group.add_actions([ + ("QuickOpen", gtk.STOCK_OPEN, _("Quick open"), + '<Ctrl><Alt>O', _("Quickly open documents"), + self.on_quick_open_activate) + ]) + + manager.insert_action_group(self._action_group, -1) + self._ui_id = manager.add_ui_from_string(ui_str) + + def _create_popup(self): + paths = [] + + # Open documents + paths.append(CurrentDocumentsDirectory(self._window)) + + doc = self._window.get_active_document() + + # Current document directory + if doc and doc.is_local(): + gfile = doc.get_location() + paths.append(gfile.get_parent()) + + # File browser root directory + if gedit.version[0] > 2 or (gedit.version[0] == 2 and (gedit.version[1] > 26 or (gedit.version[1] == 26 and gedit.version[2] >= 2))): + bus = self._window.get_message_bus() + + try: + msg = bus.send_sync('/plugins/filebrowser', 'get_root') + + if msg: + uri = msg.get_value('uri') + + if uri: + gfile = gio.File(uri) + + if gfile.is_native(): + paths.append(gfile) + + except StandardError: + pass + + # Recent documents + paths.append(RecentDocumentsDirectory(screen=self._window.get_screen())) + + # Local bookmarks + for path in self._local_bookmarks(): + paths.append(path) + + # Desktop directory + desktopdir = self._desktop_dir() + + if desktopdir: + paths.append(gio.File(desktopdir)) + + # Home directory + paths.append(gio.File(os.path.expanduser('~'))) + + self._popup = Popup(self._window, paths, self.on_activated) + + self._popup.set_default_size(*self._plugin.get_popup_size()) + self._popup.set_transient_for(self._window) + self._popup.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + + self._window.get_group().add_window(self._popup) + + self._popup.connect('destroy', self.on_popup_destroy) + + def _local_bookmarks(self): + filename = os.path.expanduser('~/.gtk-bookmarks') + + if not os.path.isfile(filename): + return [] + + paths = [] + + for line in file(filename, 'r').xreadlines(): + uri = line.strip().split(" ")[0] + f = gio.File(uri) + + if f.is_native(): + try: + info = f.query_info("standard::type") + + if info and info.get_file_type() == gio.FILE_TYPE_DIRECTORY: + paths.append(f) + except glib.GError: + pass + + return paths + + def _desktop_dir(self): + config = os.getenv('XDG_CONFIG_HOME') + + if not config: + config = os.path.expanduser('~/.config') + + config = os.path.join(config, 'user-dirs.dirs') + desktopdir = None + + if os.path.isfile(config): + for line in file(config, 'r').xreadlines(): + line = line.strip() + + if line.startswith('XDG_DESKTOP_DIR'): + parts = line.split('=', 1) + desktopdir = os.path.expandvars(parts[1].strip('"').strip("'")) + break + + if not desktopdir: + desktopdir = os.path.expanduser('~/Desktop') + + return desktopdir + + # Callbacks + def on_quick_open_activate(self, action): + if not self._popup: + self._create_popup() + + self._popup.show() + + def on_popup_destroy(self, popup): + alloc = popup.get_allocation() + self._plugin.set_popup_size((alloc.width, alloc.height)) + + self._popup = None + + def on_activated(self, gfile): + gedit.commands.load_uri(self._window, gfile.get_uri(), None, -1) + return True + +# ex:ts=8:et: diff --git a/plugins/snippets/Makefile.am b/plugins/snippets/Makefile.am new file mode 100755 index 00000000..06f0009b --- /dev/null +++ b/plugins/snippets/Makefile.am @@ -0,0 +1,15 @@ +# Python snippets plugin +SUBDIRS = snippets data +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = snippets.gedit-plugin.desktop.in +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/snippets/data/Makefile.am b/plugins/snippets/data/Makefile.am new file mode 100755 index 00000000..8ec40419 --- /dev/null +++ b/plugins/snippets/data/Makefile.am @@ -0,0 +1,33 @@ +# Python snippets plugin +SUBDIRS = lang + +snippets_DATA = \ + css.xml \ + c.xml \ + cpp.xml \ + chdr.xml \ + docbook.xml \ + fortran.xml \ + global.xml \ + haskell.xml \ + html.xml \ + idl.xml \ + javascript.xml \ + java.xml \ + latex.xml \ + mallard.xml \ + perl.xml \ + php.xml \ + python.xml \ + ruby.xml \ + sh.xml \ + snippets.xml \ + tcl.xml \ + xml.xml \ + xslt.xml + +snippetsdir = $(GEDIT_PLUGINS_DATA_DIR)/snippets + +EXTRA_DIST = $(snippets_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/snippets/data/c.xml b/plugins/snippets/data/c.xml new file mode 100755 index 00000000..61171cb8 --- /dev/null +++ b/plugins/snippets/data/c.xml @@ -0,0 +1,283 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="C"> + <snippet id="gpl"> + <text><![CDATA[/* + * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]} + * This file is part of ${2:<program name>} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '<author\>' > + * + * ${2} 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. + * + * ${2} 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 ${2}; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +$0]]></text> + <tag>gpl</tag> + <description>GPL License</description> + </snippet> + <snippet id="lgpl"> + <text><![CDATA[/* + * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]} + * This file is part of ${2:<library name>} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '<author\>' > + * + * ${2} is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * ${2} 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +$0]]></text> + <tag>lgpl</tag> + <description>LGPL License</description> + </snippet> + <snippet id="do"> + <text><![CDATA[do +{ + $0 +} while ($1);]]></text> + <tag>do</tag> + <description>do .. while</description> + </snippet> + <snippet id="for"> + <text><![CDATA[for (${1:i} = ${2:0}; ${1:i} < ${3:count}; ${1:i} += ${4:1}) +{ + $0 +}]]></text> + <tag>for</tag> + <description>for loop</description> + </snippet> + <snippet id="while"> + <text><![CDATA[while (${1:condition}) +{ + $0 +}]]></text> + <tag>while</tag> + <description>while loop</description> + </snippet> + <snippet id="if"> + <text><![CDATA[if (${1:condition}) +{ + $0 +}]]></text> + <tag>if</tag> + <description>if</description> + </snippet> + <snippet id="elif"> + <text><![CDATA[else if (${1:condition}) +{ + $0 +}]]></text> + <tag>elif</tag> + <description>else if</description> + </snippet> + <snippet id="else"> + <text><![CDATA[else +{ + $0 +}]]></text> + <tag>else</tag> + <description>else</description> + </snippet> + <snippet id="Inc"> + <text><![CDATA[#include <${1:file}.h> +$0]]></text> + <tag>Inc</tag> + <description>#include <..></description> + </snippet> + <snippet id="inc"> + <text><![CDATA[#include "${1:file}.h" +$0]]></text> + <tag>inc</tag> + <description>#include ".."</description> + </snippet> + <snippet id="main"> + <text><![CDATA[int +main (int argc, char *argv[]) +{ + $0 + return 0; +}]]></text> + <tag>main</tag> + <description>main</description> + </snippet> + <snippet id="struct"> + <text><![CDATA[struct ${1:name} +{ + ${0:/* data */} +};]]></text> + <tag>struct</tag> + <description>struct</description> + </snippet> + <snippet id="endif"> + <text><![CDATA[#endif +$0]]></text> + <description>#endif</description> + <accelerator><![CDATA[<Control><Alt>period]]></accelerator> + </snippet> + <snippet id="td"> + <text><![CDATA[typedef ${1:newtype} ${2:type}; +$0]]></text> + <tag>td</tag> + <description>typedef</description> + </snippet> + <snippet id="gobject"> + <text><![CDATA[#include "$1.h" +$< +global camel_str,low_str, type_str, is_str, up_str +components = $1.split('-') +low_str = '_'.join(components).lower() +up_str = '_'.join(components).upper() +type_str = '_'.join([components[0], 'TYPE'] + components[1:]).upper() +is_str = '_'.join([components[0], 'IS'] + components[1:]).upper() +camel_str = '' + +for t in components: + camel_str += t.capitalize() +> + +#define $<[1]: return up_str >_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), $<[1]: return type_str >, $<[1]: return camel_str >Private)) + +struct _$<[1]: return camel_str >Private +{ +}; + +G_DEFINE_TYPE ($<[1]: return camel_str >, $<[1]: return low_str >, ${2:G_TYPE_OBJECT}) + +static void +$<[1]: return low_str>_finalize (GObject *object) +{ + G_OBJECT_CLASS ($<[1]: return low_str >_parent_class)->finalize (object); +} + +static void +$<[1]: return low_str >_class_init ($<[1]: return camel_str >Class *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = $<[1]: return low_str >_finalize; + + g_type_class_add_private (object_class, sizeof ($<[1]: return camel_str >Private)); +} + +static void +$<[1]: return low_str >_init ($<[1]: return camel_str> *self) +{ + self->priv = $<[1]: return up_str >_GET_PRIVATE (self); +} + +$<[1]: return camel_str > * +$<[1]: return low_str >_new () +{ + return g_object_new ($<[1]: return type_str >, NULL); +}]]></text> + <tag>gobject</tag> + <description>GObject template</description> + </snippet> + <snippet id="ginterface"> + <text><![CDATA[#include "$1.h" +$< +global camel_str,low_str,up_str +components = $1.split('-') +low_str = '_'.join(components).lower() +up_str = '_'.join(components).upper() +camel_str = '' + +for t in components: + camel_str += t.capitalize() +> +/* Default implementation */ +static const gchar * +$<[1]: return low_str>_example_method_default ($<[1]: return camel_str > *self) +{ + g_return_val_if_reached (NULL); +} + +static void +$<[1]: return low_str>_init ($<[1]: return camel_str >Iface *iface) +{ + static gboolean initialized = FALSE; + + iface->example_method = $<[1]: return low_str>_example_method_default; + + if (!initialized) + { + initialized = TRUE; + } +} + +/* + * This is an method example for an interface + */ +const gchar * +$<[1]: return low_str>_example_method ($<[1]: return camel_str > *self) +{ + g_return_val_if_fail ($<[1]: return up_str> (self), NULL); + return $<[1]: return up_str>_GET_INTERFACE (self)->example_method (self); +} + +GType +$<[1]: return low_str>_get_type () +{ + static GType $<[1]: return low_str>_type_id = 0; + + if (!$<[1]: return low_str>_type_id) + { + static const GTypeInfo g_define_type_info = + { + sizeof ($<[1]: return camel_str >Iface), + (GBaseInitFunc) $<[1]: return low_str>_init, + NULL, + NULL, + NULL, + NULL, + 0, + 0, + NULL + }; + + $<[1]: return low_str>_type_id = + g_type_register_static (G_TYPE_INTERFACE, + "$<[1]: return camel_str>", + &g_define_type_info, + 0); + } + + return $<[1]: return low_str>_type_id; +}]]></text> + <tag>ginterface</tag> + <description>GObject interface</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/chdr.xml b/plugins/snippets/data/chdr.xml new file mode 100755 index 00000000..f71ea901 --- /dev/null +++ b/plugins/snippets/data/chdr.xml @@ -0,0 +1,241 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="chdr"> + <snippet id="once"> + <text><![CDATA[#ifndef __${1:NAME}_H__ +#define __$1_H__ + +$0 + +#endif /* __$1_H__ */ +]]></text> + <description>Header Include-Guard</description> + <tag>once</tag> + </snippet> + <snippet id="inc"> + <text><![CDATA[#include "${1:file}" +$0]]></text> + <description>#include ".."</description> + <tag>inc</tag> + </snippet> + <snippet id="Inc"> + <text><![CDATA[#include <${1:file}> +$0]]></text> + <description>#include <..></description> + <tag>Inc</tag> + </snippet> + <snippet id="namespace"> + <text><![CDATA[namespace ${1:ns} +{ + $0 +}; +]]></text> + <description>namespace ..</description> + <tag>namespace</tag> + </snippet> + <snippet id="gpl"> + <text><![CDATA[/* + * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]} + * This file is part of ${2:<program name>} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '<author\>' > + * + * ${2} 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. + * + * ${2} 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 ${2}; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +$0]]></text> + <tag>gpl</tag> + <description>GPL License</description> + </snippet> + <snippet id="lgpl"> + <text><![CDATA[/* + * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]} + * This file is part of ${2:<library name>} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '<author\>' > + * + * ${2} is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * ${2} 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +$0]]></text> + <tag>lgpl</tag> + <description>LGPL License</description> + </snippet> + <snippet id="td"> + <text><![CDATA[typedef ${1:newtype} ${2:type}; +$0]]></text> + <tag>td</tag> + <description>typedef</description> + </snippet> + <snippet id="class"> + <text><![CDATA[class ${1:name} +{ + public: + ${1:name} (${2:arguments}); + virtual ~${1:name} (); + + private: + ${0:/* data */} +};]]></text> + <description>class ..</description> + <tag>class</tag> + </snippet> + <snippet id="struct"> + <text><![CDATA[struct ${1:name} +{ + ${0:/* data */} +};]]></text> + <tag>struct</tag> + <description>struct</description> + </snippet> + <snippet id="template"> + <text><![CDATA[template <typename ${1:_InputIter}>]]></text> + <description>template <typename ..></description> + <tag>template</tag> + </snippet> + <snippet id="gobject"> + <text><![CDATA[#ifndef __${1:NAME}_H__ +#define __$1_H__ + +#include <${2:glib-object.h}> + +G_BEGIN_DECLS + +$< +global camel_str +components = $1.split('_') +type_str = '_'.join([components[0], 'TYPE'] + components[1:]) +is_str = '_'.join([components[0], 'IS'] + components[1:]) +camel_str = '' + +for t in components: + camel_str += t.capitalize() + +items = [ \ +['#define ' + type_str, '(' + $1.lower() + '_get_type ())'], \ +['#define ' + $1 + '(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + '))'], \ +['#define ' + $1 + '_CONST(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + ' const))'], \ +['#define ' + $1 + '_CLASS(klass)', '(G_TYPE_CHECK_CLASS_CAST ((klass), ' + type_str + ', ' + camel_str + 'Class))'], \ +['#define ' + is_str + '(obj)', '(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ' + type_str + '))'], \ +['#define ' + is_str + '_CLASS(klass)', '(G_TYPE_CHECK_CLASS_TYPE ((klass), ' + type_str + '))'], \ +['#define ' + $1 + '_GET_CLASS(obj)', '(G_TYPE_INSTANCE_GET_CLASS ((obj), ' + type_str + ', ' + camel_str + 'Class))'] +] + +return align(items) > + +$<[1]: +items = [ \ +['typedef struct _' + camel_str, camel_str + ';'], \ +['typedef struct _' + camel_str + 'Class', camel_str + 'Class;'], \ +['typedef struct _' + camel_str + 'Private', camel_str + 'Private;'] \ +] + +return align(items) > + +struct _$<[1]: return camel_str > { + ${7:GObject} parent; + + $<[1]: return camel_str >Private *priv; +}; + +struct _$<[1]: return camel_str >Class { + $7Class parent_class; +}; + +GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST; +$<[1]: return camel_str > *$< return $1.lower()>_new (void); + +$0 +G_END_DECLS + +#endif /* __$1_H__ */]]></text> + <tag>gobject</tag> + <description>GObject template</description> + </snippet> + <snippet id="ginterface"> + <text><![CDATA[#ifndef __${1:NAME}_H__ +#define __$1_H__ + +#include <${2:glib-object.h}> + +G_BEGIN_DECLS + +$< +global camel_str +components = $1.split('_') +type_str = '_'.join([components[0], 'TYPE'] + components[1:]) +is_str = '_'.join([components[0], 'IS'] + components[1:]) +camel_str = '' + +for t in components: + camel_str += t.capitalize() + +items = [ \ +['#define ' + type_str, '(' + $1.lower() + '_get_type ())'], \ +['#define ' + $1 + '(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + '))'], \ +['#define ' + is_str + '(obj)', '(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ' + type_str + '))'], \ +['#define ' + $1 + '_GET_INTERFACE(obj)', '(G_TYPE_INSTANCE_GET_INTERFACE ((obj), ' + type_str + ', ' + camel_str + 'Iface))'] +] + +return align(items) > + +$<[1]: +items = [ \ +['typedef struct _' + camel_str, camel_str + ';'], \ +['typedef struct _' + camel_str + 'Iface', camel_str + 'Iface;'], \ +] + +return align(items) > + +struct _$<[1]: return camel_str >Iface +{ + ${7:GTypeInterface} parent; + + const gchar * (*example_method) ($<[1]: return camel_str > *self); +}; + +GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST; + +const gchar *$< return $1.lower()>_example_method ($<[1]: return camel_str > *self); +$0 +G_END_DECLS + +#endif /* __$1_H__ */]]></text> + <tag>ginterface</tag> + <description>GObject interface</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/cpp.xml b/plugins/snippets/data/cpp.xml new file mode 100755 index 00000000..7c7ccabd --- /dev/null +++ b/plugins/snippets/data/cpp.xml @@ -0,0 +1,183 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="cpp"> + <snippet id="main"> + <text><![CDATA[int main (int argc, char const* argv[]) +{ + $0 + return 0; +}]]></text> + <description>main</description> + <tag>main</tag> + </snippet> + <snippet id="for"> + <text><![CDATA[for (${1:unsigned int} ${2:i} = ${3:0}; ${2:i} < ${4:count}; ${2:i} += ${5:1}) +{ + $0 +}]]></text> + <description>for loop</description> + <tag>for</tag> + </snippet> + <snippet id="beginend"> + <text><![CDATA[${1:v}.begin(), ${1:v}.end()]]></text> + <description>$1.begin</description> + <tag>beginend</tag> + </snippet> + <snippet id="do"> + <text><![CDATA[do +{ + $0 +} while ($1 );]]></text> + <description>do .. while</description> + <tag>do</tag> + </snippet> + <snippet id="endif"> + <text><![CDATA[#endif +$0]]></text> + <accelerator><![CDATA[<Control><Alt>period]]></accelerator> + <description>#endif</description> + </snippet> + <snippet id="if"> + <text><![CDATA[if (${1:condition}) +{ + $0 +}]]></text> + <description>if ..</description> + <tag>if</tag> + </snippet> + <snippet id="inc"> + <text><![CDATA[#include "${1:file}" +$0]]></text> + <description>#include ".."</description> + <tag>inc</tag> + </snippet> + <snippet id="Inc"> + <text><![CDATA[#include <${1:file}> +$0]]></text> + <description>#include <..></description> + <tag>Inc</tag> + </snippet> + <snippet id="namespace"> + <text><![CDATA[namespace ${1:ns} +{ + $0 +}; +]]></text> + <description>namespace ..</description> + <tag>namespace</tag> + </snippet> + <snippet id="readfile"> + <text><![CDATA[std::vector<uint8_t> v; +if (FILE* fp = fopen (${1:"filename"}, "r")) +{ + uint8_t buf[1024]; + while (size_t len = fread (buf, 1, sizeof (buf), fp)) + v.insert (v.end(), buf, buf + len); + fclose(fp); +} +$0]]></text> + <description>Read File Into Vector</description> + <tag>readfile</tag> + </snippet> + <snippet id="map"> + <text><![CDATA[std::map<${1:key}, ${2:value}> ${3:map}; +$0]]></text> + <description>std::map</description> + <tag>map</tag> + </snippet> + <snippet id="vector"> + <text><![CDATA[std::vector<${1:char}> ${2:v}; +$0]]></text> + <description>std::vector</description> + <tag>vector</tag> + </snippet> + <snippet id="struct"> + <text><![CDATA[struct ${1:name} +{ + ${0:/* data */} +};]]></text> + <description>struct ..</description> + <tag>struct</tag> + </snippet> + <snippet id="template"> + <text><![CDATA[template <typename ${1:_InputIter}>]]></text> + <description>template <typename ..></description> + <tag>template</tag> + </snippet> + <snippet id="gpl"> + <text><![CDATA[/* + * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]} + * This file is part of ${2:<program name>} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '<author\>' > + * + * ${2} 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. + * + * ${2} 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 ${2}; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + $0]]></text> + <tag>gpl</tag> + <description>GPL License</description> + </snippet> + <snippet id="lgpl"> + <text><![CDATA[/* + * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]} + * This file is part of ${2:<library name>} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '<author\>' > + * + * ${2} is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * ${2} 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + $0]]></text> + <tag>lgpl</tag> + <description>LGPL License</description> + </snippet> + <snippet id="td"> + <text><![CDATA[typedef ${1:newtype} ${2:type}; +$0]]></text> + <tag>td</tag> + <description>typedef</description> + </snippet> + <snippet id="while"> + <text><![CDATA[while ($1) +{ + $0 +}]]></text> + <tag>while</tag> + <description>while</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/css.xml b/plugins/snippets/data/css.xml new file mode 100755 index 00000000..babca912 --- /dev/null +++ b/plugins/snippets/data/css.xml @@ -0,0 +1,557 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="CSS"> + <snippet id="background"> + <text><![CDATA[background-attachment: ${1:scroll/fixed}; +$0]]></text> + <description>background-attachment: scroll/fixed</description> + <tag>background</tag> + </snippet> + <snippet id="background-1"> + <text><![CDATA[background-color: #${1:DDD}; +$0]]></text> + <description>background-color: color-hex</description> + <tag>background</tag> + </snippet> + <snippet id="background-2"> + <text><![CDATA[background-color: ${1:red}; +$0]]></text> + <description>background-color: color-name</description> + <tag>background</tag> + </snippet> + <snippet id="background-3"> + <text><![CDATA[background-color: rgb(${1:255},${2:255},${3:255}); +$0]]></text> + <description>background-color: color-rgb</description> + <tag>background</tag> + </snippet> + <snippet id="background-4"> + <text><![CDATA[background: #${1:DDD} url($2) ${3:repeat/repeat-x/repeat-y/no-repeat} ${4:scroll/fixed} ${5:top letft/top center/top right/center left/center center/center right/bottom left/bottom center/bottom right/x-% y-%/x-pos y-pos}; +$0]]></text> + <description>background: color image repeat attachment position</description> + <tag>background</tag> + </snippet> + <snippet id="background-5"> + <text><![CDATA[background-color: transparent; +$0]]></text> + <description>background-color: transparent</description> + <tag>background</tag> + </snippet> + <snippet id="background-6"> + <text><![CDATA[background-image: none; +$0]]></text> + <description>background-image: none</description> + <tag>background</tag> + </snippet> + <snippet id="background-7"> + <text><![CDATA[background-image: url($1); +$0]]></text> + <description>background-image: url</description> + <tag>background</tag> + </snippet> + <snippet id="background-8"> + <text><![CDATA[background-position: ${1:top letft/top center/top right/center left/center center/center right/bottom left/bottom center/bottom right/x-% y-%/x-pos y-pos}; +$0]]></text> + <description>background-position: position</description> + <tag>background</tag> + </snippet> + <snippet id="background-9"> + <text><![CDATA[background-repeat: ${1:repeat/repeat-x/repeat-y/no-repeat}; +$0]]></text> + <description>background-repeat: r/r-x/r-y/n-r</description> + <tag>background</tag> + </snippet> + <snippet id="border"> + <text><![CDATA[border-bottom-color: #${1:999}; +$0]]></text> + <description>border-bottom-color: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-1"> + <text><![CDATA[border-bottom: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-bottom: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-2"> + <text><![CDATA[border-bottom-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset}; +$0]]></text> + <description>border-bottom-style: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-3"> + <text><![CDATA[border-bottom-width: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-bottom-width: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-4"> + <text><![CDATA[border-color: ${1:999}; +$0]]></text> + <description>border-color: color</description> + <tag>border</tag> + </snippet> + <snippet id="border-5"> + <text><![CDATA[border-right-color: #${1:999}; +$0]]></text> + <description>border-left-color: color</description> + <tag>border</tag> + </snippet> + <snippet id="border-6"> + <text><![CDATA[border-left: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-left: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-7"> + <text><![CDATA[border-left-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset}; +$0]]></text> + <description>border-left-style: style</description> + <tag>border</tag> + </snippet> + <snippet id="border-8"> + <text><![CDATA[border-left-width: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-left-width: size</description> + <tag>border</tag> + </snippet> + <snippet id="border-9"> + <text><![CDATA[border-right-color: #${1:999}; +$0]]></text> + <description>border-right-color: color</description> + <tag>border</tag> + </snippet> + <snippet id="border-10"> + <text><![CDATA[border-right: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-right: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-11"> + <text><![CDATA[border-right-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset}; +$0]]></text> + <description>border-right-style: style</description> + <tag>border</tag> + </snippet> + <snippet id="border-12"> + <text><![CDATA[border-right-width: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-right-width: size</description> + <tag>border</tag> + </snippet> + <snippet id="border-13"> + <text><![CDATA[border: ${1:1px} ${2:solid} #${3:999}; +$0]]></text> + <description>border: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-14"> + <text><![CDATA[border-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset}; +$0]]></text> + <description>border-style: style</description> + <tag>border</tag> + </snippet> + <snippet id="border-15"> + <text><![CDATA[border-top-color: #${1:999}; +$0]]></text> + <description>border-top-color: color</description> + <tag>border</tag> + </snippet> + <snippet id="border-16"> + <text><![CDATA[border-top: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-top: size style color</description> + <tag>border</tag> + </snippet> + <snippet id="border-17"> + <text><![CDATA[border-top-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset}; +$0]]></text> + <description>border-top-style: style</description> + <tag>border</tag> + </snippet> + <snippet id="border-18"> + <text><![CDATA[border-top-width: ${1:1}px ${2:solid} #${3:999}; +$0]]></text> + <description>border-top-width: size</description> + <tag>border</tag> + </snippet> + <snippet id="border-19"> + <text><![CDATA[border-color: ${1:1px}; +$0]]></text> + <description>border-width: width</description> + <tag>border</tag> + </snippet> + <snippet id="clear"> + <text><![CDATA[clear: ${1:left/right/both/none}; +$0]]></text> + <description>clear: value</description> + <tag>clear</tag> + </snippet> + <snippet id="color"> + <text><![CDATA[color: #${1:DDD}; +$0]]></text> + <description>color: color-hex</description> + <tag>color</tag> + </snippet> + <snippet id="color-1"> + <text><![CDATA[color: ${1:red}; +$0]]></text> + <description>color: color-name</description> + <tag>color</tag> + </snippet> + <snippet id="color-2"> + <text><![CDATA[color: rgb(${1:255},${2:255},${3:255}); +$0]]></text> + <description>color: color-rgb</description> + <tag>color</tag> + </snippet> + <snippet id="cursor"> + <text><![CDATA[cursor: {$1:default/auto/crosshair/pointer/move/*-resize/text/wait/help}; +$0]]></text> + <description>cursor: type</description> + <tag>cursor</tag> + </snippet> + <snippet id="clear-1"> + <text><![CDATA[cursor: url($1); +$0]]></text> + <description>cursor: url</description> + <tag>clear</tag> + </snippet> + <snippet id="direction"> + <text><![CDATA[direction: ${1:ltr|rtl}; +$0]]></text> + <description>direction: ltr|rtl</description> + <tag>direction</tag> + </snippet> + <snippet id="display"> + <text><![CDATA[display: block; +$0]]></text> + <description>display: block</description> + <tag>display</tag> + </snippet> + <snippet id="display-1"> + <text><![CDATA[display: ${1:none/inline/block/list-item/run-in/compact/marker}; +$0]]></text> + <description>display: common-types</description> + <tag>display</tag> + </snippet> + <snippet id="display-2"> + <text><![CDATA[display: inline; +$0]]></text> + <description>display: inline</description> + <tag>display</tag> + </snippet> + <snippet id="display-3"> + <text><![CDATA[display: ${1:table/inline-table/table-row-group/table-header-group/table-footer-group/table-row/table-column-group/table-column/table-cell/table-caption}; +$0]]></text> + <description>display: table-types</description> + <tag>display</tag> + </snippet> + <snippet id="float"> + <text><![CDATA[float: ${1:left/right/none}; +$0]]></text> + <description>float: left/right/none</description> + <tag>float</tag> + </snippet> + <snippet id="font"> + <text><![CDATA[font-family: ${1:Arial, "MS Trebuchet"}, ${2:sans-}serif; +$0]]></text> + <description>font-family: family</description> + <tag>font</tag> + </snippet> + <snippet id="font-1"> + <text><![CDATA[font: ${1:75%} ${2:"Lucida Grande", "Trebuchet MS", Verdana,} ${3:sans-}serif; +$0]]></text> + <description>font: size font</description> + <tag>font</tag> + </snippet> + <snippet id="font-2"> + <text><![CDATA[font-size: ${1:100%}; +$0]]></text> + <description>font-size: size</description> + <tag>font</tag> + </snippet> + <snippet id="font-3"> + <text><![CDATA[font-style: ${1:normal/italic/oblique}; +$0]]></text> + <description>font-style: normal/italic/oblique</description> + <tag>font</tag> + </snippet> + <snippet id="font-4"> + <text><![CDATA[font: ${1:normal/italic/oblique} ${2:normal/small-caps} ${3:normal/bold} ${4:1em/1.5em} ${5:Arial}, ${6:sans-}serif; +$0]]></text> + <description>font: style variant weight size/line-height font-family</description> + <tag>font</tag> + </snippet> + <snippet id="font-5"> + <text><![CDATA[font-variant: ${1:normal/small-caps}; +$0]]></text> + <description>font-variant: normal/small-caps</description> + <tag>font</tag> + </snippet> + <snippet id="font-6"> + <text><![CDATA[font-weight: ${1:normal/bold}; +$0]]></text> + <description>font-weight: weight</description> + <tag>font</tag> + </snippet> + <snippet id="letter"> + <text><![CDATA[letter-spacing: $1em; +$0]]></text> + <description>letter-spacing: length-em</description> + <tag>letter</tag> + </snippet> + <snippet id="letter-1"> + <text><![CDATA[letter-spacing: $1px; +$0]]></text> + <description>letter-spacing: length-px</description> + <tag>letter</tag> + </snippet> + <snippet id="list"> + <text><![CDATA[list-style-image: url($1); +$0]]></text> + <description>list-style-image: url</description> + <tag>list</tag> + </snippet> + <snippet id="list-1"> + <text><![CDATA[list-style-position: ${1:inside/outside}; +$0]]></text> + <description>list-style-position: pos</description> + <tag>list</tag> + </snippet> + <snippet id="list-2"> + <text><![CDATA[list-style-type: ${1:cjk-ideographic/hiragana/katakana/hiragana-iroha/katakana-iroha}; +$0]]></text> + <description>list-style-type: asian</description> + <tag>list</tag> + </snippet> + <snippet id="list-3"> + <text><![CDATA[list-style-type: ${1:none/disc/circle/square}; +$0]]></text> + <description>list-style-type: marker</description> + <tag>list</tag> + </snippet> + <snippet id="list-4"> + <text><![CDATA[list-style-type: ${1:decimal/decimal-leading-zero/zero}; +$0]]></text> + <description>list-style-type: numeric</description> + <tag>list</tag> + </snippet> + <snippet id="list-5"> + <text><![CDATA[list-style-type: ${1:hebrew/armenian/georgian}; +$0]]></text> + <description>list-style-type: other</description> + <tag>list</tag> + </snippet> + <snippet id="list-6"> + <text><![CDATA[list-style: ${1:none/disc/circle/square/decimal/zero} ${2:inside/outside} url($3); +$0]]></text> + <description>list-style: type position image</description> + <tag>list</tag> + </snippet> + <snippet id="list-7"> + <text><![CDATA[list-style-type: ${1:lower-roman/uppert-roman/lower-alpha/upper-alpha/lower-greek/lower-latin/upper-latin}; +$0]]></text> + <description>list-style-type: roman-alpha-greek</description> + <tag>list</tag> + </snippet> + <snippet id="margin"> + <text><![CDATA[margin: ${1:20px}; +$0]]></text> + <description>margin: all</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-1"> + <text><![CDATA[margin-bottom: ${1:20px}; +$0]]></text> + <description>margin-bottom: length</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-2"> + <text><![CDATA[margin-left: ${1:20px}; +$0]]></text> + <description>margin-left: length</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-3"> + <text><![CDATA[margin-right: ${1:20px}; +$0]]></text> + <description>margin-right: length</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-4"> + <text><![CDATA[margin-top: ${1:20px}; +$0]]></text> + <description>margin-top: length</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-5"> + <text><![CDATA[margin: ${1:20px} ${2:0px} ${3:40px} ${4:0px}; +$0]]></text> + <description>margin: T R B L</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-6"> + <text><![CDATA[margin: ${1:20px} ${2:0px}; +$0]]></text> + <description>margin: V H</description> + <tag>margin</tag> + </snippet> + <snippet id="marker"> + <text><![CDATA[marker-offset: auto; +$0]]></text> + <description>marker-offset: auto</description> + <tag>marker</tag> + </snippet> + <snippet id="marker-1"> + <text><![CDATA[marker-offset: ${1:10px}; +$0]]></text> + <description>marker-offset: length</description> + <tag>marker</tag> + </snippet> + <snippet id="overflow"> + <text><![CDATA[overflow: ${1:visible/hidden/scroll/auto}; +$0]]></text> + <description>overflow: type</description> + <tag>overflow</tag> + </snippet> + <snippet id="padding"> + <text><![CDATA[padding: ${1:20px}; +$0]]></text> + <description>padding: all</description> + <tag>padding</tag> + </snippet> + <snippet id="margin-7"> + <text><![CDATA[padding-bottom: ${1:20px}; +$0]]></text> + <description>padding-bottom: length</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-8"> + <text><![CDATA[padding-left: ${1:20px}; +$0]]></text> + <description>padding-left: length</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-9"> + <text><![CDATA[padding-right: ${1:20px}; +$0]]></text> + <description>padding-right: length</description> + <tag>margin</tag> + </snippet> + <snippet id="margin-10"> + <text><![CDATA[padding-top: ${1:20px}; +$0]]></text> + <description>padding-top: length</description> + <tag>margin</tag> + </snippet> + <snippet id="padding-1"> + <text><![CDATA[padding: ${1:20px} ${2:0px} ${3:40px} ${4:0px}; +$0]]></text> + <description>padding: T R B L</description> + <tag>padding</tag> + </snippet> + <snippet id="padding-2"> + <text><![CDATA[padding: ${1:20px} ${2:0px}; +$0]]></text> + <description>padding: V H</description> + <tag>padding</tag> + </snippet> + <snippet id="position"> + <text><![CDATA[position: ${1:static/relative/absolute/fixed}; +$0]]></text> + <description>position: type</description> + <tag>position</tag> + </snippet> + <snippet id="{"> + <text><![CDATA[{ + /* $1 */ + $0 +]]></text> + <description>properties { }</description> + <tag>{</tag> + </snippet> + <snippet id="text"> + <text><![CDATA[text-align: ${1:left/right/center/justify}; +$0]]></text> + <description>text-align: left/center/right</description> + <tag>text</tag> + </snippet> + <snippet id="text-1"> + <text><![CDATA[text-decoration: ${1:none/underline/overline/line-through/blink}; +$0]]></text> + <description>text-decoration: none/underline/overline/line-through/blink</description> + <tag>text</tag> + </snippet> + <snippet id="text-2"> + <text><![CDATA[text-indent: ${1:10p}x; +$0]]></text> + <description>text-indent: length</description> + <tag>text</tag> + </snippet> + <snippet id="text-3"> + <text><![CDATA[text-shadow: #${1:DDD} ${2:10px} ${3:10px} ${4:2px}; +$0]]></text> + <description>text-shadow: color-hex x y blur</description> + <tag>text</tag> + </snippet> + <snippet id="text-4"> + <text><![CDATA[text-shadow: rgb(${1:255},${2:255},${3:255}) ${4:10px} ${5:10px} ${6:2px}; +$0]]></text> + <description>text-shadow: color-rgb x y blur</description> + <tag>text</tag> + </snippet> + <snippet id="text-5"> + <text><![CDATA[text-shadow: none; +$0]]></text> + <description>text-shadow: none</description> + <tag>text</tag> + </snippet> + <snippet id="text-6"> + <text><![CDATA[text-transform: ${1:capitalize/uppercase/lowercase}; +$0]]></text> + <description>text-transform: capitalize/upper/lower</description> + <tag>text</tag> + </snippet> + <snippet id="text-7"> + <text><![CDATA[text-transform: none; +$0]]></text> + <description>text-transform: none</description> + <tag>text</tag> + </snippet> + <snippet id="vertical"> + <text><![CDATA[vertical-align: ${1:baseline/sub/super/top/text-top/middle/bottom/text-bottom/length/%}; +$0]]></text> + <description>vertical-align: type</description> + <tag>vertical</tag> + </snippet> + <snippet id="visibility"> + <text><![CDATA[visibility: ${1:visible/hidden/collapse}; +$0]]></text> + <description>visibility: type</description> + <tag>visibility</tag> + </snippet> + <snippet id="white"> + <text><![CDATA[white-space: ${1:normal/pre/nowrap}; +$0]]></text> + <description>white-space: normal/pre/nowrap</description> + <tag>white</tag> + </snippet> + <snippet id="word"> + <text><![CDATA[word-spacing: ${1:10px}; +$0]]></text> + <description>word-spacing: length</description> + <tag>word</tag> + </snippet> + <snippet id="word-1"> + <text><![CDATA[word-spacing: normal; +$0]]></text> + <description>word-spacing: normal</description> + <tag>word</tag> + </snippet> + <snippet id="z"> + <text><![CDATA[z-index: $1; +$0]]></text> + <description>z-index: index</description> + <tag>z</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/docbook.xml b/plugins/snippets/data/docbook.xml new file mode 100755 index 00000000..3159b603 --- /dev/null +++ b/plugins/snippets/data/docbook.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="docbook"> + <!-- useful snippets from xml set --> + <snippet id="<"> + <text><![CDATA[<${1}>$0</${1}>]]></text> + <description>XML tag</description> + <tag><</tag> + </snippet> + <snippet id="menuchoice"> + <text><![CDATA[<menuchoice><guimenu>$1</guimenu><guimenuitem>$2</guimenuitem></menuchoice> +]]></text> + <tag>menu</tag> + <description>menuchoice</description> + <accelerator /> + </snippet> + <snippet id="keycombo"> + <text><![CDATA[<keycombo><keycap>${1:Ctrl}</keycap><keycap>${2}</keycap></keycombo>]]></text> + <tag>key</tag> + <description>keycombo</description> + <accelerator/> + </snippet> + <snippet id="sect"> + <text><![CDATA[<sect${1} id="${2}"> + <title>${3}</title> +</sect${1}>]]></text> + <tag>sect</tag> + <description>sect*</description> + <accelerator/> + </snippet> + <snippet id="app"> + <text><![CDATA[<application>&app;</application>]]></text> + <tag>app</tag> + <description>app entity</description> + <accelerator/> + </snippet> + <snippet id="appwrap"> + <text><![CDATA[<application>$GEDIT_SELECTED_TEXT</application>]]></text> + <tag>application</tag> + <description>application tag</description> + <accelerator/> + </snippet> + <snippet id="enclose"> + <text><![CDATA[<${1}>$GEDIT_SELECTED_TEXT</${1}>]]></text> + <tag>enclose</tag> + <description>enclose selected text</description> + <accelerator/> + </snippet> + <snippet id="itemizedlist"> + <text><![CDATA[<itemizedlist> + <listitem> + <para>${1}</para> + </listitem> +</itemizedlist>]]></text> + <tag>ul</tag> + <description>itemized list</description> + <accelerator/> + </snippet> + <snippet id="orderedlist"> + <text><![CDATA[<orderedlist> + <listitem> + <para>${1}</para> + </listitem> +</orderedlist>]]></text> + <tag>ol</tag> + <description>ordered list</description> + <accelerator/> + </snippet> + <snippet id="listitem"> + <text><![CDATA[<listitem> + <para>${1}</para> +</listitem>]]></text> + <tag>li</tag> + <description>list item</description> + <accelerator/> + </snippet> + <snippet id="variablelist"> + <text><![CDATA[<variablelist> + $1 +</variablelist>]]></text> + <tag>vl</tag> + <description>variablelist</description> + <accelerator/> + </snippet> + <snippet id="varlistentry"> + <text><![CDATA[<varlistentry><term>${1}</term> + <listitem> + <para>${2}</para> + </listitem> +</varlistentry>]]></text> + <tag>vli</tag> + <description>variablelist entry</description> + <accelerator/> + </snippet> + <snippet id="closepara"> + <text><![CDATA[</para>]]></text> + <tag>/</tag> + <description>para close</description> + <accelerator/> + </snippet> + <snippet id="openpara"> + <text><![CDATA[<para>]]></text> + <tag>p</tag> + <description>para open</description> + <accelerator/> + </snippet> + <snippet id="http"> + <text><![CDATA[<ulink type="http" url="$1">$2</ulink>]]></text> + <tag>http</tag> + <description>ulink http</description> + <accelerator/> + </snippet> + <snippet id="yelp"> + <text><![CDATA[<ulink type="help" url="$1">$2</ulink>]]></text> + <tag>help</tag> + <description>ulink mate help</description> + <accelerator/> + </snippet> +</snippets> diff --git a/plugins/snippets/data/fortran.xml b/plugins/snippets/data/fortran.xml new file mode 100755 index 00000000..c64d6461 --- /dev/null +++ b/plugins/snippets/data/fortran.xml @@ -0,0 +1,164 @@ +<?xml version='1.0' encoding='utf-8'?> +<snippets language="fortran"> + <snippet id="c"> + <text><![CDATA[character(len=${1:10}) :: $0]]></text> + <tag>c</tag> + <description>character</description> + </snippet> + <snippet id="cl"> + <text><![CDATA[close(${1:unit}, status='${2:keep}')]]></text> + <tag>cl</tag> + <description>close</description> + </snippet> + <snippet id="do"> + <text><![CDATA[do ${1:i}=$2, $3, ${4:1} + ${0:source} +end do]]></text> + <tag>do</tag> + <description>do ... end do</description> + </snippet> + <snippet id="func"> + <text><![CDATA[function ${1:name}( ${2:parameter} ) + ${3:integer/real ::} $1 + ${4:integer/real ::} $2 + + ${0:source} + + $1 = !result +end function]]></text> + <tag>func</tag> + <description>function</description> + </snippet> + <snippet id="ifel"> + <text><![CDATA[if( $1 ) then + ${2:source} +else + ${0:source} +end if]]></text> + <tag>ifel</tag> + <description>if ... else ... end if</description> + </snippet> + <snippet id="if"> + <text><![CDATA[if( $1 ) then + ${0:source} +end if]]></text> + <tag>if</tag> + <description>if ... end if</description> + </snippet> + <snippet id="i"> + <text><![CDATA[integer(kind=${1:4}) :: $0]]></text> + <tag>i</tag> + <description>integer</description> + </snippet> + <snippet id="ida"> + <text><![CDATA[integer(kind=${1:4}), dimension(${2::}), allocatable :: $0]]></text> + <tag>ida</tag> + <description>integerdimalloc</description> + </snippet> + <snippet id="id"> + <text><![CDATA[integer(kind=${1:4}), dimension(${2::}) :: $0]]></text> + <tag>id</tag> + <description>integerdim</description> + </snippet> + <snippet id="l"> + <text><![CDATA[logical(kind=${1:1}) :: $0]]></text> + <tag>l</tag> + <description>logical</description> + </snippet> + <snippet id="mod"> + <text><![CDATA[module ${1:name} + implicit none + ${2:integer/real ::} $3 + + ${4:contains} + + ${0:source} +end module]]></text> + <tag>mod</tag> + <description>module</description> + </snippet> + <snippet id="op"> + <text><![CDATA[open(${1:unit}, file='${2:name}', status='${3:new}')]]></text> + <tag>op</tag> + <description>open</description> + </snippet> + <snippet id="prog"> + <text><![CDATA[program ${1:name} + implicit none + + ${0:source} +end program]]></text> + <tag>prog</tag> + <description>program</description> + </snippet> + <snippet id="re"> + <text><![CDATA[read(unit=${1:*},fmt=${2:*}) $0]]></text> + <tag>re</tag> + <description>read</description> + </snippet> + <snippet id="r"> + <text><![CDATA[real(kind=${1:8}) :: $0]]></text> + <tag>r</tag> + <description>real</description> + </snippet> + <snippet id="rda"> + <text><![CDATA[real(kind=${1:8}), dimension(${2::}), allocatable :: $0]]></text> + <tag>rda</tag> + <description>realdimalloc</description> + </snippet> + <snippet id="rd"> + <text><![CDATA[real(kind=${1:8}), dimension(${2::}) :: $0]]></text> + <tag>rd</tag> + <description>realdim</description> + </snippet> + <snippet id="rec"> + <text><![CDATA[recursive function ${1:name}( ${2:parameter} ) result( ${3:res} ) + ${4:integer/real ::} $3 + ${5:integer/real ::} $2 + + ${0:source} + + $3 = !result +end function]]></text> + <tag>rec</tag> + <description>recursivfunc</description> + </snippet> + <snippet id="sel"> + <text><![CDATA[select case( $1 ) + case( $2 ) + ${3:source} + case default + ${0:source} +end select]]></text> + <tag>sel</tag> + <description>select</description> + </snippet> + <snippet id="sub"> + <text><![CDATA[subroutine ${1:name}( ${2:parameter} ) + ${3:integer/real ::} $2 + + ${0:source} +end subroutine]]></text> + <tag>sub</tag> + <description>subroutine</description> + </snippet> + <snippet id="t"> + <text><![CDATA[type :: ${1:name} + ${2:integer/real ::} $0 +end type $1]]></text> + <tag>t</tag> + <description>type</description> + </snippet> + <snippet id="dow"> + <text><![CDATA[do while( ${1} ) + ${0:source} +end do]]></text> + <tag>dow</tag> + <description>while</description> + </snippet> + <snippet id="wr"> + <text><![CDATA[write(unit=${1:*},fmt=${2:*}) "$3", $0]]></text> + <tag>wr</tag> + <description>write</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/global.xml b/plugins/snippets/data/global.xml new file mode 100755 index 00000000..afe3c0b7 --- /dev/null +++ b/plugins/snippets/data/global.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets/> diff --git a/plugins/snippets/data/haskell.xml b/plugins/snippets/data/haskell.xml new file mode 100755 index 00000000..54a8e7d6 --- /dev/null +++ b/plugins/snippets/data/haskell.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="Haskell"> + <snippet id="mod"> + <text><![CDATA[module ${1:Main} where + $0]]></text> + <description>module</description> + <tag>mod</tag> + </snippet> + <snippet id="\"> + <text><![CDATA[\\${1:t} -> ${1:t}]]></text> + <description>\t -> t</description> + <tag>\</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/html.xml b/plugins/snippets/data/html.xml new file mode 100755 index 00000000..d294f934 --- /dev/null +++ b/plugins/snippets/data/html.xml @@ -0,0 +1,246 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="HTML"> + <snippet id="doctype"> + <text><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +]]></text> + <description>HTML — 4.01 Strict</description> + <tag>doctype</tag> + </snippet> + <snippet id="doctype-1"> + <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"> +]]></text> + <description>XHTML — 1.0 Frameset</description> + <tag>doctype</tag> + </snippet> + <snippet id="doctype-2"> + <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +]]></text> + <description>XHTML — 1.0 Strict</description> + <tag>doctype</tag> + </snippet> + <snippet id="doctype-3"> + <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +]]></text> + <description>XHTML — 1.0 Transitional</description> + <tag>doctype</tag> + </snippet> + <snippet id="doctype-4"> + <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +]]></text> + <description>XHTML — 1.1</description> + <tag>doctype</tag> + </snippet> + <snippet id="doctype-5"> + <text><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +]]></text> + <description>HTML — 4.0 Transitional</description> + <tag>doctype</tag> + </snippet> + <snippet id="author"> + <text><![CDATA[<meta name="author" content="${1:author}" /> +$0]]></text> + <tag>author</tag> + <description>Author</description> + </snippet> + <snippet id="date"> + <text><![CDATA[<meta name="date" content="$<1: import time; return time.strftime("%Y-%m-%d") >" /> +$0]]></text> + <tag>date</tag> + <description>Date</description> + </snippet> + <snippet id="ref"> + <text><![CDATA[<a href="${1:http://somesite.com/}">${2:$GEDIT_SELECTED_TEXT}</a> +]]></text> + <accelerator><![CDATA[<Shift><Alt>l]]></accelerator> + <description>Wrap Selection as Link</description> + <tag>ref</tag> + </snippet> + <snippet id="open/close"> + <text><![CDATA[<${1:p}>$GEDIT_SELECTED_TEXT</${1}>]]></text> + <accelerator><![CDATA[<Shift><Alt>w]]></accelerator> + <description>Wrap Selection in Open/Close Tag</description> + </snippet> + <snippet id="mailto"> + <text><![CDATA[<a href="mailto:${1:[email protected]}?subject=${2:feedback}">${3:email me}</a> $0]]></text> + <description>Mail Anchor</description> + <tag>mailto</tag> + </snippet> + <snippet id="base"> + <text><![CDATA[<base href="$1" ${2}/>$0]]></text> + <description>Base</description> + <tag>base</tag> + </snippet> + <snippet id="body"> + <text><![CDATA[<body id="${1:ID} " onload="$2"}> + $0 +</body>]]></text> + <description>Body</description> + <tag>body</tag> + </snippet> + <snippet id="br"> + <text><![CDATA[<br /> +$0]]></text> + <accelerator><![CDATA[<Shift><Control>space]]></accelerator> + <description>Br</description> + </snippet> + <snippet id="button"> + <text><![CDATA[<button type="button" name="${1:name}" value="${2:caption}" onclick="$3" />$4 +]]></text> + <tag>button</tag> + <description>Button</description> + </snippet> + <snippet id="div"> + <text><![CDATA[<div ${1}> + ${0:$GEDIT_SELECTED_TEXT} +</div>]]></text> + <description>Div</description> + <tag>div</tag> + </snippet> + <snippet id="file"> + <text><![CDATA[<input type="file" name="${1:name}" size="$2" accept="$3" />$0 +]]></text> + <tag>file</tag> + <description>File</description> + </snippet> + <snippet id="form"> + <text><![CDATA[<form action="${1}" method="${2:get}"> + $0 + + <p><input type="submit" value="${3:Continue →}" /></p> +</form>]]></text> + <description>Form</description> + <tag>form</tag> + </snippet> + <snippet id="h"> + <text><![CDATA[<h${1:1} id="${2}">${3:$GEDIT_SELECTED_TEXT}</h${1}> +$0]]></text> + <description>Heading</description> + <tag>h</tag> + </snippet> + <snippet id="head"> + <text><![CDATA[<head> + <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> + <title>${1:Page Title}</title> + $0 +</head>]]></text> + <description>Head</description> + <tag>head</tag> + </snippet> + <snippet id="image"> + <text><![CDATA[<img src="${1:path/to/file}" alt="${2:description}" title="${3:description}" width="$4" height="$5" />$0]]></text> + <tag>img</tag> + <description>Image</description> + </snippet> + <snippet id="input"> + <text><![CDATA[<input type="${1:[button,checkbox,file,hidden,image,password,radio,reset,submit,text]}" name="${2:some_name}" value="$3" id="${5}" />]]></text> + <description>Input</description> + <tag>input</tag> + </snippet> + <snippet id="li"> + <text><![CDATA[<li>$1</li>$0]]></text> + <tag>li</tag> + <description>List Element</description> + </snippet> + <snippet id="link"> + <text><![CDATA[<link rel="${1:stylesheet}" href="${2:/css/master.css}" type="text/css" media="${3:screen}" title="${4:no title}" charset="${5:utf-8}" />]]></text> + <description>Link</description> + <tag>link</tag> + </snippet> + <snippet id="meta"> + <text><![CDATA[<meta name="${1:name}" content="${2:content}" />]]></text> + <description>Meta</description> + <tag>meta</tag> + </snippet> + <snippet id="nbsp"> + <text><![CDATA[ ]]></text> + <accelerator><![CDATA[<Control><Alt>space]]></accelerator> + <description>Non-Breaking Space</description> + </snippet> + <snippet id="noscript"> + <text><![CDATA[<noscript>$1</noscript>$0]]></text> + <tag>noscript</tag> + <description>Noscript</description> + </snippet> + <snippet id="option"> + <text><![CDATA[<option value="${1:value}">$2</option>$0]]></text> + <tag>option</tag> + <description>Option</description> + </snippet> + <snippet id="script"> + <text><![CDATA[<script type="text/javascript" language="javascript" charset="utf-8"> +// <![CDATA[ + $0 +// ]]]]><![CDATA[> +</script>]]></text> + <description>Script</description> + <tag>script</tag> + </snippet> + <snippet id="scriptsrc"> + <text><![CDATA[<script src="$1" type="text/javascript" language="${2:javascript}" charset="${3:utf-8}" />]]></text> + <description>Script With External Source</description> + <tag>scriptsrc</tag> + </snippet> + <snippet id="select"> + <text><![CDATA[<select name="${1:name}"> + <option value="${2:value}">$3</option> + $4 +</select>$0 +]]></text> + <tag>select</tag> + <description>Select</description> + </snippet> + <snippet id="span"> + <text><![CDATA[<span ${1}>$2</span>$0]]></text> + <tag>span</tag> + <description>Span</description> + </snippet> + <snippet id="style"> + <text><![CDATA[<style type="text/css" media="screen"> +/* <![CDATA[ */ + $0 +/* ]]]]><![CDATA[> */ +</style> +]]></text> + <description>Style</description> + <tag>style</tag> + </snippet> + <snippet id="table"> + <text><![CDATA[<table border="${1:0}" cellspacing="${2:0}" cellpadding="${3:0}"> + <tr><th>${4:Header}</th></tr> + <tr><td>${5:Data}</td></tr> + $0 +</table>]]></text> + <description>Table</description> + <tag>table</tag> + </snippet> + <snippet id="textarea"> + <text><![CDATA[<textarea name="${1:Name}" rows="${2:8}" cols="${3:40}">$0</textarea>]]></text> + <description>Text Area</description> + <tag>textarea</tag> + </snippet> + <snippet id="title"> + <text><![CDATA[<title>${1:Page Title}</title>]]></text> + <description>Title</description> + <tag>title</tag> + </snippet> + <snippet id="tr"> + <text><![CDATA[<tr><td>$1</td></tr> +$0]]></text> + <tag>tr</tag> + <description>Table Row</description> + </snippet> + <snippet id="ul"> + <text><![CDATA[<ul> + <li>$1</li> + $2 +</ul> +$0]]></text> + <tag>ul</tag> + <description>Unordered List</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/idl.xml b/plugins/snippets/data/idl.xml new file mode 100755 index 00000000..2b6ef30d --- /dev/null +++ b/plugins/snippets/data/idl.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="IDL"> + <snippet id="mod"> + <text><![CDATA[module ${1:name} +{ + $0 +}; +]]></text> + <tag>mod</tag> + <description>Module</description> + </snippet> + <snippet id="if"> + <text><![CDATA[interface ${1:name} +{ + $0 +}; +]]></text> + <tag>if</tag> + <description>Interface</description> + </snippet> + <snippet id="str"> + <text><![CDATA[struct ${1:name} +{ + $0 +}; +]]></text> + <tag>str</tag> + <description>Struct</description> + </snippet> + <snippet id="exc"> + <text><![CDATA[exception ${1:name} +{ + $0 +}; +]]></text> + <tag>exc</tag> + <description>Exception</description> + </snippet> + <snippet id="seq"> + <text><![CDATA[sequence<${1:type}> ]]></text> + <tag>seq</tag> + <description>Sequence</description> + </snippet> + <snippet id="tseq"> + <text><![CDATA[typedef sequence<${1:type}> ${0:newtype};]]></text> + <tag>tseq</tag> + <description>Typedef Sequence</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/java.xml b/plugins/snippets/data/java.xml new file mode 100755 index 00000000..043a5dd3 --- /dev/null +++ b/plugins/snippets/data/java.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="Java"> + <snippet id="cd"> + <text><![CDATA[private static final ${1:String} ${2:var} = "$0";]]></text> + <description>const def</description> + <tag>cd</tag> + </snippet> + <snippet id="ife"> + <text><![CDATA[if ($1) { // $2 + + $0 + +} else { // $3 + + + +} + +]]></text> + <description>if .. else</description> + <tag>ife</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[if ($1) { // $2 + $0 +}]]></text> + <description>if</description> + <tag>if</tag> + </snippet> + <snippet id="log"> + <text><![CDATA[/** Logger for this class and subclasses. */ +protected final Log log = LogFactory.getLog(getClass()); +]]></text> + <description>logger</description> + <tag>log</tag> + </snippet> + <snippet id="tcf"> + <text><![CDATA[try { + $2 +} catch (${1:Exception} e) { + $3 +} finally { + $4 +} +$0]]></text> + <description>try .. catch .. finally</description> + <tag>tcf</tag> + </snippet> + <snippet id="while"> + <text><![CDATA[while ($1) { // $2 + $0 +}]]></text> + <description>while statement</description> + <tag>while</tag> + </snippet> + <snippet id="main"> + <text><![CDATA[public static void main(String[] args) { + ${1:System.exit(0)}; +}]]></text> + <description>main</description> + <tag>main</tag> + </snippet> + <snippet id="sout"> + <text><![CDATA[System.out.println("${1}"); +$0 +]]></text> + <description>System.out.println</description> + <tag>sout</tag> + </snippet> + <snippet id="try/catch"> + <text><![CDATA[try { + $GEDIT_SELECTED_TEXT +} +catch (Exception e) { + ${1:e.printStackTrace();} +} +$0]]></text> + <accelerator><![CDATA[<Shift><Alt>t]]></accelerator> + <description>Wrap Selection in Try/Catch</description> + </snippet> + <snippet id="tc"> + <text><![CDATA[try { + $2 +} catch (${1:Exception} e) { + $3 +} +$0]]></text> + <tag>tc</tag> + <description>try .. catch</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/javascript.xml b/plugins/snippets/data/javascript.xml new file mode 100755 index 00000000..c1d498a6 --- /dev/null +++ b/plugins/snippets/data/javascript.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="JavaScript"> + <snippet id="fun"> + <text><![CDATA[function ${1:function_name} (${2:first_argument}) +{ + ${0:# body...} +}]]></text> + <description>function</description> + <tag>fun</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/lang/Makefile.am b/plugins/snippets/data/lang/Makefile.am new file mode 100755 index 00000000..8a497c12 --- /dev/null +++ b/plugins/snippets/data/lang/Makefile.am @@ -0,0 +1,9 @@ +# Python snippets plugin +lang_DATA = \ + snippets.lang + +langdir = $(GEDIT_PLUGINS_DATA_DIR)/snippets/lang + +EXTRA_DIST = $(lang_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/snippets/data/lang/snippets.lang b/plugins/snippets/data/lang/snippets.lang new file mode 100755 index 00000000..81e57394 --- /dev/null +++ b/plugins/snippets/data/lang/snippets.lang @@ -0,0 +1,162 @@ +<?xml version="1.0"?> +<!-- + + Author: Jesse van den Kieboom <[email protected]> + Copyright (C) 2007-2008 Jesse van den Kieboom <[email protected]> + + 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. + +--> +<language id="snippets" name="Snippets" hidden="true" version="2.0"> + <styles> + <style id="placeholder-bounds" name="Placeholder begin and end" map-to="def:function"/> + <style id="default-value" name="Default Value" map-to="def:string"/> + <style id="single-placeholder" name="Single Placeholder" map-to="def:decimal"/> + <style id="shell-placeholder" name="Shell Placeholder" map-to="def:preprocessor"/> + <style id="python-placeholder" name="Python Placeholder" map-to="def:preprocessor"/> + <style id="regex-placeholder" name="Regular Expression Placeholder" map-to="def:preprocessor"/> + <style id="tabstop" name="Tabstop" map-to="def:decimal"/> + <style id="placeholder-ref" name="Placeholder Reference" map-to="def:decimal"/> + <style id="placeholder-def" name="Placeholder Default" map-to="def:string"/> + <style id="escape" name="Escape" map-to="def:special-char"/> + <style id="environmental-var" name="Environmental Variable" map-to="def:string"/> + <style id="seperator" name="Seperator" map-to="def:shebang"/> + <style id="regex-pattern" name="Regular Expression Pattern" map-to="def:string"/> + <style id="replace-pattern" name="Regular Expression Replace Pattern" map-to="def:string"/> + <style id="modifier" name="Modifier" map-to="def:keyword"/> + </styles> + + <definitions> + <define-regex id="number">[0-9]+</define-regex> + <define-regex id="tabstop">\s*((\%{number})(:))</define-regex> + <define-regex id="number-list" extended="true">\s*(\[(\%{number}(,\%{number})*)\](:))</define-regex> + <define-regex id="environment">\$[A-Z_]+</define-regex> + <define-regex id="regex-pattern">((?:\\[/]|\\}|[^/}])+)</define-regex> + + <context id="escape" style-ref="escape"> + <match>\\\$</match> + </context> + <context id="single-placeholder" style-ref="single-placeholder"> + <match>\$\%{number}|\${\%{number}}</match> + </context> + <context id="simple-placeholder-def" style-ref="default-value"> + <start>\${\%{tabstop}</start> + <end>}</end> + <include> + <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/> + <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/> + <context sub-pattern="2" where="start" style-ref="tabstop"/> + <context sub-pattern="3" where="start" style-ref="seperator"/> + <context> + <match>\\}</match> + </context> + <context ref="escape"/> + <context ref="environmental-variable"/> + </include> + </context> + <context id="simple-placeholder"> + <include> + <context ref="single-placeholder"/> + <context ref="simple-placeholder-def"/> + </include> + </context> + <context id="shell-placeholder-contents"> + <include> + <context ref="escape"/> + <context ref="environmental-variable"/> + <context ref="single-placeholder"/> + </include> + </context> + <context id="shell-placeholder"> + <include> + <context style-ref="shell-placeholder"> + <start>\$\(\%{tabstop}?</start> + <end>\)</end> + <include> + <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/> + <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/> + <context sub-pattern="2" where="start" style-ref="tabstop"/> + <context sub-pattern="3" where="start" style-ref="seperator"/> + <context ref="shell-placeholder-contents"/> + <context> + <match>\\\)</match> + </context> + </include> + </context> + <context style-ref="shell-placeholder"> + <start>`\%{tabstop}?</start> + <end>`</end> + <include> + <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/> + <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/> + <context sub-pattern="2" where="start" style-ref="tabstop"/> + <context sub-pattern="3" where="start" style-ref="seperator"/> + <context ref="shell-placeholder-contents"/> + <context> + <match>\\`</match> + </context> + </include> + </context> + </include> + </context> + <context id="python-placeholder"> + <start>\$<\%{tabstop}?\%{number-list}?</start> + <end>></end> + <include> + <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/> + <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/> + <context sub-pattern="2" where="start" style-ref="tabstop"/> + <context sub-pattern="3" where="start" style-ref="seperator"/> + <context sub-pattern="5" where="start" style-ref="tabstop"/> + <context sub-pattern="7" where="start" style-ref="seperator"/> + <context> + <match>\\></match> + </context> + <context ref="escape"/> + <context ref="environmental-variable"/> + <context ref="single-placeholder"/> + <context ref="python:python"/> + </include> + </context> + <context id="regex-placeholder" style-ref="regex-placeholder"> + <match>(\${)\%{tabstop}?(?:\s*(?:(\%{number})|(\%{environment})))/\%{regex-pattern}/\%{regex-pattern}(?:[/]([a-zA-Z]*))?(})</match> + <include> + <context sub-pattern="1" style-ref="placeholder-bounds"/> + <context sub-pattern="10" style-ref="placeholder-bounds"/> + <context sub-pattern="3" style-ref="tabstop"/> + <context sub-pattern="4" style-ref="seperator"/> + <context sub-pattern="5" style-ref="tabstop"/> + <context sub-pattern="6" style-ref="environmental-var"/> + <context sub-pattern="7" style-ref="regex-pattern"/> + <context sub-pattern="8" style-ref="replace-pattern"/> + <context sub-pattern="9" style-ref="modifier"/> + </include> + </context> + <context id="environmental-variable" style-ref="environmental-var"> + <match>\%{environment}</match> + </context> + <context id="snippets"> + <include> + <context ref="escape"/> + <context ref="regex-placeholder"/> + <context ref="simple-placeholder"/> + <context ref="shell-placeholder"/> + <context ref="python-placeholder"/> + <context ref="environmental-variable"/> + </include> + </context> + </definitions> +</language> diff --git a/plugins/snippets/data/latex.xml b/plugins/snippets/data/latex.xml new file mode 100755 index 00000000..71672ec8 --- /dev/null +++ b/plugins/snippets/data/latex.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="LaTeX"> + <snippet id="command"> + <text><![CDATA[{\\${1:bf} $GEDIT_SELECTED_TEXT}]]></text> + <accelerator><![CDATA[<Shift><Alt>w]]></accelerator> + <description>Wrap Selection in Command</description> + </snippet> + <snippet id="$"> + <text><![CDATA[\[ + $1 +\]]]></text> + <description>Displaymath</description> + <tag>$</tag> + </snippet> + <snippet id="itd"> + <text><![CDATA[\item[${1:description}] ${0:item}]]></text> + <description>\item[description]</description> + <tag>itd</tag> + </snippet> + <snippet id="sec"> + <text><![CDATA[\section{${1:section name}}\label{${2:label}} +]]></text> + <description>Section</description> + <tag>sec</tag> + </snippet> + <snippet id="sub"> + <text><![CDATA[\subsection{${1:subsection name}}\label{${2:label}} +]]></text> + <description>Sub Section</description> + <tag>sub</tag> + </snippet> + <snippet id="ssub"> + <text><![CDATA[\subsubsection{${1:subsubsection name}}\label{${2:label}} +]]></text> + <description>Sub Sub Section</description> + <tag>ssub</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/mallard.xml b/plugins/snippets/data/mallard.xml new file mode 100755 index 00000000..ffa418d7 --- /dev/null +++ b/plugins/snippets/data/mallard.xml @@ -0,0 +1,207 @@ +<?xml version='1.0' encoding='utf-8'?> +<snippets language="mallard"> + <snippet> + <text><![CDATA[<app>${1:Application's name}</app>$0]]></text> + <tag>app</tag> + <description>app</description> + </snippet> + <snippet> + <text><![CDATA[<![CDATA[$0]]]]><![CDATA[>]]></text> + <tag>cdata</tag> + <description>cdata</description> + </snippet> + <snippet> + <text><![CDATA[<cite>${1:Joe Example}</cite>]]></text> + <tag>cite</tag> + <description>cite</description> + </snippet> + <snippet> + <text><![CDATA[<cite date="$1">${2:Joe Example}</cite>]]></text> + <tag>cited</tag> + <description>cite</description> + </snippet> + <snippet> + <text><![CDATA[<desc>${1:Description}</desc>]]></text> + <tag>desc</tag> + <description>desc</description> + </snippet> + <snippet> + <text><![CDATA[<em>$1</em>]]></text> + <tag>em</tag> + <description>em</description> + </snippet> + <snippet> + <text><![CDATA[<figure> + <title>${1:Title}</title> + <desc>${2:Short description}</desc> + <media type="${3:image}" mime="${5:image/png}" src="${4:figures/image.png}" > + </media> +</figure>]]></text> + <tag>figure</tag> + <description>figure</description> + </snippet> + <snippet> + <text><![CDATA[<file>${0:filename}</file>]]></text> + <tag>file</tag> + <description>file</description> + </snippet> + <snippet> + <text><![CDATA[<gui>$1</gui>$2]]></text> + <tag>g</tag> + <description>gui</description> + </snippet> + <snippet> + <text><![CDATA[<guiseq><gui>$1</gui><gui>$2</gui></guiseq>]]></text> + <tag>q</tag> + <description>guiseq</description> + </snippet> + <snippet> + <text><![CDATA[<link href="http://$1/"<$2></link>]]></text> + <tag>http</tag> + <description>http</description> + </snippet> + <snippet> + <text><![CDATA[<item><p>$0</p></item>]]></text> + <tag>item</tag> + <description>item</description> + </snippet> + <snippet> + <text><![CDATA[<key>$1</key>]]></text> + <tag>key</tag> + <description>key</description> + </snippet> + <snippet> + <text><![CDATA[<keyseq><key>$1</key><key>$2</key></keyseq>]]></text> + <tag>keys</tag> + <description>keystroke</description> + </snippet> + <snippet> + <text><![CDATA[<link type="${1:guide}" xref="${2:index}"/>]]></text> + <tag>link</tag> + <description>link</description> + </snippet> + <snippet> + <text><![CDATA[<list> + <title>${2:Title}</title> + <item><p>${3}</p></item> + <item><p>${4}</p></item> +</list>]]></text> + <tag>list</tag> + <description>list</description> + </snippet> + <snippet> + <text><![CDATA[<listing> + <title>${1:Title}</title> + <desc>${2:Short description}</desc> + <code><![CDATA[ +$0 +]]]]><![CDATA[></code> +</listing>]]></text> + <tag>listing</tag> + <description>listing</description> + </snippet> + <snippet> + <text><![CDATA[<note style="${1:advanced|bug|important|tip|warning}"> + <p> + $0 + </p> +</note>]]></text> + <tag>note</tag> + <description>note</description> + </snippet> + <snippet> + <text><![CDATA[<list type="numbered"> + <item><p>$1</p></item> + <item><p>$2</p></item> + <item><p>$3</p></item> +</list> ]]></text> + <tag>num</tag> + <description>numbered list</description> + </snippet> + <snippet> + <text><![CDATA[<page xmlns="http://projectmallard.org/1.0/" + type="${1:topic}" style="${2:task}" + id="${3:id}"> + +<info> + <desc>${4:Short description}</desc> + <revision pkgversion="${5:program_version}" version="${6:document_version}" date="$<7: import datetime as d; return d.date.isoformat(d.date.today())>" status="${8:incomplete}"/> + <credit type="author"> + <name>$<9: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return 'Joe Example' ></name> + <email>$<10: +import os +return os.getenv('EMAIL', '[email protected]') ></email> + </credit> +</info> + +$0 + +</page>]]></text> + <tag>page</tag> + <description>page</description> + </snippet> + <snippet> + <text><![CDATA[</p>]]></text> + <tag>/</tag> + <description>p close</description> + </snippet> + <snippet> + <text><![CDATA[<p>]]></text> + <tag>p</tag> + <description>p open</description> + </snippet> + <snippet> + <text><![CDATA[<quote> + <p>$0</p> +</quote>]]></text> + <tag>quote</tag> + <description>quote</description> + </snippet> + <snippet> + <text><![CDATA[<section id="${1}"> + <title>${2:Section Title}</title> + <p>$0</p> +</section>]]></text> + <tag>section</tag> + <description>section</description> + </snippet> + <snippet> + <text><![CDATA[<steps> + <item><p>$0</p></item> +</steps>]]></text> + <tag>steps</tag> + <description>steps</description> + </snippet> + <snippet> + <text><![CDATA[<terms> + <title>$1</title> + <item><p>$2</p></item> +</terms>]]></text> + <tag>terms</tag> + <description>terms</description> + </snippet> + <snippet> + <text><![CDATA[<note style="tip"> + <p>$1</p> +</note> ]]></text> + <tag>tip</tag> + <description>tip note</description> + </snippet> + <snippet> + <text><![CDATA[<title>$1</title>]]></text> + <tag>title</tag> + <description>title</description> + </snippet> + <snippet> + <text><![CDATA[<note style="warning> + <p>$1</p> +</note> ]]></text> + <tag>warn</tag> + <description>warning note</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/perl.xml b/plugins/snippets/data/perl.xml new file mode 100755 index 00000000..add148fa --- /dev/null +++ b/plugins/snippets/data/perl.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="Perl"> + <snippet id="perl"> + <text><![CDATA[#!/usr/bin/perl +$0]]></text> + <tag>perl</tag> + <description>#!/usr/bin/perl</description> + </snippet> + <snippet id="ife"> + <text><![CDATA[if ($1) { + ${2:# body...} +} else { + ${3:# else...} +} +]]></text> + <description>Conditional if..else</description> + <tag>ife</tag> + </snippet> + <snippet id="ifee"> + <text><![CDATA[if ($1) { + ${2:# body...} +} elsif ($3) { + ${4:# elsif...} +} else { + ${5:# else...} +} +]]></text> + <description>Conditional if..elsif..else</description> + <tag>ifee</tag> + </snippet> + <snippet id="xunless"> + <text><![CDATA[${1:expression} unless ${2:condition}; +]]></text> + <description>Conditional one-line</description> + <tag>xunless</tag> + </snippet> + <snippet id="xif"> + <text><![CDATA[${1:expression} if ${2:condition}; +]]></text> + <description>Conditional one-line</description> + <tag>xif</tag> + </snippet> + <snippet id="eval"> + <text><![CDATA[eval { + ${1:# do something risky...} +}; +if ($@) { + ${2:# handle failure...} +} +]]></text> + <description>Try/Except</description> + <tag>eval</tag> + </snippet> + <snippet id="fore"> + <text><![CDATA[foreach ${1:my $${2:x} }(@${3:array}) { + ${4:# body...} +} +]]></text> + <description>Loop</description> + <tag>fore</tag> + </snippet> + <snippet id="for"> + <text><![CDATA[for (my $${1:var} = 0; $$1 < ${2:expression}; $$1++) { + ${3:# body...} +} +]]></text> + <description>Loop</description> + <tag>for</tag> + </snippet> + <snippet id="sub"> + <text><![CDATA[sub ${1:function_name} { + ${2:# body...} +} +]]></text> + <description>Function</description> + <tag>sub</tag> + </snippet> + <snippet id="hashpointer"> + <text><![CDATA[ => ]]></text> + <accelerator><![CDATA[<Shift><Alt>l]]></accelerator> + <description>hash pointer</description> + </snippet> + <snippet id="if"> + <text><![CDATA[if ($1) { + ${2:# body...} +} +]]></text> + <description>Conditional</description> + <tag>if</tag> + </snippet> + <snippet id="xfore"> + <text><![CDATA[${1:expression} foreach @${2:array}; +]]></text> + <description>Loop one-line</description> + <tag>xfore</tag> + </snippet> + <snippet id="xwhile"> + <text><![CDATA[${1:expression} while ${2:condition}; +]]></text> + <description>Loop one-line</description> + <tag>xwhile</tag> + </snippet> + <snippet id="slurp"> + <text><![CDATA[my $${1:var}; +{ local $/ = undef; local *FILE; open FILE, "<${2:file}"; $$1 = <FILE>; close FILE } +]]></text> + <description>Read File</description> + <tag>slurp</tag> + </snippet> + <snippet id="unless"> + <text><![CDATA[unless ($1) { + ${2:# body...} +} +]]></text> + <description>Conditional</description> + <tag>unless</tag> + </snippet> + <snippet id="while"> + <text><![CDATA[while ($1) { + ${2:# body...} +} +]]></text> + <description>Loop</description> + <tag>while</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/php.xml b/plugins/snippets/data/php.xml new file mode 100755 index 00000000..7d03f97d --- /dev/null +++ b/plugins/snippets/data/php.xml @@ -0,0 +1,224 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="PHP"> + <snippet id="class"> + <text><![CDATA[#doc +# classname: ${1:ClassName} +# scope: ${2:PUBLIC} +# +#/doc + +class ${1:ClassName} ${3:extends AnotherClass} +{ + # internal variables + + # Constructor + function __construct (${4:argument}) + { + # code... + $0 + } + ### + +} +###]]></text> + <description>class ..</description> + <tag>class</tag> + </snippet> + <snippet id="$"> + <text><![CDATA[\$_COOKIE['${1:variable}']]]></text> + <description>COOKIE['..']</description> + <tag>$</tag> + </snippet> + <snippet id="do"> + <text><![CDATA[do +{ + # code... + $0 +} while (${1:$a <= 10});]]></text> + <description>do .. while ..</description> + <tag>do</tag> + </snippet> + <snippet id="elseif"> + <text><![CDATA[elseif (${1:condition}) +{ + # code... + $0 +}]]></text> + <description>elseif ..</description> + <tag>elseif</tag> + </snippet> + <snippet id="else"> + <text><![CDATA[else +{ + # code... + $0 +}]]></text> + <description>else ..</description> + <tag>else</tag> + </snippet> + <snippet id="$-1"> + <text><![CDATA[\$_ENV['${1:variable}']]]></text> + <description>ENV['..']</description> + <tag>$</tag> + </snippet> + <snippet id="$-2"> + <text><![CDATA[\$_FILES['${1:variable}']]]></text> + <description>FILES['..']</description> + <tag>$</tag> + </snippet> + <snippet id="foreach"> + <text><![CDATA[foreach ($${1:variable} as $${2:key} => $${3:value}) +{ + # code... + $0: +}]]></text> + <description>foreach ..</description> + <tag>foreach</tag> + </snippet> + <snippet id="for"> + <text><![CDATA[for ($${1:i} = ${2:0}; $${1:i} < $3; $${1:i}++) +{ + # code... + $0 +}]]></text> + <description>for ..</description> + <tag>for</tag> + </snippet> + <snippet id="function"> + <text><![CDATA[${1:public }function ${2:FunctionName}($3) +{ + ${0:# code...} +}]]></text> + <description>function ..</description> + <tag>function</tag> + </snippet> + <snippet id="$-3"> + <text><![CDATA[\$_GET['${1:variable}']]]></text> + <description>GET['..']</description> + <tag>$</tag> + </snippet> + <snippet id="globals"> + <text><![CDATA[\$GLOBALS['${1:variable}']${2: =} ${3:something} ${4:;}]]></text> + <description>$GLOBALS['..']</description> + <tag>globals</tag> + </snippet> + <snippet id="if?"> + <text><![CDATA[$${1:retVal} = (${2:condition}) ? ${3:a} : ${4:b};]]></text> + <description>$.. =</description> + <tag>iff</tag> + </snippet> + <snippet id="ifelse"> + <text><![CDATA[if (${1:condition}) +{ + ${2:# code...} +} +else +{ + ${3:# code...} +} +$0]]></text> + <description>if .. else ..</description> + <tag>ifelse</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[if (${1:condition}) +{ + # code... + $0 +}]]></text> + <description>if ..</description> + <tag>if</tag> + </snippet> + <snippet id="incl1"> + <text><![CDATA[include_once('${1:file}');$0]]></text> + <description>include_once</description> + <tag>inclo</tag> + </snippet> + <snippet id="incl"> + <text><![CDATA[include('${1:file}');$0]]></text> + <description>include</description> + <tag>incl</tag> + </snippet> + <snippet id="array"> + <text><![CDATA[$${1:arrayName} = array('$2'${3:,});]]></text> + <description>$.. = array</description> + <tag>array</tag> + </snippet> + <snippet id="php"> + <text><![CDATA[<?php + + $0 + +?>]]></text> + <description><?php .. ?></description> + <tag>php</tag> + </snippet> + <snippet id="$-4"> + <text><![CDATA[\$_POST['${1:variable}']]]></text> + <description>POST['..']</description> + <tag>$</tag> + </snippet> + <snippet id="print"> + <text><![CDATA[print "${1:string}"${2: . };]]></text> + <description>print ".."</description> + <tag>print</tag> + </snippet> + <snippet id="$-5"> + <text><![CDATA[\$_REQUEST['${1:variable}']]]></text> + <description>REQUEST['..']</description> + <tag>$</tag> + </snippet> + <snippet id="req1"> + <text><![CDATA[require_once('${1:file}');]]></text> + <description>require_once</description> + <tag>reqo</tag> + </snippet> + <snippet id="req"> + <text><![CDATA[require('${1:file}');]]></text> + <description>require</description> + <tag>req</tag> + </snippet> + <snippet id="$-6"> + <text><![CDATA[\$_SERVER['${1:variable}']]]></text> + <description>SERVER['..']</description> + <tag>$</tag> + </snippet> + <snippet id="$-7"> + <text><![CDATA[\$_SESSION['${1:variable}']]]></text> + <description>SESSION['..']</description> + <tag>$</tag> + </snippet> + <snippet id="case"> + <text><![CDATA[case '${1:variable}': + # code... + $0 +break;]]></text> + <description>case ..</description> + <tag>case</tag> + </snippet> + <snippet id="switch"> + <text><![CDATA[switch (${1:variable}) +{ + case '${2:value}': + ${3:# code...} + break; + + $0 + + default: + ${4:# code...} + break; +}]]></text> + <description>switch ..</description> + <tag>switch</tag> + </snippet> + <snippet id="while"> + <text><![CDATA[while (${1:$a <= 10}) +{ + # code... + $0 +}]]></text> + <description>while ..</description> + <tag>while</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/python.xml b/plugins/snippets/data/python.xml new file mode 100755 index 00000000..a25617b8 --- /dev/null +++ b/plugins/snippets/data/python.xml @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="Python"> + <snippet id="py"> + <text><![CDATA[#!/usr/bin/env python +#-*- coding:utf-8 -*- + +$0]]></text> + <description>#!/usr/bin/env python</description> + <tag>py</tag> + </snippet> + <snippet id="def"> + <text><![CDATA[def ${1:fname}(${2:self}): + ${3:pass}]]></text> + <description>New Function</description> + <tag>def</tag> + </snippet> + <snippet id="doc"> + <text><![CDATA[""" + $1 +""" +$0]]></text> + <description>doc string</description> + <tag>doc</tag> + </snippet> + <snippet id="get"> + <text><![CDATA[def get$1(self): return self._$1]]></text> + <description>New Get Method</description> + <tag>get</tag> + </snippet> + <snippet id="class"> + <text><![CDATA[class ${1:ClassName} (${2:object}): + + def __init__(self${3:,}): + ${4:pass} + +$0]]></text> + <description>New Class</description> + <tag>class</tag> + </snippet> + <snippet id="for"> + <text><![CDATA[for ${1:i} in ${2:xrange}(${3:count}): + $0]]></text> + <description>for loop</description> + <tag>for</tag> + </snippet> + <snippet id="from"> + <text><![CDATA[from $1 import $2 +$0]]></text> + <description>from</description> + <tag>from</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[if ${1:condition}: + $0]]></text> + <description>if</description> + <tag>if</tag> + </snippet> + <snippet id="elif"> + <text><![CDATA[elif ${1:condition}: + $0]]></text> + <description>elif</description> + <tag>elif</tag> + </snippet> + <snippet id="else"> + <text><![CDATA[else: + $0]]></text> + <description>else</description> + <tag>else</tag> + </snippet> + <snippet id="while"> + <text><![CDATA[while ${1:condition}: + $0]]></text> + <tag>while</tag> + <description>while loop</description> + </snippet> + <snippet id="insert"> + <text><![CDATA["${1:$GEDIT_SELECTED_TEXT}"]]></text> + <accelerator><![CDATA[<Control>2]]></accelerator> + <description>Inside String: Insert "…"</description> + </snippet> + <snippet id="insert-1"> + <text><![CDATA['${1:$GEDIT_SELECTED_TEXT}']]></text> + <accelerator><![CDATA[<Control>apostrophe]]></accelerator> + <description>Inside String: Insert '…'</description> + </snippet> + <snippet id="."> + <text><![CDATA[self.]]></text> + <description>self</description> + <tag>.</tag> + </snippet> + <snippet id="set"> + <text><![CDATA[def set$1(self, ${2:newValue}): self._$1 = $2]]></text> + <description>New Set Method</description> + <tag>set</tag> + </snippet> + <snippet id="try"> + <text><![CDATA[try: + $1 +except ${2:Error}: + $0]]></text> + <tag>try</tag> + <description>Try... Except</description> + </snippet> + <snippet id="main"> + <text><![CDATA[if __name__ == '__main__': + ${1:sys.exit(main())} + +$0]]></text> + <description>main</description> + <tag>main</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/ruby.xml b/plugins/snippets/data/ruby.xml new file mode 100755 index 00000000..e68602f3 --- /dev/null +++ b/plugins/snippets/data/ruby.xml @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="Ruby"> + <snippet id="forin"> + <text><![CDATA[for ${1:element} in ${2:collection} + ${1:element}.$0 +end]]></text> + <description>for .. in .. end</description> + <tag>forin</tag> + </snippet> + <snippet id="inject"> + <text><![CDATA[inject(${1:object}) { |${2:injection}, ${3:element}| $0 }]]></text> + <description>inject object</description> + <tag>inject</tag> + </snippet> + <snippet id="reject"> + <text><![CDATA[reject { |${1:element}| ${1:element}.$0 }]]></text> + <description>reject element</description> + <tag>reject</tag> + </snippet> + <snippet id="select"> + <text><![CDATA[select { |${1:element}| ${1:element}.$0 }]]></text> + <description>select element</description> + <tag>select</tag> + </snippet> + <snippet id="ife"> + <text><![CDATA[if ${1:condition} + $2 +else + $3 +end]]></text> + <description>if .. else .. end</description> + <tag>ife</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[if ${1:condition} + $0 +end]]></text> + <description>if .. end</description> + <tag>if</tag> + </snippet> + <snippet id="case"> + <text><![CDATA[case ${1:object} + when ${2:condition} + $0 +end]]></text> + <description>case .. end</description> + <tag>case</tag> + </snippet> + <snippet id="begin"> + <text><![CDATA[begin + $1 +rescue ${2:Exception} => ${3:e} + $0 +end]]></text> + <description>begin .. rescue .. end</description> + <tag>begin</tag> + </snippet> + <snippet id="class"> + <text><![CDATA[class ${1:class_name} + $0 +end]]></text> + <description>class .. end</description> + <tag>class</tag> + </snippet> + <snippet id="collecto"> + <text><![CDATA[collect do |${1:element}| + ${1:element}.$0 +end]]></text> + <description>collect element do</description> + <tag>collecto</tag> + </snippet> + <snippet id="collect"> + <text><![CDATA[collect { |${1:element}| ${1:element}.$0 }]]></text> + <description>collect element</description> + <tag>collect</tag> + </snippet> + <snippet id="def"> + <text><![CDATA[def ${1:method_name} + $0 +end]]></text> + <description>def .. end</description> + <tag>def</tag> + </snippet> + <snippet id="do"> + <text><![CDATA[do + $0 +end]]></text> + <description>do .. end</description> + <tag>do</tag> + </snippet> + <snippet id="doo"> + <text><![CDATA[do |${1:object}| + $0 +end]]></text> + <description>do |object| .. end</description> + <tag>doo</tag> + </snippet> + <snippet id="eacho"> + <text><![CDATA[each do |${1:element}| + ${1:element}.$0 +end]]></text> + <description>each element do</description> + <tag>eacho</tag> + </snippet> + <snippet id="each"> + <text><![CDATA[each { |${1:element}| ${1:element}.$0 }]]></text> + <description>each element</description> + <tag>each</tag> + </snippet> + <snippet id="each_with_indexo"> + <text><![CDATA[each_with_index do |${1:element}, ${2:idx}| + ${1:element}.$0 +end]]></text> + <description>each_with_index do</description> + <tag>eachwithindexo</tag> + </snippet> + <snippet id="each_with_index"> + <text><![CDATA[each_with_index { |${1:element}, ${2:idx}| ${1:element}.$0 }]]></text> + <description>each_with_index</description> + <tag>eachwithindex</tag> + </snippet> + <snippet id=":"> + <text><![CDATA[:${1:key} => ${2:"value"}${3:, }]]></text> + <description>hash pair</description> + <tag>:</tag> + </snippet> + <snippet id="hashpointer"> + <text><![CDATA[ => ]]></text> + <accelerator><![CDATA[<Shift><Alt>l]]></accelerator> + <description>hash pointer</description> + </snippet> + <snippet id="injecto"> + <text><![CDATA[inject(${1:object}) do |${2:injection}, ${3:element}| + $0 +end]]></text> + <description>inject object do</description> + <tag>injecto</tag> + </snippet> + <snippet id="rejecto"> + <text><![CDATA[reject do |${1:element}| + ${1:element}.$0 +end]]></text> + <description>reject element do</description> + <tag>rejecto</tag> + </snippet> + <snippet id="selecto"> + <text><![CDATA[select do |${1:element}| + ${1:element}.$0 +end]]></text> + <description>select element do</description> + <tag>selecto</tag> + </snippet> + <snippet id="unless"> + <text><![CDATA[unless ${1:condition} + $0 +end]]></text> + <description>unless</description> + <tag>unless</tag> + </snippet> + <snippet id="when"> + <text><![CDATA[when ${1:condition} + $0]]></text> + <description>when</description> + <tag>when</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/sh.xml b/plugins/snippets/data/sh.xml new file mode 100755 index 00000000..b8fc0a62 --- /dev/null +++ b/plugins/snippets/data/sh.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="sh"> + <snippet id="elif"> + <text><![CDATA[elif [[ ${1:condition} ]]; then + $0]]></text> + <description>elif ..</description> + <tag>elif</tag> + </snippet> + <snippet id="case"> + <text><![CDATA[case ${1:choice} in +${2:first}) + $3 + ;; +*) + $4 + ;; +esac]]></text> + <description>case ..</description> + <tag>case</tag> + </snippet> + <snippet id="for"> + <text><![CDATA[for (( ${1:i = 0}; ${2:i < 10}; ${3:i++} )); do + $0 +done]]></text> + <description>for .. done</description> + <tag>for</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[if [[ ${1:condition} ]]; then + $0 +fi]]></text> + <description>if .. then</description> + <tag>if</tag> + </snippet> + <snippet id="sh"> + <text><![CDATA[#!/bin/sh +$0]]></text> + <description>#!/bin/sh</description> + <tag>sh</tag> + </snippet> + <snippet id="bash"> + <text><![CDATA[#!/bin/bash +$0]]></text> + <description>#!/bin/bash</description> + <tag>bash</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/snippets.xml b/plugins/snippets/data/snippets.xml new file mode 100755 index 00000000..ee405e6d --- /dev/null +++ b/plugins/snippets/data/snippets.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="snippets"> + <snippet id="simple"> + <text><![CDATA[\${${1:n:default}}]]></text> + <description>Simple Placeholder</description> + <tag>simple</tag> + </snippet> + <snippet id="simple-fallback"> + <text><![CDATA[\${${1:n:}[${2:default1,default2}]}]]></text> + <description>Simple Fallback Placeholder</description> + <tag>simplef</tag> + </snippet> + <snippet id="shell"> + <text><![CDATA[\$(${1:n:} ${2:shell code})]]></text> + <description>Shell Placeholder</description> + <tag>shell</tag> + </snippet> + <snippet id="python"> + <text><![CDATA[\$<${1:n:} ${2:[refs]:} return 'python code' >]]></text> + <description>Python Placeholder</description> + <tag>python</tag> + </snippet> + <snippet id="regex"> + <text><![CDATA[\${${1:n:} ${2:input}/${3:regex-pattern}/${4:replacement}/${5:modifiers}}]]></text> + <description>Regular Expression Placeholder</description> + <tag>regex</tag> + </snippet> + <snippet id="$-CURRENT_DOCUMENT_PATH"> + <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_PATH]]></text> + <description>Gedit Current Document Path Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-CURRENT_DOCUMENT_NAME"> + <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_NAME]]></text> + <description>Gedit Current Document Name Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-CURRENT_DOCUMENT_URI"> + <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_URI]]></text> + <description>Gedit Current Document Uri Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-CURRENT_DOCUMENT_SCHEME"> + <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_SCHEME]]></text> + <description>Gedit Current Document Scheme Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-CURRENT_DOCUMENT_TYPE"> + <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_TYPE]]></text> + <description>Gedit Current Document Type Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-DOCUMENTS_URI"> + <text><![CDATA[\$GEDIT_DOCUMENTS_URI]]></text> + <description>Gedit Documents Uri Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-DOCUMENTS_PATH"> + <text><![CDATA[\$GEDIT_DOCUMENTS_PATH]]></text> + <description>Gedit Documents Path Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-SELECTED_TEXT"> + <text><![CDATA[\$GEDIT_SELECTED_TEXT]]></text> + <description>Gedit Selected Text Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-CURRENT_WORD"> + <text><![CDATA[\$GEDIT_CURRENT_WORD]]></text> + <description>Gedit Current Word Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-CURRENT_LINE"> + <text><![CDATA[\$GEDIT_CURRENT_LINE]]></text> + <description>Gedit Current Line Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-CURRENT_LINE_NUMBER"> + <text><![CDATA[\$GEDIT_CURRENT_LINE_NUMBER]]></text> + <description>Gedit Current Line Number Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-DROP_FILENAME"> + <text><![CDATA[\$GEDIT_DROP_FILENAME]]></text> + <description>Gedit Drop Filename Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-DROP_REL_FILENAME"> + <text><![CDATA[\$GEDIT_DROP_REL_FILENAME]]></text> + <description>Gedit Drop Relative Filename Variable</description> + <tag>$</tag> + </snippet> + <snippet id="$-DROP_MIME_TYPE"> + <text><![CDATA[\$GEDIT_DROP_MIME_TYPE]]></text> + <description>Gedit Drop Mime Type Variable</description> + <tag>$</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/tcl.xml b/plugins/snippets/data/tcl.xml new file mode 100755 index 00000000..73a50c0a --- /dev/null +++ b/plugins/snippets/data/tcl.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="Tcl"> + <snippet id="foreach"> + <text><![CDATA[foreach ${1:var} ${2:$list} { + ${3} +} +]]></text> + <description>foreach...</description> + <tag>foreach</tag> + </snippet> + <snippet id="for"> + <text><![CDATA[for {${1:set i 0}} {${2:$i < $n}} {${3:incr i}} { + ${4} +} +]]></text> + <description>for...</description> + <tag>for</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[if {${1:condition}} { + ${2} +} +]]></text> + <description>if...</description> + <tag>if</tag> + </snippet> + <snippet id="proc"> + <text><![CDATA[proc ${1:name} {${2:args}} \ +{ + ${3} +} +]]></text> + <description>proc...</description> + <tag>proc</tag> + </snippet> + <snippet id="switch"> + <text><![CDATA[switch ${1:-exact} -- ${2:$var} { + ${3:match} { + ${4} + } + default {${5}} +} +]]></text> + <description>switch...</description> + <tag>switch</tag> + </snippet> + <snippet id="while"> + <text><![CDATA[while {${1:condition}} { + ${2} +} +]]></text> + <description>while...</description> + <tag>while</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/xml.xml b/plugins/snippets/data/xml.xml new file mode 100755 index 00000000..a53d565b --- /dev/null +++ b/plugins/snippets/data/xml.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="XML"> + <snippet id="""> + <text><![CDATA[<${1:name} ${2:attr}="${3:value}">$0</${1}>]]></text> + <description>Long Attribute Tag</description> + <tag>"</tag> + </snippet> + <snippet id="<"> + <text><![CDATA[<${1:name}>$0</${1}> + +]]></text> + <description>Long Tag</description> + <tag><</tag> + </snippet> + <snippet id=">"> + <text><![CDATA[<${1:name} />]]></text> + <description>Short Tag</description> + <tag>></tag> + </snippet> + <snippet id="cdata"> + <text><![CDATA[<![CDATA[$0]]]]><![CDATA[>]]></text> + <tag>cdata</tag> + <description>CDATA</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/xslt.xml b/plugins/snippets/data/xslt.xml new file mode 100755 index 00000000..0ff5cc15 --- /dev/null +++ b/plugins/snippets/data/xslt.xml @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="xslt"> + <snippet id="stylesheet"> + <text><![CDATA[<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +$0 +</xsl:stylesheet> +]]></text> + <description>StyleSheet</description> + <tag>stylesheet</tag> + </snippet> + <snippet id="include"> + <text><![CDATA[<xsl:include href="$1"/> +]]></text> + <description>Include</description> + <tag>inc</tag> + </snippet> + <snippet id="import"> + <text><![CDATA[<xsl:import href="$1"/> +]]></text> + <description>Import</description> + <tag>imp</tag> + </snippet> + <snippet id="param"> + <text><![CDATA[<xsl:param name="$1"/> +]]></text> + <description>Parameter</description> + <tag>param</tag> + </snippet> + <snippet id="template"> + <text><![CDATA[<xsl:template ${1:[match,name]}="$2" ${3:mode=""}> + $0 +</xsl:template> +]]></text> + <description>Template</description> + <tag>templ</tag> + </snippet> + <snippet id="variable-1"> + <text><![CDATA[<xsl:variable name="$1"> + $0 +</xsl:variable> +]]></text> + <description>Variable</description> + <tag>var</tag> + </snippet> + <snippet id="variable-2"> + <text><![CDATA[<xsl:variable name="$1" select="$2"/> +$0]]></text> + <description>Variable with Select Attribute</description> + <tag>var</tag> + </snippet> + <snippet id="choose"> + <text><![CDATA[<xsl:choose> + <xsl:when test="$1"> + $2 + </xsl:when> + $3 +</xsl:choose> +]]></text> + <description>Choose</description> + <tag>choose</tag> + </snippet> + <snippet id="when"> + <text><![CDATA[<xsl:when test="$1"> + $2 +</xsl:when> +$0]]></text> + <description>When</description> + <tag>when</tag> + </snippet> + <snippet id="otherwise"> + <text><![CDATA[<xsl:otherwise> + $1 +</xsl:otherwise> +$0]]></text> + <description>Otherwise</description> + <tag>otherwise</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[<xsl:if test="$1"> + $2 +</xsl:if> +$0]]></text> + <description>If</description> + <tag>if</tag> + </snippet> + <snippet id="value-of"> + <text><![CDATA[<xsl:value-of select="$1"/> +]]></text> + <description>Value of</description> + <tag>val</tag> + </snippet> + <snippet id="element"> + <text><![CDATA[<xsl:element name="$1"> +</xsl:element> +$0]]></text> + <description>Element</description> + <tag>elem</tag> + </snippet> + <snippet id="attribute"> + <text><![CDATA[<xsl:attribute name="$1">$2</xsl:attribute> +$0]]></text> + <description>Attribute</description> + <tag>attr</tag> + </snippet> + <snippet id="text"> + <text><![CDATA[<xsl:text>${1:$GEDIT_SELECTED_TEXT}</xsl:text> +]]></text> + <description>Text</description> + <tag>text</tag> + </snippet> + <snippet id="comment"> + <text><![CDATA[<xsl:comment>${1:$GEDIT_SELECTED_TEXT}</xsl:comment> +]]></text> + <description>Comment</description> + <tag>comment</tag> + </snippet> + <snippet id="call-template"> + <text><![CDATA[<xsl:call-template name="$1"/> +]]></text> + <description>Call Template</description> + <tag>call</tag> + </snippet> + <snippet id="apply-templates"> + <text><![CDATA[<xsl:apply-templates mode="$1" select="$2"/> +$0]]></text> + <description>Apply Templates</description> + <tag>applyt</tag> + </snippet> + <snippet id="apply-imports"> + <text><![CDATA[<xsl:apply-imports/> +]]></text> + <description>Apply Imports</description> + <tag>applyimp</tag> + </snippet> + <snippet id="with-param"> + <text><![CDATA[<xsl:with-param name="$1"> + $2 +</xsl:with-param> +$0]]></text> + <description>With Param</description> + <tag>with</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/snippets.gedit-plugin.desktop.in b/plugins/snippets/snippets.gedit-plugin.desktop.in new file mode 100755 index 00000000..dc3c8281 --- /dev/null +++ b/plugins/snippets/snippets.gedit-plugin.desktop.in @@ -0,0 +1,9 @@ +[Gedit Plugin] +Loader=python +Module=snippets +IAge=2 +_Name=Snippets +_Description=Insert often-used pieces of text in a fast way +Authors=Jesse van den Kieboom <[email protected]> +Copyright=Copyright © 2005 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/snippets/snippets/Completion.py b/plugins/snippets/snippets/Completion.py new file mode 100755 index 00000000..d83915a5 --- /dev/null +++ b/plugins/snippets/snippets/Completion.py @@ -0,0 +1,165 @@ +import gtksourceview2 as gsv +import gobject +import gedit +import gtk + +from Library import Library +from LanguageManager import get_language_manager +from Snippet import Snippet + +class Proposal(gobject.GObject, gsv.CompletionProposal): + def __init__(self, snippet): + gobject.GObject.__init__(self) + self._snippet = Snippet(snippet) + + def snippet(self): + return self._snippet.data + + # Interface implementation + def do_get_markup(self): + return self._snippet.display() + + def do_get_info(self): + return self._snippet.data['text'] + +class Provider(gobject.GObject, gsv.CompletionProvider): + def __init__(self, name, language_id, handler): + gobject.GObject.__init__(self) + + self.name = name + self.info_widget = None + self.proposals = [] + self.language_id = language_id + self.handler = handler + self.info_widget = None + self.mark = None + + theme = gtk.icon_theme_get_default() + w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU) + + self.icon = theme.load_icon(gtk.STOCK_JUSTIFY_LEFT, w, 0) + + def __del__(self): + if self.mark: + self.mark.get_buffer().delete_mark(self.mark) + + def set_proposals(self, proposals): + self.proposals = proposals + + def mark_position(self, it): + if not self.mark: + self.mark = it.get_buffer().create_mark(None, it, True) + else: + self.mark.get_buffer().move_mark(self.mark, it) + + def get_word(self, context): + it = context.get_iter() + + if it.starts_word() or it.starts_line() or not it.ends_word(): + return None + + start = it.copy() + + if start.backward_word_start(): + self.mark_position(start) + return start.get_text(it) + else: + return None + + def do_get_start_iter(self, context, proposal): + if not self.mark or self.mark.get_deleted(): + return None + + return self.mark.get_buffer().get_iter_at_mark(self.mark) + + def do_match(self, context): + return True + + def get_proposals(self, word): + if self.proposals: + proposals = self.proposals + else: + proposals = Library().get_snippets(None) + + if self.language_id: + proposals += Library().get_snippets(self.language_id) + + # Filter based on the current word + if word: + proposals = filter(lambda x: x['tag'].startswith(word), proposals) + + return map(lambda x: Proposal(x), proposals) + + def do_populate(self, context): + proposals = self.get_proposals(self.get_word(context)) + context.add_proposals(self, proposals, True) + + def do_get_name(self): + return self.name + + def do_activate_proposal(self, proposal, piter): + return self.handler(proposal, piter) + + def do_get_info_widget(self, proposal): + if not self.info_widget: + view = gedit.View(gedit.Document()) + manager = get_language_manager() + + lang = manager.get_language('snippets') + view.get_buffer().set_language(lang) + + sw = gtk.ScrolledWindow() + sw.add(view) + + self.info_view = view + self.info_widget = sw + + return self.info_widget + + def do_update_info(self, proposal, info): + buf = self.info_view.get_buffer() + + buf.set_text(proposal.get_info()) + buf.move_mark(buf.get_insert(), buf.get_start_iter()) + buf.move_mark(buf.get_selection_bound(), buf.get_start_iter()) + self.info_view.scroll_to_iter(buf.get_start_iter(), False) + + info.set_sizing(-1, -1, False, False) + info.process_resize() + + def do_get_icon(self): + return self.icon + + def do_get_activation(self): + return gsv.COMPLETION_ACTIVATION_USER_REQUESTED + +class Defaults(gobject.GObject, gsv.CompletionProvider): + def __init__(self, handler): + gobject.GObject.__init__(self) + + self.handler = handler + self.proposals = [] + + def set_defaults(self, defaults): + self.proposals = [] + + for d in defaults: + self.proposals.append(gsv.CompletionItem(d)) + + def do_get_name(self): + return "" + + def do_activate_proposal(self, proposal, piter): + return self.handler(proposal, piter) + + def do_populate(self, context): + context.add_proposals(self, self.proposals, True) + + def do_get_activation(self): + return gsv.COMPLETION_ACTIVATION_NONE + +gobject.type_register(Proposal) +gobject.type_register(Provider) +gobject.type_register(Defaults) + +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Document.py b/plugins/snippets/snippets/Document.py new file mode 100755 index 00000000..da166668 --- /dev/null +++ b/plugins/snippets/snippets/Document.py @@ -0,0 +1,1089 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import re + +import gtk +from gtk import gdk +import gio +import gedit +import gtksourceview2 as gsv +import gobject + +from Library import Library +from Snippet import Snippet +from Placeholder import * +import Completion + +class DynamicSnippet(dict): + def __init__(self, text): + self['text'] = text + self.valid = True + +class Document: + TAB_KEY_VAL = (gtk.keysyms.Tab, \ + gtk.keysyms.ISO_Left_Tab) + SPACE_KEY_VAL = (gtk.keysyms.space,) + + def __init__(self, instance, view): + self.view = None + self.instance = instance + + self.placeholders = [] + self.active_snippets = [] + self.active_placeholder = None + self.signal_ids = {} + + self.ordered_placeholders = [] + self.update_placeholders = [] + self.jump_placeholders = [] + self.language_id = 0 + self.timeout_update_id = 0 + + self.provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) + self.defaults_provider = Completion.Defaults(self.on_default_activated) + + # Always have a reference to the global snippets + Library().ref(None) + self.set_view(view) + + # Stop controlling the view. Remove all active snippets, remove references + # to the view and the plugin instance, disconnect all signal handlers + def stop(self): + if self.timeout_update_id != 0: + gobject.source_remove(self.timeout_update_id) + self.timeout_update_id = 0 + del self.update_placeholders[:] + del self.jump_placeholders[:] + + # Always release the reference to the global snippets + Library().unref(None) + self.set_view(None) + self.instance = None + self.active_placeholder = None + + def disconnect_signal(self, obj, signal): + if (obj, signal) in self.signal_ids: + obj.disconnect(self.signal_ids[(obj, signal)]) + del self.signal_ids[(obj, signal)] + + def connect_signal(self, obj, signal, cb): + self.disconnect_signal(obj, signal) + self.signal_ids[(obj, signal)] = obj.connect(signal, cb) + + def connect_signal_after(self, obj, signal, cb): + self.disconnect_signal(obj, signal) + self.signal_ids[(obj, signal)] = obj.connect_after(signal, cb) + + # Set the view to be controlled. Installs signal handlers and sets current + # language. If there is already a view set this function will first remove + # all currently active snippets and disconnect all current signals. So + # self.set_view(None) will effectively remove all the control from the + # current view + def _set_view(self, view): + if self.view: + buf = self.view.get_buffer() + + # Remove signals + signals = {self.view: ('key-press-event', 'destroy', + 'notify::editable', 'drag-data-received', 'expose-event'), + buf: ('notify::language', 'changed', 'cursor-moved', 'insert-text'), + self.view.get_completion(): ('hide',)} + + for obj, sig in signals.items(): + for s in sig: + self.disconnect_signal(obj, s) + + # Remove all active snippets + for snippet in list(self.active_snippets): + self.deactivate_snippet(snippet, True) + + completion = self.view.get_completion() + completion.remove_provider(self.provider) + completion.remove_provider(self.defaults_provider) + + self.view = view + + if view != None: + buf = view.get_buffer() + + self.connect_signal(view, 'destroy', self.on_view_destroy) + + if view.get_editable(): + self.connect_signal(view, 'key-press-event', self.on_view_key_press) + + self.connect_signal(buf, 'notify::language', self.on_notify_language) + self.connect_signal(view, 'notify::editable', self.on_notify_editable) + self.connect_signal(view, 'drag-data-received', self.on_drag_data_received) + self.connect_signal_after(view, 'expose-event', self.on_expose_event) + + self.update_language() + + completion = view.get_completion() + completion.add_provider(self.provider) + + completion.add_provider(self.defaults_provider) + + self.connect_signal(completion, 'hide', self.on_completion_hide) + elif self.language_id != 0: + langid = self.language_id + + self.language_id = None; + self.provider.language_id = self.language_id + + if self.instance: + self.instance.language_changed(self) + + Library().unref(langid) + + def set_view(self, view): + if view == self.view: + return + + self._set_view(view) + + # Call this whenever the language in the view changes. This makes sure that + # the correct language is used when finding snippets + def update_language(self): + lang = self.view.get_buffer().get_language() + + if lang == None and self.language_id == None: + return + elif lang and lang.get_id() == self.language_id: + return + + langid = self.language_id + + if lang: + self.language_id = lang.get_id() + else: + self.language_id = None + + if self.instance: + self.instance.language_changed(self) + + if langid != 0: + Library().unref(langid) + + Library().ref(self.language_id) + self.provider.language_id = self.language_id + + def accelerator_activate(self, keyval, mod): + if not self.view or not self.view.get_editable(): + return False + + accelerator = gtk.accelerator_name(keyval, mod) + snippets = Library().from_accelerator(accelerator, \ + self.language_id) + + snippets_debug('Accel!') + + if len(snippets) == 0: + return False + elif len(snippets) == 1: + self.apply_snippet(snippets[0]) + else: + # Do the fancy completion dialog + self.provider.set_proposals(snippets) + self.view.show_completion((self,)) + + return True + + def first_snippet_inserted(self): + buf = self.view.get_buffer() + + self.connect_signal(buf, 'changed', self.on_buffer_changed) + self.connect_signal(buf, 'cursor-moved', self.on_buffer_cursor_moved) + self.connect_signal_after(buf, 'insert-text', self.on_buffer_insert_text) + + def last_snippet_removed(self): + buf = self.view.get_buffer() + self.disconnect_signal(buf, 'changed') + self.disconnect_signal(buf, 'cursor-moved') + self.disconnect_signal(buf, 'insert-text') + + def current_placeholder(self): + buf = self.view.get_buffer() + + piter = buf.get_iter_at_mark(buf.get_insert()) + found = [] + + for placeholder in self.placeholders: + begin = placeholder.begin_iter() + end = placeholder.end_iter() + + if piter.compare(begin) >= 0 and piter.compare(end) <= 0: + found.append(placeholder) + + if self.active_placeholder in found: + return self.active_placeholder + elif len(found) > 0: + return found[0] + else: + return None + + def advance_placeholder(self, direction): + # Returns (CurrentPlaceholder, NextPlaceholder), depending on direction + buf = self.view.get_buffer() + + piter = buf.get_iter_at_mark(buf.get_insert()) + found = current = next = None + length = len(self.placeholders) + + placeholders = list(self.placeholders) + + if self.active_placeholder: + begin = self.active_placeholder.begin_iter() + end = self.active_placeholder.end_iter() + + if piter.compare(begin) >= 0 and piter.compare(end) <= 0: + current = self.active_placeholder + currentIndex = placeholders.index(self.active_placeholder) + + if direction == 1: + # w = piter, x = begin, y = end, z = found + nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \ + x.compare(z.begin_iter()) < 0)) + indexer = lambda x: x < length - 1 + else: + # w = piter, x = begin, y = end, z = prev + nearest = lambda w, x, y, z: (w.compare(x) >= 0 and (not z or \ + x.compare(z.begin_iter()) >= 0)) + indexer = lambda x: x > 0 + + for index in range(0, length): + placeholder = placeholders[index] + begin = placeholder.begin_iter() + end = placeholder.end_iter() + + # Find the nearest placeholder + if nearest(piter, begin, end, found): + foundIndex = index + found = placeholder + + # Find the current placeholder + if piter.compare(begin) >= 0 and \ + piter.compare(end) <= 0 and \ + current == None: + currentIndex = index + current = placeholder + + if current and current != found and \ + (current.begin_iter().compare(found.begin_iter()) == 0 or \ + current.end_iter().compare(found.begin_iter()) == 0) and \ + self.active_placeholder and \ + current.begin_iter().compare(self.active_placeholder.begin_iter()) == 0: + # if current and found are at the same place, then + # resolve the 'hugging' problem + current = self.active_placeholder + currentIndex = placeholders.index(current) + + if current: + if indexer(currentIndex): + next = placeholders[currentIndex + direction] + elif found: + next = found + elif length > 0: + next = self.placeholders[0] + + return current, next + + def next_placeholder(self): + return self.advance_placeholder(1) + + def previous_placeholder(self): + return self.advance_placeholder(-1) + + def cursor_on_screen(self): + buf = self.view.get_buffer() + self.view.scroll_mark_onscreen(buf.get_insert()) + + def set_active_placeholder(self, placeholder): + self.active_placeholder = placeholder + + def goto_placeholder(self, current, next): + last = None + + if current: + # Signal this placeholder to end action + self.view.get_completion().hide() + current.leave() + + if current.__class__ == PlaceholderEnd: + last = current + + self.set_active_placeholder(next) + + if next: + next.enter() + + if next.__class__ == PlaceholderEnd: + last = next + elif len(next.defaults) > 1 and next.get_text() == next.default: + self.defaults_provider.set_defaults(next.defaults) + + cm = self.view.get_completion() + cm.show([self.defaults_provider], cm.create_context()) + + if last: + # This is the end of the placeholder, remove the snippet etc + for snippet in list(self.active_snippets): + if snippet.placeholders[0] == last: + self.deactivate_snippet(snippet) + break + + self.cursor_on_screen() + + return next != None + + def skip_to_next_placeholder(self): + (current, next) = self.next_placeholder() + return self.goto_placeholder(current, next) + + def skip_to_previous_placeholder(self): + (current, prev) = self.previous_placeholder() + return self.goto_placeholder(current, prev) + + def env_get_selected_text(self, buf): + bounds = buf.get_selection_bounds() + + if bounds: + return buf.get_text(bounds[0], bounds[1]) + else: + return '' + + def env_get_current_word(self, buf): + start, end = buffer_word_boundary(buf) + + return buf.get_text(start, end) + + def env_get_current_line(self, buf): + start, end = buffer_line_boundary(buf) + + return buf.get_text(start, end) + + def env_get_current_line_number(self, buf): + start, end = buffer_line_boundary(buf) + + return str(start.get_line() + 1) + + def env_get_document_uri(self, buf): + location = buf.get_location() + + if location: + return location.get_uri() + else: + return '' + + def env_get_document_name(self, buf): + location = buf.get_location() + + if location: + return location.get_basename() + else: + return '' + + def env_get_document_scheme(self, buf): + location = buf.get_location() + + if location: + return location.get_uri_scheme() + else: + return '' + + def env_get_document_path(self, buf): + location = buf.get_location() + + if location: + return location.get_path() + else: + return '' + + def env_get_document_dir(self, buf): + location = buf.get_location() + + if location: + return location.get_parent().get_path() or '' + else: + return '' + + def env_get_document_type(self, buf): + typ = buf.get_mime_type() + + if typ: + return typ + else: + return '' + + def env_get_documents_uri(self, buf): + toplevel = self.view.get_toplevel() + + if isinstance(toplevel, gedit.Window): + documents_uri = [doc.get_location().get_uri() + for doc in toplevel.get_documents() + if doc.get_location() is not None] + else: + documents_uri = [] + + return ' '.join(documents_uri) + + def env_get_documents_path(self, buf): + toplevel = self.view.get_toplevel() + + if isinstance(toplevel, gedit.Window): + documents_location = [doc.get_location() + for doc in toplevel.get_documents() + if doc.get_location() is not None] + + documents_path = [location.get_path() + for location in documents_location + if gedit.utils.uri_has_file_scheme(location.get_uri())] + else: + documents_path = [] + + return ' '.join(documents_path) + + def update_environment(self): + buf = self.view.get_buffer() + + variables = {'GEDIT_SELECTED_TEXT': self.env_get_selected_text, + 'GEDIT_CURRENT_WORD': self.env_get_current_word, + 'GEDIT_CURRENT_LINE': self.env_get_current_line, + 'GEDIT_CURRENT_LINE_NUMBER': self.env_get_current_line_number, + 'GEDIT_CURRENT_DOCUMENT_URI': self.env_get_document_uri, + 'GEDIT_CURRENT_DOCUMENT_NAME': self.env_get_document_name, + 'GEDIT_CURRENT_DOCUMENT_SCHEME': self.env_get_document_scheme, + 'GEDIT_CURRENT_DOCUMENT_PATH': self.env_get_document_path, + 'GEDIT_CURRENT_DOCUMENT_DIR': self.env_get_document_dir, + 'GEDIT_CURRENT_DOCUMENT_TYPE': self.env_get_document_type, + 'GEDIT_DOCUMENTS_URI': self.env_get_documents_uri, + 'GEDIT_DOCUMENTS_PATH': self.env_get_documents_path, + } + + for var in variables: + os.environ[var] = variables[var](buf) + + def uses_current_word(self, snippet): + matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_WORD', snippet['text']) + + for match in matches: + if len(match) % 2 == 0: + return True + + return False + + def uses_current_line(self, snippet): + matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_LINE', snippet['text']) + + for match in matches: + if len(match) % 2 == 0: + return True + + return False + + def apply_snippet(self, snippet, start = None, end = None): + if not snippet.valid: + return False + + buf = self.view.get_buffer() + s = Snippet(snippet) + + if not start: + start = buf.get_iter_at_mark(buf.get_insert()) + + if not end: + end = buf.get_iter_at_mark(buf.get_selection_bound()) + + if start.equal(end) and self.uses_current_word(s): + # There is no tab trigger and no selection and the snippet uses + # the current word. Set start and end to the word boundary so that + # it will be removed + start, end = buffer_word_boundary(buf) + elif start.equal(end) and self.uses_current_line(s): + # There is no tab trigger and no selection and the snippet uses + # the current line. Set start and end to the line boundary so that + # it will be removed + start, end = buffer_line_boundary(buf) + + # Set environmental variables + self.update_environment() + + # You know, we could be in an end placeholder + (current, next) = self.next_placeholder() + if current and current.__class__ == PlaceholderEnd: + self.goto_placeholder(current, None) + + buf.begin_user_action() + + # Remove the tag, selection or current word + buf.delete(start, end) + + # Insert the snippet + holders = len(self.placeholders) + + if len(self.active_snippets) == 0: + self.first_snippet_inserted() + + sn = s.insert_into(self, start) + self.active_snippets.append(sn) + + # Put cursor at first tab placeholder + keys = filter(lambda x: x > 0, sn.placeholders.keys()) + + if len(keys) == 0: + if 0 in sn.placeholders: + self.goto_placeholder(self.active_placeholder, sn.placeholders[0]) + else: + buf.place_cursor(sn.begin_iter()) + else: + self.goto_placeholder(self.active_placeholder, sn.placeholders[keys[0]]) + + if sn in self.active_snippets: + # Check if we can get end_iter in view without moving the + # current cursor position out of view + cur = buf.get_iter_at_mark(buf.get_insert()) + last = sn.end_iter() + + curloc = self.view.get_iter_location(cur) + lastloc = self.view.get_iter_location(last) + + if (lastloc.y + lastloc.height) - curloc.y <= \ + self.view.get_visible_rect().height: + self.view.scroll_mark_onscreen(sn.end_mark) + + buf.end_user_action() + self.view.grab_focus() + + return True + + def get_tab_tag(self, buf, end = None): + if not end: + end = buf.get_iter_at_mark(buf.get_insert()) + + start = end.copy() + + word = None + + if start.backward_word_start(): + # Check if we were at a word start ourselves + tmp = start.copy() + tmp.forward_word_end() + + if tmp.equal(end): + word = buf.get_text(start, end) + else: + start = end.copy() + else: + start = end.copy() + + if not word or word == '': + if start.backward_char(): + word = start.get_char() + + if word.isalnum() or word.isspace(): + return (None, None, None) + else: + return (None, None, None) + + return (word, start, end) + + def parse_and_run_snippet(self, data, iter): + self.apply_snippet(DynamicSnippet(data), iter, iter) + + def run_snippet_trigger(self, trigger, bounds): + if not self.view: + return False + + snippets = Library().from_tag(trigger, self.language_id) + buf = self.view.get_buffer() + + if snippets: + if len(snippets) == 1: + return self.apply_snippet(snippets[0], bounds[0], bounds[1]) + else: + # Do the fancy completion dialog + self.provider.set_proposals(snippets) + cm = self.view.get_completion() + + cm.show([self.provider], cm.create_context()) + return True + + return False + + def run_snippet(self): + if not self.view: + return False + + buf = self.view.get_buffer() + + # get the word preceding the current insertion position + (word, start, end) = self.get_tab_tag(buf) + + if not word: + return self.skip_to_next_placeholder() + + if not self.run_snippet_trigger(word, (start, end)): + return self.skip_to_next_placeholder() + else: + return True + + def deactivate_snippet(self, snippet, force = False): + buf = self.view.get_buffer() + remove = [] + ordered_remove = [] + + for tabstop in snippet.placeholders: + if tabstop == -1: + placeholders = snippet.placeholders[-1] + else: + placeholders = [snippet.placeholders[tabstop]] + + for placeholder in placeholders: + if placeholder in self.placeholders: + if placeholder in self.update_placeholders: + placeholder.update_contents() + + self.update_placeholders.remove(placeholder) + elif placeholder in self.jump_placeholders: + placeholder[0].leave() + + remove.append(placeholder) + elif placeholder in self.ordered_placeholders: + ordered_remove.append(placeholder) + + for placeholder in remove: + if placeholder == self.active_placeholder: + self.active_placeholder = None + + self.placeholders.remove(placeholder) + self.ordered_placeholders.remove(placeholder) + + placeholder.remove(force) + + for placeholder in ordered_remove: + self.ordered_placeholders.remove(placeholder) + placeholder.remove(force) + + snippet.deactivate() + self.active_snippets.remove(snippet) + + if len(self.active_snippets) == 0: + self.last_snippet_removed() + + self.view.queue_draw() + + def update_snippet_contents(self): + self.timeout_update_id = 0 + + for placeholder in self.update_placeholders: + placeholder.update_contents() + + for placeholder in self.jump_placeholders: + self.goto_placeholder(placeholder[0], placeholder[1]) + + del self.update_placeholders[:] + del self.jump_placeholders[:] + + return False + + # Callbacks + def on_view_destroy(self, view): + self.stop() + return + + def on_buffer_cursor_moved(self, buf): + piter = buf.get_iter_at_mark(buf.get_insert()) + + # Check for all snippets if the cursor is outside its scope + for snippet in list(self.active_snippets): + if snippet.begin_mark.get_deleted() or snippet.end_mark.get_deleted(): + self.deactivate(snippet) + else: + begin = snippet.begin_iter() + end = snippet.end_iter() + + if piter.compare(begin) < 0 or piter.compare(end) > 0: + # Oh no! Remove the snippet this instant!! + self.deactivate_snippet(snippet) + + current = self.current_placeholder() + + if current != self.active_placeholder: + self.jump_placeholders.append((self.active_placeholder, current)) + + if self.timeout_update_id == 0: + self.timeout_update_id = gobject.timeout_add(0, + self.update_snippet_contents) + + def on_buffer_changed(self, buf): + current = self.current_placeholder() + + if current: + if not current in self.update_placeholders: + self.update_placeholders.append(current) + + if self.timeout_update_id == 0: + self.timeout_update_id = gobject.timeout_add(0, \ + self.update_snippet_contents) + + def on_buffer_insert_text(self, buf, piter, text, length): + ctx = get_buffer_context(buf) + + # do nothing special if there is no context and no active + # placeholder + if (not ctx) and (not self.active_placeholder): + return + + if not ctx: + ctx = self.active_placeholder + + if not ctx in self.ordered_placeholders: + return + + # move any marks that were incorrectly moved by this insertion + # back to where they belong + begin = ctx.begin_iter() + end = ctx.end_iter() + idx = self.ordered_placeholders.index(ctx) + + for placeholder in self.ordered_placeholders: + if placeholder == ctx: + continue + + ob = placeholder.begin_iter() + oe = placeholder.end_iter() + + if ob.compare(begin) == 0 and ((not oe) or oe.compare(end) == 0): + oidx = self.ordered_placeholders.index(placeholder) + + if oidx > idx and ob: + buf.move_mark(placeholder.begin, end) + elif oidx < idx and oe: + buf.move_mark(placeholder.end, begin) + elif ob.compare(begin) >= 0 and ob.compare(end) < 0 and (oe and oe.compare(end) >= 0): + buf.move_mark(placeholder.begin, end) + elif (oe and oe.compare(begin) > 0) and ob.compare(begin) <= 0: + buf.move_mark(placeholder.end, begin) + + def on_notify_language(self, buf, spec): + self.update_language() + + def on_notify_editable(self, view, spec): + self._set_view(view) + + def on_view_key_press(self, view, event): + library = Library() + + if not (event.state & gdk.CONTROL_MASK) and \ + not (event.state & gdk.MOD1_MASK) and \ + event.keyval in self.TAB_KEY_VAL: + if not event.state & gdk.SHIFT_MASK: + return self.run_snippet() + else: + return self.skip_to_previous_placeholder() + elif not library.loaded and \ + library.valid_accelerator(event.keyval, event.state): + library.ensure_files() + library.ensure(self.language_id) + self.accelerator_activate(event.keyval, \ + event.state & gtk.accelerator_get_default_mod_mask()) + + return False + + def path_split(self, path, components=[]): + head, tail = os.path.split(path) + + if not tail and head: + return [head] + components + elif tail: + return self.path_split(head, [tail] + components) + else: + return components + + def relative_path(self, first, second, mime): + prot1 = re.match('(^[a-z]+:\/\/|\/)(.*)', first) + prot2 = re.match('(^[a-z]+:\/\/|\/)(.*)', second) + + if not prot1 or not prot2: + return second + + # Different protocols + if prot1.group(1) != prot2.group(1): + return second + + # Split on backslash + path1 = self.path_split(prot1.group(2)) + path2 = self.path_split(prot2.group(2)) + + # Remove as long as common + while path1 and path2 and path1[0] == path2[0]: + path1.pop(0) + path2.pop(0) + + # If we need to ../ more than 3 times, then just return + # the absolute path + if len(path1) - 1 > 3: + return second + + if mime.startswith('x-directory'): + # directory, special case + if not path2: + result = './' + else: + result = '../' * (len(path1) - 1) + else: + # Insert ../ + result = '../' * (len(path1) - 1) + + if not path2: + result = os.path.basename(second) + + if path2: + result += os.path.join(*path2) + + return result + + def apply_uri_snippet(self, snippet, mime, uri): + # Remove file scheme + gfile = gio.File(uri) + pathname = '' + dirname = '' + ruri = '' + + if gedit.utils.uri_has_file_scheme(uri): + pathname = gfile.get_path() + dirname = gfile.get_parent().get_path() + + name = os.path.basename(uri) + scheme = gfile.get_uri_scheme() + + os.environ['GEDIT_DROP_DOCUMENT_URI'] = uri + os.environ['GEDIT_DROP_DOCUMENT_NAME'] = name + os.environ['GEDIT_DROP_DOCUMENT_SCHEME'] = scheme + os.environ['GEDIT_DROP_DOCUMENT_PATH'] = pathname + os.environ['GEDIT_DROP_DOCUMENT_DIR'] = dirname + os.environ['GEDIT_DROP_DOCUMENT_TYPE'] = mime + + buf = self.view.get_buffer() + location = buf.get_location() + if location: + ruri = location.get_uri() + + relpath = self.relative_path(ruri, uri, mime) + + os.environ['GEDIT_DROP_DOCUMENT_RELATIVE_PATH'] = relpath + + mark = buf.get_mark('gtk_drag_target') + + if not mark: + mark = buf.get_insert() + + piter = buf.get_iter_at_mark(mark) + self.apply_snippet(snippet, piter, piter) + + def in_bounds(self, x, y): + rect = self.view.get_visible_rect() + rect.x, rect.y = self.view.buffer_to_window_coords(gtk.TEXT_WINDOW_WIDGET, rect.x, rect.y) + + return not (x < rect.x or x > rect.x + rect.width or y < rect.y or y > rect.y + rect.height) + + def on_drag_data_received(self, view, context, x, y, data, info, timestamp): + if not (gtk.targets_include_uri(context.targets) and data.data and self.in_bounds(x, y)): + return + + uris = drop_get_uris(data) + uris.reverse() + stop = False + + for uri in uris: + try: + mime = gio.content_type_guess(uri) + except: + mime = None + + if not mime: + continue + + snippets = Library().from_drop_target(mime, self.language_id) + + if snippets: + stop = True + self.apply_uri_snippet(snippets[0], mime, uri) + + if stop: + context.finish(True, False, timestamp) + view.stop_emission('drag-data-received') + view.get_toplevel().present() + view.grab_focus() + + def find_uri_target(self, context): + lst = gtk.target_list_add_uri_targets((), 0) + + return self.view.drag_dest_find_target(context, lst) + + def on_completion_hide(self, completion): + self.provider.set_proposals(None) + + def on_proposal_activated(self, proposal, piter): + buf = self.view.get_buffer() + bounds = buf.get_selection_bounds() + + if bounds: + self.apply_snippet(proposal.snippet(), None, None) + else: + (word, start, end) = self.get_tab_tag(buf, piter) + self.apply_snippet(proposal.snippet(), start, end) + + return True + + def on_default_activated(self, proposal, piter): + buf = self.view.get_buffer() + bounds = buf.get_selection_bounds() + + if bounds: + buf.begin_user_action() + buf.delete(bounds[0], bounds[1]) + buf.insert(bounds[0], proposal.props.label) + buf.end_user_action() + + return True + else: + return False + + def iter_coords(self, piter): + rect = self.view.get_iter_location(piter) + rect.x, rect.y = self.view.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, rect.x, rect.y) + + return rect + + def placeholder_in_area(self, placeholder, area): + start = placeholder.begin_iter() + end = placeholder.end_iter() + + if not start or not end: + return False + + # Test if start is before bottom, and end is after top + start_rect = self.iter_coords(start) + end_rect = self.iter_coords(end) + + return start_rect.y <= area.y + area.height and \ + end_rect.y + end_rect.height >= area.y + + def draw_placeholder_rect(self, ctx, placeholder, col): + start = placeholder.begin_iter() + start_rect = self.iter_coords(start) + start_line = start.get_line() + + end = placeholder.end_iter() + end_rect = self.iter_coords(end) + end_line = end.get_line() + + line = start.copy() + line.set_line_offset(0) + geom = self.view.get_window(gtk.TEXT_WINDOW_TEXT).get_geometry() + + ctx.translate(0.5, 0.5) + + while line.get_line() <= end_line: + ypos, height = self.view.get_line_yrange(line) + x_, ypos = self.view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, 0, ypos) + + if line.get_line() == start_line and line.get_line() == end_line: + # Simply draw a box, both are on the same line + ctx.rectangle(start_rect.x, start_rect.y, end_rect.x - start_rect.x, start_rect.height - 1) + ctx.stroke() + elif line.get_line() == start_line or line.get_line() == end_line: + if line.get_line() == start_line: + rect = start_rect + else: + rect = end_rect + + ctx.move_to(0, rect.y + rect.height - 1) + ctx.rel_line_to(rect.x, 0) + ctx.rel_line_to(0, -rect.height + 1) + ctx.rel_line_to(geom[2], 0) + ctx.stroke() + + if not line.forward_line(): + break + + def draw_placeholder_bar(self, ctx, placeholder, col): + start = placeholder.begin_iter() + start_rect = self.iter_coords(start) + + ctx.translate(0.5, 0.5) + extend_width = 2.5 + + ctx.move_to(start_rect.x - extend_width, start_rect.y) + ctx.rel_line_to(extend_width * 2, 0) + + ctx.move_to(start_rect.x, start_rect.y) + ctx.rel_line_to(0, start_rect.height - 1) + + ctx.rel_move_to(-extend_width, 0) + ctx.rel_line_to(extend_width * 2, 0) + ctx.stroke() + + def from_color(self, col): + return [col.red / 0x10000, col.green / 0x10000, col.blue / 0x10000] + + def draw_placeholder(self, ctx, placeholder): + if isinstance(placeholder, PlaceholderEnd): + return + + buf = self.view.get_buffer() + + col = self.from_color(self.view.get_style().text[gtk.STATE_INSENSITIVE]) + ctx.set_source_rgba(col[0], col[1], col[2], 0.5) + + if placeholder.tabstop > 0: + ctx.set_dash([], 0) + else: + ctx.set_dash([2], 0) + + start = placeholder.begin_iter() + end = placeholder.end_iter() + + if start.equal(end): + self.draw_placeholder_bar(ctx, placeholder, col) + else: + self.draw_placeholder_rect(ctx, placeholder, col) + + def on_expose_event(self, view, event): + if event.window != view.get_window(gtk.TEXT_WINDOW_TEXT): + return False + + # Draw something + ctx = event.window.cairo_create() + ctx.rectangle(event.area) + ctx.clip() + + ctx.set_line_width(1.0) + + for placeholder in self.ordered_placeholders: + if not self.placeholder_in_area(placeholder, event.area): + continue + + ctx.save() + self.draw_placeholder(ctx, placeholder) + ctx.restore() + + return False + +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Exporter.py b/plugins/snippets/snippets/Exporter.py new file mode 100755 index 00000000..51d49104 --- /dev/null +++ b/plugins/snippets/snippets/Exporter.py @@ -0,0 +1,98 @@ +import os +import tempfile +import sys +import shutil + +from snippets.Library import * +import xml.etree.ElementTree as et +from Helper import * + +class Exporter: + def __init__(self, filename, snippets): + self.filename = filename + self.set_snippets(snippets) + + def set_snippets(self, snippets): + self.snippets = {} + + for snippet in snippets: + lang = snippet.language() + + if lang in self.snippets: + self.snippets[lang].append(snippet) + else: + self.snippets[lang] = [snippet] + + def export_xml(self, dirname, language, snippets): + # Create the root snippets node + root = et.Element('snippets') + + # Create filename based on language + if language: + filename = os.path.join(dirname, language + '.xml') + + # Set the language attribute + root.attrib['language'] = language + else: + filename = os.path.join(dirname, 'global.xml') + + # Add all snippets to the root node + for snippet in snippets: + root.append(snippet.to_xml()) + + # Write xml + write_xml(root, filename, ('text', 'accelerator')) + + def export_archive(self, cmd): + dirname = tempfile.mkdtemp() + + # Save current working directory and change to temporary directory + curdir = os.getcwd() + + try: + os.chdir(dirname) + + # Write snippet xml files + for language, snippets in self.snippets.items(): + self.export_xml(dirname, language , snippets) + + # Archive files + status = os.system('%s "%s" *.xml' % (cmd, self.filename)) + finally: + os.chdir(curdir) + + if status != 0: + return _('The archive "%s" could not be created' % self.filename) + + # Remove the temporary directory + shutil.rmtree(dirname) + + def export_targz(self): + self.export_archive('tar -c --gzip -f') + + def export_tarbz2(self): + self.export_archive('tar -c --bzip2 -f') + + def export_tar(self): + self.export_archive('tar -cf') + + def run(self): + dirname = os.path.dirname(self.filename) + if not os.path.exists(dirname): + return _('Target directory "%s" does not exist') % dirname + + if not os.path.isdir(dirname): + return _('Target directory "%s" is not a valid directory') % dirname + + (root, ext) = os.path.splitext(self.filename) + + actions = {'.tar.gz': self.export_targz, + '.tar.bz2': self.export_tarbz2, + '.tar': self.export_tar} + + for k, v in actions.items(): + if self.filename.endswith(k): + return v() + + return self.export_targz() +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Helper.py b/plugins/snippets/snippets/Helper.py new file mode 100755 index 00000000..de363360 --- /dev/null +++ b/plugins/snippets/snippets/Helper.py @@ -0,0 +1,182 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import string +from xml.sax import saxutils +from xml.etree.ElementTree import * +import re + +import gtk +from gtk import gdk + +def message_dialog(par, typ, msg): + d = gtk.MessageDialog(par, gtk.DIALOG_MODAL, typ, gtk.BUTTONS_OK, msg) + d.set_property('use-markup', True) + + d.run() + d.destroy() + +def compute_indentation(view, piter): + line = piter.get_line() + start = view.get_buffer().get_iter_at_line(line) + end = start.copy() + + ch = end.get_char() + + while (ch.isspace() and ch != '\r' and ch != '\n' and \ + end.compare(piter) < 0): + if not end.forward_char(): + break; + + ch = end.get_char() + + if start.equal(end): + return '' + + return start.get_slice(end) + +def markup_escape(text): + return saxutils.escape(text) + +def spaces_instead_of_tabs(view, text): + if not view.get_insert_spaces_instead_of_tabs(): + return text + + return text.replace("\t", view.get_tab_width() * ' ') + +def insert_with_indent(view, piter, text, indentfirst = True, context = None): + text = spaces_instead_of_tabs(view, text) + lines = text.split('\n') + + view.get_buffer().set_data('GeditSnippetsPluginContext', context) + + if len(lines) == 1: + view.get_buffer().insert(piter, text) + else: + # Compute indentation + indent = compute_indentation(view, piter) + text = '' + + for i in range(0, len(lines)): + if indentfirst or i > 0: + text += indent + lines[i] + '\n' + else: + text += lines[i] + '\n' + + view.get_buffer().insert(piter, text[:-1]) + + view.get_buffer().set_data('GeditSnippetsPluginContext', None) + +def get_buffer_context(buf): + return buf.get_data('GeditSnippetsPluginContext') + +def snippets_debug(*s): + return + +def write_xml(node, f, cdata_nodes=()): + assert node is not None + + if not hasattr(f, "write"): + f = open(f, "wb") + + # Encoding + f.write("<?xml version='1.0' encoding='utf-8'?>\n") + + _write_node(node, f, cdata_nodes) + +def _write_indent(file, text, indent): + file.write(' ' * indent + text) + +def _write_node(node, file, cdata_nodes=(), indent=0): + # write XML to file + tag = node.tag + + if node is Comment: + _write_indent(file, "<!-- %s -->\n" % saxutils.escape(node.text.encode('utf-8')), indent) + elif node is ProcessingInstruction: + _write_indent(file, "<?%s?>\n" % saxutils.escape(node.text.encode('utf-8')), indent) + else: + items = node.items() + + if items or node.text or len(node): + _write_indent(file, "<" + tag.encode('utf-8'), indent) + + if items: + items.sort() # lexical order + for k, v in items: + file.write(" %s=%s" % (k.encode('utf-8'), saxutils.quoteattr(v.encode('utf-8')))) + if node.text or len(node): + file.write(">") + if node.text and node.text.strip() != "": + if tag in cdata_nodes: + file.write(_cdata(node.text)) + else: + file.write(saxutils.escape(node.text.encode('utf-8'))) + else: + file.write("\n") + + for n in node: + _write_node(n, file, cdata_nodes, indent + 1) + + if not len(node): + file.write("</" + tag.encode('utf-8') + ">\n") + else: + _write_indent(file, "</" + tag.encode('utf-8') + ">\n", \ + indent) + else: + file.write(" />\n") + + if node.tail and node.tail.strip() != "": + file.write(saxutils.escape(node.tail.encode('utf-8'))) + +def _cdata(text, replace=string.replace): + text = text.encode('utf-8') + return '<![CDATA[' + replace(text, ']]>', ']]]]><![CDATA[>') + ']]>' + +def buffer_word_boundary(buf): + iter = buf.get_iter_at_mark(buf.get_insert()) + start = iter.copy() + + if not iter.starts_word() and (iter.inside_word() or iter.ends_word()): + start.backward_word_start() + + if not iter.ends_word() and iter.inside_word(): + iter.forward_word_end() + + return (start, iter) + +def buffer_line_boundary(buf): + iter = buf.get_iter_at_mark(buf.get_insert()) + start = iter.copy() + start.set_line_offset(0) + + if not iter.ends_line(): + iter.forward_to_line_end() + + return (start, iter) + +def drop_get_uris(selection): + lines = re.split('\\s*[\\n\\r]+\\s*', selection.data.strip()) + result = [] + + for line in lines: + if not line.startswith('#'): + result.append(line) + + return result + +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Importer.py b/plugins/snippets/snippets/Importer.py new file mode 100755 index 00000000..6151027d --- /dev/null +++ b/plugins/snippets/snippets/Importer.py @@ -0,0 +1,100 @@ +import os +import tempfile +import sys +import shutil + +from snippets.Library import * + +class Importer: + def __init__(self, filename): + self.filename = filename + + def import_destination(self, filename): + userdir = Library().userdir + + filename = os.path.basename(filename) + (root, ext) = os.path.splitext(filename) + + filename = os.path.join(userdir, root + ext) + i = 1 + + while os.path.exists(filename): + filename = os.path.join(userdir, root + '_' + str(i) + ext) + i += 1 + + return filename + + def import_file(self, filename): + if not os.path.exists(filename): + return _('File "%s" does not exist') % filename + + if not os.path.isfile(filename): + return _('File "%s" is not a valid snippets file') % filename + + # Find destination for file to copy to + dest = self.import_destination(filename) + + # Copy file + shutil.copy(filename, dest) + + # Add library + if not Library().add_user_library(dest): + return _('Imported file "%s" is not a valid snippets file') % os.path.basename(dest) + + def import_xml(self): + return self.import_file(self.filename) + + def import_archive(self, cmd): + dirname = tempfile.mkdtemp() + status = os.system('cd %s; %s "%s"' % (dirname, cmd, self.filename)) + + if status != 0: + return _('The archive "%s" could not be extracted' % self.filename) + + errors = [] + + # Now import all the files from the archive + for f in os.listdir(dirname): + f = os.path.join(dirname, f) + + if os.path.isfile(f): + if self.import_file(f): + errors.append(os.path.basename(f)) + else: + sys.stderr.write('Skipping %s, not a valid snippets file' % os.path.basename(f)) + + # Remove the temporary directory + shutil.rmtree(dirname) + + if len(errors) > 0: + return _('The following files could not be imported: %s') % ', '.join(errors) + + def import_targz(self): + self.import_archive('tar -x --gzip -f') + + def import_tarbz2(self): + self.import_archive('tar -x --bzip2 -f') + + def import_tar(self): + self.import_archive('tar -xf') + + def run(self): + if not os.path.exists(self.filename): + return _('File "%s" does not exist') % self.filename + + if not os.path.isfile(self.filename): + return _('File "%s" is not a valid snippets archive') % self.filename + + (root, ext) = os.path.splitext(self.filename) + + actions = {'.tar.gz': self.import_targz, + '.tar.bz2': self.import_tarbz2, + '.xml': self.import_xml, + '.tar': self.import_tar} + + for k, v in actions.items(): + if self.filename.endswith(k): + return v() + + return _('File "%s" is not a valid snippets archive') % self.filename +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/LanguageManager.py b/plugins/snippets/snippets/LanguageManager.py new file mode 100755 index 00000000..9646ef17 --- /dev/null +++ b/plugins/snippets/snippets/LanguageManager.py @@ -0,0 +1,21 @@ +import gtksourceview2 as gsv +import os + +from Library import Library + +global manager +manager = None + +def get_language_manager(): + global manager + + if not manager: + dirs = [] + + for d in Library().systemdirs: + dirs.append(os.path.join(d, 'lang')) + + manager = gsv.LanguageManager() + manager.set_search_path(dirs + manager.get_search_path()) + + return manager diff --git a/plugins/snippets/snippets/Library.py b/plugins/snippets/snippets/Library.py new file mode 100755 index 00000000..4717280c --- /dev/null +++ b/plugins/snippets/snippets/Library.py @@ -0,0 +1,993 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import weakref +import sys +import tempfile +import re + +import gtk + +import xml.etree.ElementTree as et +from Helper import * + +class NamespacedId: + def __init__(self, namespace, id): + if not id: + self.id = None + else: + if namespace: + self.id = namespace + '-' + else: + self.id = 'global-' + + self.id += id + +class SnippetData: + PROPS = {'tag': '', 'text': '', 'description': 'New snippet', + 'accelerator': '', 'drop-targets': ''} + + def __init__(self, node, library): + self.priv_id = node.attrib.get('id') + + self.set_library(library) + self.valid = False + self.set_node(node) + + def can_modify(self): + return (self.library and (isinstance(self.library(), SnippetsUserFile))) + + def set_library(self, library): + if library: + self.library = weakref.ref(library) + else: + self.library = None + + self.id = NamespacedId(self.language(), self.priv_id).id + + def set_node(self, node): + if self.can_modify(): + self.node = node + else: + self.node = None + + self.init_snippet_data(node) + + def init_snippet_data(self, node): + if node == None: + return + + self.override = node.attrib.get('override') + + self.properties = {} + props = SnippetData.PROPS.copy() + + # Store all properties present + for child in node: + if child.tag in props: + del props[child.tag] + + # Normalize accelerator + if child.tag == 'accelerator' and child.text != None: + keyval, mod = gtk.accelerator_parse(child.text) + + if gtk.accelerator_valid(keyval, mod): + child.text = gtk.accelerator_name(keyval, mod) + else: + child.text = '' + + if self.can_modify(): + self.properties[child.tag] = child + else: + self.properties[child.tag] = child.text or '' + + # Create all the props that were not found so we stay consistent + for prop in props: + if self.can_modify(): + child = et.SubElement(node, prop) + + child.text = props[prop] + self.properties[prop] = child + else: + self.properties[prop] = props[prop] + + self.check_validation() + + def check_validation(self): + if not self['tag'] and not self['accelerator'] and not self['drop-targets']: + return False + + library = Library() + keyval, mod = gtk.accelerator_parse(self['accelerator']) + + self.valid = library.valid_tab_trigger(self['tag']) and \ + (not self['accelerator'] or library.valid_accelerator(keyval, mod)) + + def _format_prop(self, prop, value): + if prop == 'drop-targets' and value != '': + return re.split('\\s*[,;]\\s*', value) + else: + return value + + def __getitem__(self, prop): + if prop in self.properties: + if self.can_modify(): + return self._format_prop(prop, self.properties[prop].text or '') + else: + return self._format_prop(prop, self.properties[prop] or '') + + return self._format_prop(prop, '') + + def __setitem__(self, prop, value): + if not prop in self.properties: + return + + if isinstance(value, list): + value = ','.join(value) + + if not self.can_modify() and self.properties[prop] != value: + # ohoh, this is not can_modify, but it needs to be changed... + # make sure it is transfered to the changes file and set all the + # fields. + # This snippet data container will effectively become the container + # for the newly created node, but transparently to whoever uses + # it + self._override() + + if self.can_modify() and self.properties[prop].text != value: + if self.library(): + self.library().tainted = True + + oldvalue = self.properties[prop].text + self.properties[prop].text = value + + if prop == 'tag' or prop == 'accelerator' or prop == 'drop-targets': + container = Library().container(self.language()) + container.prop_changed(self, prop, oldvalue) + + self.check_validation() + + def language(self): + if self.library and self.library(): + return self.library().language + else: + return None + + def is_override(self): + return self.override and Library().overridden[self.override] + + def to_xml(self): + return self._create_xml() + + def _create_xml(self, parent=None, update=False, attrib={}): + # Create a new node + if parent != None: + element = et.SubElement(parent, 'snippet', attrib) + else: + element = et.Element('snippet') + + # Create all the properties + for p in self.properties: + prop = et.SubElement(element, p) + prop.text = self[p] + + if update: + self.properties[p] = prop + + return element + + def _override(self): + # Find the user file + target = Library().get_user_library(self.language()) + + # Create a new node there with override + element = self._create_xml(target.root, True, {'override': self.id}) + + # Create an override snippet data, feed it element so that it stores + # all the values and then set the node to None so that it only contains + # the values in .properties + override = SnippetData(element, self.library()) + override.set_node(None) + override.id = self.id + + # Set our node to the new element + self.node = element + + # Set the override to our id + self.override = self.id + self.id = None + + # Set the new library + self.set_library(target) + + # The library is tainted because we added this snippet + target.tainted = True + + # Add the override + Library().overridden[self.override] = override + + def revert(self, snippet): + userlib = self.library() + self.set_library(snippet.library()) + + userlib.remove(self.node) + + self.set_node(None) + + # Copy the properties + self.properties = snippet.properties + + # Set the id + self.id = snippet.id + + # Reset the override flag + self.override = None + +class SnippetsTreeBuilder(et.TreeBuilder): + def __init__(self, start=None, end=None): + et.TreeBuilder.__init__(self) + self.set_start(start) + self.set_end(end) + + def set_start(self, start): + self._start_cb = start + + def set_end(self, end): + self._end_cb = end + + def start(self, tag, attrs): + result = et.TreeBuilder.start(self, tag, attrs) + + if self._start_cb: + self._start_cb(result) + + return result + + def end(self, tag): + result = et.TreeBuilder.end(self, tag) + + if self._end_cb: + self._end_cb(result) + + return result + +class LanguageContainer: + def __init__(self, language): + self.language = language + self.snippets = [] + self.snippets_by_prop = {'tag': {}, 'accelerator': {}, 'drop-targets': {}} + self.accel_group = gtk.AccelGroup() + self._refs = 0 + + def _add_prop(self, snippet, prop, value=0): + if value == 0: + value = snippet[prop] + + if not value or value == '': + return + + snippets_debug('Added ', prop ,' ', value, ' to ', str(self.language)) + + if prop == 'accelerator': + keyval, mod = gtk.accelerator_parse(value) + self.accel_group.connect_group(keyval, mod, 0, \ + Library().accelerator_activated) + + snippets = self.snippets_by_prop[prop] + + if not isinstance(value, list): + value = [value] + + for val in value: + if val in snippets: + snippets[val].append(snippet) + else: + snippets[val] = [snippet] + + def _remove_prop(self, snippet, prop, value=0): + if value == 0: + value = snippet[prop] + + if not value or value == '': + return + + snippets_debug('Removed ', prop, ' ', value, ' from ', str(self.language)) + + if prop == 'accelerator': + keyval, mod = gtk.accelerator_parse(value) + self.accel_group.disconnect_key(keyval, mod) + + snippets = self.snippets_by_prop[prop] + + if not isinstance(value, list): + value = [value] + + for val in value: + try: + snippets[val].remove(snippet) + except: + True + + def append(self, snippet): + tag = snippet['tag'] + accelerator = snippet['accelerator'] + + self.snippets.append(snippet) + + self._add_prop(snippet, 'tag') + self._add_prop(snippet, 'accelerator') + self._add_prop(snippet, 'drop-targets') + + return snippet + + def remove(self, snippet): + try: + self.snippets.remove(snippet) + except: + True + + self._remove_prop(snippet, 'tag') + self._remove_prop(snippet, 'accelerator') + self._remove_prop(snippet, 'drop-targets') + + def prop_changed(self, snippet, prop, oldvalue): + snippets_debug('PROP CHANGED (', prop, ')', oldvalue) + + self._remove_prop(snippet, prop, oldvalue) + self._add_prop(snippet, prop) + + def from_prop(self, prop, value): + snippets = self.snippets_by_prop[prop] + + if prop == 'drop-targets': + s = [] + + # FIXME: change this to use + # matevfs.mime_type_get_equivalence when it comes + # available + for key, val in snippets.items(): + if not value.startswith(key): + continue + + for snippet in snippets[key]: + if not snippet in s: + s.append(snippet) + + return s + else: + if value in snippets: + return snippets[value] + else: + return [] + + def ref(self): + self._refs += 1 + + return True + + def unref(self): + if self._refs > 0: + self._refs -= 1 + + return self._refs != 0 + +class SnippetsSystemFile: + def __init__(self, path=None): + self.path = path + self.loaded = False + self.language = None + self.ok = True + self.need_id = True + + def load_error(self, message): + sys.stderr.write("An error occurred loading " + self.path + ":\n") + sys.stderr.write(message + "\nSnippets in this file will not be " \ + "available, please correct or remove the file.\n") + + def _add_snippet(self, element): + if not self.need_id or element.attrib.get('id'): + self.loading_elements.append(element) + + def set_language(self, element): + self.language = element.attrib.get('language') + + if self.language: + self.language = self.language.lower() + + def _set_root(self, element): + self.set_language(element) + + def _preprocess_element(self, element): + if not self.loaded: + if not element.tag == "snippets": + self.load_error("Root element should be `snippets' instead " \ + "of `%s'" % element.tag) + return False + else: + self._set_root(element) + self.loaded = True + elif element.tag != 'snippet' and not self.insnippet: + self.load_error("Element should be `snippet' instead of `%s'" \ + % element.tag) + return False + else: + self.insnippet = True + + return True + + def _process_element(self, element): + if element.tag == 'snippet': + self._add_snippet(element) + self.insnippet = False + + return True + + def ensure(self): + if not self.ok or self.loaded: + return + + self.load() + + def parse_xml(self, readsize=16384): + if not self.path: + return + + elements = [] + + builder = SnippetsTreeBuilder( \ + lambda node: elements.append((node, True)), \ + lambda node: elements.append((node, False))) + + parser = et.XMLTreeBuilder(target=builder) + self.insnippet = False + + try: + f = open(self.path, "r") + + while True: + data = f.read(readsize) + + if not data: + break + + parser.feed(data) + + for element in elements: + yield element + + del elements[:] + + f.close() + except IOError: + self.ok = False + + def load(self): + if not self.ok: + return + + snippets_debug("Loading library (" + str(self.language) + "): " + \ + self.path) + + self.loaded = False + self.ok = False + self.loading_elements = [] + + for element in self.parse_xml(): + if element[1]: + if not self._preprocess_element(element[0]): + del self.loading_elements[:] + return + else: + if not self._process_element(element[0]): + del self.loading_elements[:] + return + + for element in self.loading_elements: + snippet = Library().add_snippet(self, element) + + del self.loading_elements[:] + self.ok = True + + # This function will get the language for a file by just inspecting the + # root element of the file. This is provided so that a cache can be built + # for which file contains which language. + # It returns the name of the language + def ensure_language(self): + if not self.loaded: + self.ok = False + + for element in self.parse_xml(256): + if element[1]: + if element[0].tag == 'snippets': + self.set_language(element[0]) + self.ok = True + + break + + def unload(self): + snippets_debug("Unloading library (" + str(self.language) + "): " + \ + self.path) + self.language = None + self.loaded = False + self.ok = True + +class SnippetsUserFile(SnippetsSystemFile): + def __init__(self, path=None): + SnippetsSystemFile.__init__(self, path) + self.tainted = False + self.need_id = False + + def _set_root(self, element): + SnippetsSystemFile._set_root(self, element) + self.root = element + + def add_prop(self, node, tag, data): + if data[tag]: + prop = et.SubElement(node, tag) + prop.text = data[tag] + + return prop + else: + return None + + def new_snippet(self, properties=None): + if (not self.ok) or self.root == None: + return None + + element = et.SubElement(self.root, 'snippet') + + if properties: + for prop in properties: + sub = et.SubElement(element, prop) + sub.text = properties[prop] + + self.tainted = True + + return Library().add_snippet(self, element) + + def set_language(self, element): + SnippetsSystemFile.set_language(self, element) + + filename = os.path.basename(self.path).lower() + + if not self.language and filename == "global.xml": + self.modifier = True + elif self.language and filename == self.language + ".xml": + self.modifier = True + else: + self.modifier = False + + def create_root(self, language): + if self.loaded: + snippets_debug('Not creating root, already loaded') + return + + if language: + root = et.Element('snippets', {'language': language}) + self.path = os.path.join(Library().userdir, language.lower() + '.xml') + else: + root = et.Element('snippets') + self.path = os.path.join(Library().userdir, 'global.xml') + + self._set_root(root) + self.loaded = True + self.ok = True + self.tainted = True + self.save() + + def remove(self, element): + try: + self.root.remove(element) + self.tainted = True + except: + return + + try: + first = self.root[0] + except: + # No more elements, this library is useless now + Library().remove_library(self) + + def save(self): + if not self.ok or self.root == None or not self.tainted: + return + + path = os.path.dirname(self.path) + + try: + if not os.path.isdir(path): + os.makedirs(path, 0755) + except OSError: + # TODO: this is bad... + sys.stderr.write("Error in making dirs\n") + + try: + write_xml(self.root, self.path, ('text', 'accelerator')) + self.tainted = False + except IOError: + # Couldn't save, what to do + sys.stderr.write("Could not save user snippets file to " + \ + self.path + "\n") + + def unload(self): + SnippetsSystemFile.unload(self) + self.root = None + +class Singleton(object): + _instance = None + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(Singleton, cls).__new__( + cls, *args, **kwargs) + cls._instance.__init_once__() + + return cls._instance + +class Library(Singleton): + def __init_once__(self): + self._accelerator_activated_cb = None + self.loaded = False + self.check_buffer = gtk.TextBuffer() + + def set_dirs(self, userdir, systemdirs): + self.userdir = userdir + self.systemdirs = systemdirs + + self.libraries = {} + self.containers = {} + self.overridden = {} + self.loaded_ids = [] + + self.loaded = False + + def set_accelerator_callback(self, cb): + self._accelerator_activated_cb = cb + + def accelerator_activated(self, group, obj, keyval, mod): + ret = False + + if self._accelerator_activated_cb: + ret = self._accelerator_activated_cb(group, obj, keyval, mod) + + return ret + + def add_snippet(self, library, element): + container = self.container(library.language) + overrided = self.overrided(library, element) + + if overrided: + overrided.set_library(library) + snippets_debug('Snippet is overriden: ' + overrided['description']) + return None + + snippet = SnippetData(element, library) + + if snippet.id in self.loaded_ids: + snippets_debug('Not added snippet ' + str(library.language) + \ + '::' + snippet['description'] + ' (duplicate)') + return None + + snippet = container.append(snippet) + snippets_debug('Added snippet ' + str(library.language) + '::' + \ + snippet['description']) + + if snippet and snippet.override: + self.add_override(snippet) + + if snippet.id: + self.loaded_ids.append(snippet.id) + + return snippet + + def container(self, language): + language = self.normalize_language(language) + + if not language in self.containers: + self.containers[language] = LanguageContainer(language) + + return self.containers[language] + + def get_user_library(self, language): + target = None + + if language in self.libraries: + for library in self.libraries[language]: + if isinstance(library, SnippetsUserFile) and library.modifier: + target = library + elif not isinstance(library, SnippetsUserFile): + break + + if not target: + # Create a new user file then + snippets_debug('Creating a new user file for language ' + \ + str(language)) + target = SnippetsUserFile() + target.create_root(language) + self.add_library(target) + + return target + + def new_snippet(self, language, properties=None): + language = self.normalize_language(language) + library = self.get_user_library(language) + + return library.new_snippet(properties) + + def revert_snippet(self, snippet): + # This will revert the snippet to the one it overrides + if not snippet.can_modify() or not snippet.override in self.overridden: + # It can't be reverted, shouldn't happen, but oh.. + return + + # The snippet in self.overriden only contains the property contents and + # the library it belongs to + revertto = self.overridden[snippet.override] + del self.overridden[snippet.override] + + if revertto: + snippet.revert(revertto) + + if revertto.id: + self.loaded_ids.append(revertto.id) + + def remove_snippet(self, snippet): + if not snippet.can_modify() or snippet.is_override(): + return + + # Remove from the library + userlib = snippet.library() + userlib.remove(snippet.node) + + # Remove from the container + container = self.containers[userlib.language] + container.remove(snippet) + + def overrided(self, library, element): + id = NamespacedId(library.language, element.attrib.get('id')).id + + if id in self.overridden: + snippet = SnippetData(element, None) + snippet.set_node(None) + + self.overridden[id] = snippet + return snippet + else: + return None + + def add_override(self, snippet): + snippets_debug('Add override:', snippet.override) + if not snippet.override in self.overridden: + self.overridden[snippet.override] = None + + def add_library(self, library): + library.ensure_language() + + if not library.ok: + snippets_debug('Library in wrong format, ignoring') + return False + + snippets_debug('Adding library (' + str(library.language) + '): ' + \ + library.path) + + if library.language in self.libraries: + # Make sure all the user files are before the system files + if isinstance(library, SnippetsUserFile): + self.libraries[library.language].insert(0, library) + else: + self.libraries[library.language].append(library) + else: + self.libraries[library.language] = [library] + + return True + + def remove_library(self, library): + if not library.ok: + return + + if library.path and os.path.isfile(library.path): + os.unlink(library.path) + + try: + self.libraries[library.language].remove(library) + except KeyError: + True + + container = self.containers[library.language] + + for snippet in list(container.snippets): + if snippet.library() == library: + container.remove(snippet) + + def add_user_library(self, path): + library = SnippetsUserFile(path) + return self.add_library(library) + + def add_system_library(self, path): + library = SnippetsSystemFile(path) + return self.add_library(library) + + def find_libraries(self, path, searched, addcb): + snippets_debug("Finding in: " + path) + + if not os.path.isdir(path): + return searched + + files = os.listdir(path) + searched.append(path) + + for f in files: + f = os.path.realpath(os.path.join(path, f)) + + # Determine what language this file provides snippets for + if os.path.isfile(f): + addcb(f) + + return searched + + def normalize_language(self, language): + if language: + return language.lower() + + return language + + def remove_container(self, language): + for snippet in self.containers[language].snippets: + if snippet.id in self.loaded_ids: + self.loaded_ids.remove(snippet.id) + + if snippet.override in self.overridden: + del self.overridden[snippet.override] + + del self.containers[language] + + def get_accel_group(self, language): + language = self.normalize_language(language) + container = self.container(language) + + self.ensure(language) + return container.accel_group + + def save(self, language): + language = self.normalize_language(language) + + if language in self.libraries: + for library in self.libraries[language]: + if isinstance(library, SnippetsUserFile): + library.save() + else: + break + + def ref(self, language): + language = self.normalize_language(language) + + snippets_debug('Ref:', language) + self.container(language).ref() + + def unref(self, language): + language = self.normalize_language(language) + + snippets_debug('Unref:', language) + + if language in self.containers: + if not self.containers[language].unref() and \ + language in self.libraries: + + for library in self.libraries[language]: + library.unload() + + self.remove_container(language) + + def ensure(self, language): + self.ensure_files() + language = self.normalize_language(language) + + # Ensure language as well as the global snippets (None) + for lang in (None, language): + if lang in self.libraries: + # Ensure the container exists + self.container(lang) + + for library in self.libraries[lang]: + library.ensure() + + def ensure_files(self): + if self.loaded: + return + + searched = [] + searched = self.find_libraries(self.userdir, searched, \ + self.add_user_library) + + for d in self.systemdirs: + searched = self.find_libraries(d, searched, \ + self.add_system_library) + + self.loaded = True + + def valid_accelerator(self, keyval, mod): + mod &= gtk.accelerator_get_default_mod_mask() + + return (mod and (gdk.keyval_to_unicode(keyval) or \ + keyval in range(gtk.keysyms.F1, gtk.keysyms.F12 + 1))) + + def valid_tab_trigger(self, trigger): + if not trigger: + return True + + if trigger.isdigit(): + return False + + self.check_buffer.set_text(trigger) + + start, end = self.check_buffer.get_bounds() + text = self.check_buffer.get_text(start, end) + + s = start.copy() + e = end.copy() + + end.backward_word_start() + start.forward_word_end() + + return (s.equal(end) and e.equal(start)) or (len(text) == 1 and not (text.isalnum() or text.isspace())) + + # Snippet getters + # =============== + def _from_prop(self, prop, value, language=None): + self.ensure_files() + + result = [] + language = self.normalize_language(language) + + if not language in self.containers: + return [] + + self.ensure(language) + result = self.containers[language].from_prop(prop, value) + + if len(result) == 0 and language and None in self.containers: + result = self.containers[None].from_prop(prop, value) + + return result + + # Get snippets for a given language + def get_snippets(self, language=None): + self.ensure_files() + language = self.normalize_language(language) + + if not language in self.libraries: + return [] + + snippets = [] + self.ensure(language) + + return list(self.containers[language].snippets) + + # Get snippets for a given accelerator + def from_accelerator(self, accelerator, language=None): + return self._from_prop('accelerator', accelerator, language) + + # Get snippets for a given tag + def from_tag(self, tag, language=None): + return self._from_prop('tag', tag, language) + + # Get snippets for a given drop target + def from_drop_target(self, drop_target, language=None): + return self._from_prop('drop-targets', drop_target, language) + +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Makefile.am b/plugins/snippets/snippets/Makefile.am new file mode 100755 index 00000000..7a05b562 --- /dev/null +++ b/plugins/snippets/snippets/Makefile.am @@ -0,0 +1,28 @@ +# Python snippets plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/snippets + +plugin_PYTHON = \ + __init__.py \ + WindowHelper.py \ + Document.py \ + Library.py \ + Snippet.py \ + Parser.py \ + Placeholder.py \ + Manager.py \ + Helper.py \ + SubstitutionParser.py \ + Importer.py \ + Exporter.py \ + LanguageManager.py \ + Completion.py + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/snippets/ui +ui_DATA = snippets.ui + +EXTRA_DIST = $(ui_DATA) + +CLEANFILES = *.bak *.gladep *.pyc +DISTCLEANFILES = *.bak *.gladep *.pyc + +-include $(top_srcdir)/git.mk diff --git a/plugins/snippets/snippets/Manager.py b/plugins/snippets/snippets/Manager.py new file mode 100755 index 00000000..16acbdb4 --- /dev/null +++ b/plugins/snippets/snippets/Manager.py @@ -0,0 +1,1157 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import tempfile +import shutil + +import gobject +import gtk +from gtk import gdk +import gtksourceview2 as gsv +import pango +import gedit +import gio + +from Snippet import Snippet +from Helper import * +from Library import * +from Importer import * +from Exporter import * +from Document import Document +from LanguageManager import get_language_manager + +class Manager: + NAME_COLUMN = 0 + SORT_COLUMN = 1 + OBJ_COLUMN = 2 + TARGET_URI = 105 + + model = None + drag_icons = ('mate-mime-application-x-tarz', 'mate-package', 'package') + default_export_name = _('Snippets archive') + '.tar.gz' + dragging = False + dnd_target_list = [('text/uri-list', 0, TARGET_URI)] + + def __init__(self, datadir): + self.datadir = datadir + self.snippet = None + self.dlg = None + self._temp_export = None + self.snippets_doc = None + self.manager = None + self.default_size = None + + self.key_press_id = 0 + self.run() + + def get_language_snippets(self, path, name = None): + library = Library() + + name = self.get_language(path) + nodes = library.get_snippets(name) + + return nodes + + def add_new_snippet_node(self, parent): + return self.model.append(parent, ('<i>' + _('Add a new snippet...') + \ + '</i>', '', None)) + + def fill_language(self, piter, expand=True): + # Remove all children + child = self.model.iter_children(piter) + + while child and self.model.remove(child): + True + + path = self.model.get_path(piter) + nodes = self.get_language_snippets(path) + language = self.get_language(path) + + Library().ref(language) + + if nodes: + for node in nodes: + self.add_snippet(piter, node) + else: + # Add node that tells there are no snippets currently + self.add_new_snippet_node(piter) + + if expand: + self.tree_view.expand_row(path, False) + + def build_model(self, force_reload = False): + window = gedit.app_get_default().get_active_window() + + if window: + view = window.get_active_view() + + if not view: + current_lang = None + else: + current_lang = view.get_buffer().get_language() + source_view = self['source_view_snippet'] + + else: + current_lang = None + + tree_view = self['tree_view_snippets'] + expand = None + + if not self.model or force_reload: + self.model = gtk.TreeStore(str, str, object) + self.model.set_sort_column_id(self.SORT_COLUMN, gtk.SORT_ASCENDING) + manager = get_language_manager() + langs = gedit.language_manager_list_languages_sorted(manager, True) + + piter = self.model.append(None, (_('Global'), '', None)) + # Add dummy node + self.model.append(piter, ('', '', None)) + + nm = None + + if current_lang: + nm = current_lang.get_name() + + for lang in langs: + name = lang.get_name() + parent = self.model.append(None, (name, name, lang)) + + # Add dummy node + self.model.append(parent, ('', '', None)) + + if (nm == name): + expand = parent + else: + if current_lang: + piter = self.model.get_iter_first() + nm = current_lang.get_name() + + while piter: + lang = self.model.get_value(piter, \ + self.SORT_COLUMN) + + if lang == nm: + expand = piter + break; + + piter = self.model.iter_next(piter) + + tree_view.set_model(self.model) + + if not expand: + expand = self.model.get_iter_root() + + tree_view.expand_row(self.model.get_path(expand), False) + self.select_iter(expand) + + def get_cell_data_pixbuf_cb(self, column, cell, model, iter): + s = model.get_value(iter, self.OBJ_COLUMN) + + snippet = isinstance(s, SnippetData) + + if snippet and not s.valid: + cell.set_property('stock-id', gtk.STOCK_DIALOG_ERROR) + else: + cell.set_property('stock-id', None) + + cell.set_property('xalign', 1.0) + + def get_cell_data_cb(self, column, cell, model, iter): + s = model.get_value(iter, self.OBJ_COLUMN) + + snippet = isinstance(s, SnippetData) + + cell.set_property('editable', snippet) + cell.set_property('markup', model.get_value(iter, self.NAME_COLUMN)) + + def on_tree_view_drag_data_get(self, widget, context, selection_data, info, time): + gfile = gio.File(self._temp_export) + selection_data.set_uris([gfile.get_uri()]) + + def on_tree_view_drag_begin(self, widget, context): + self.dragging = True + + if self._temp_export: + shutil.rmtree(os.path.dirname(self._temp_export)) + self._temp_export = None + + if self.dnd_name: + context.set_icon_name(self.dnd_name, 0, 0) + + dirname = tempfile.mkdtemp() + filename = os.path.join(dirname, self.default_export_name) + + # Generate temporary file name + self.export_snippets(filename, False) + self._temp_export = filename + + def on_tree_view_drag_end(self, widget, context): + self.dragging = False + + def on_tree_view_drag_data_received(self, widget, context, x, y, selection, info, timestamp): + uris = selection.get_uris() + + self.import_snippets(uris) + + def on_tree_view_drag_motion(self, widget, context, x, y, timestamp): + # Return False if we are dragging + if self.dragging: + return False + + # Check uri target + if not gtk.targets_include_uri(context.targets): + return False + + # Check action + action = None + if context.suggested_action == gdk.ACTION_COPY: + action = gdk.ACTION_COPY + else: + for act in context.actions: + if act == gdk.ACTION_COPY: + action = gdk.ACTION_COPY + break + + if action == gdk.ACTION_COPY: + context.drag_status(gdk.ACTION_COPY, timestamp) + return True + else: + return False + + def build_dnd(self): + tv = self.tree_view + + # Set it as a drag source for exporting snippets + tv.drag_source_set(gdk.BUTTON1_MASK, self.dnd_target_list, gdk.ACTION_DEFAULT | gdk.ACTION_COPY) + + # Set it as a drag destination for importing snippets + tv.drag_dest_set(gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, + self.dnd_target_list, gdk.ACTION_DEFAULT | gdk.ACTION_COPY) + + tv.connect('drag_data_get', self.on_tree_view_drag_data_get) + tv.connect('drag_begin', self.on_tree_view_drag_begin) + tv.connect('drag_end', self.on_tree_view_drag_end) + tv.connect('drag_data_received', self.on_tree_view_drag_data_received) + tv.connect('drag_motion', self.on_tree_view_drag_motion) + + theme = gtk.icon_theme_get_for_screen(tv.get_screen()) + + self.dnd_name = None + for name in self.drag_icons: + icon = theme.lookup_icon(name, gtk.ICON_SIZE_DND, 0) + + if icon: + self.dnd_name = name + break + + def build_tree_view(self): + self.tree_view = self['tree_view_snippets'] + + self.column = gtk.TreeViewColumn(None) + + self.renderer = gtk.CellRendererText() + self.column.pack_start(self.renderer, False) + self.column.set_cell_data_func(self.renderer, self.get_cell_data_cb) + + renderer = gtk.CellRendererPixbuf() + self.column.pack_start(renderer, True) + self.column.set_cell_data_func(renderer, self.get_cell_data_pixbuf_cb) + + self.tree_view.append_column(self.column) + + self.renderer.connect('edited', self.on_cell_edited) + self.renderer.connect('editing-started', self.on_cell_editing_started) + + selection = self.tree_view.get_selection() + selection.set_mode(gtk.SELECTION_MULTIPLE) + selection.connect('changed', self.on_tree_view_selection_changed) + + self.build_dnd() + + def build(self): + self.builder = gtk.Builder() + self.builder.add_from_file(os.path.join(self.datadir, 'ui', 'snippets.ui')) + + handlers_dic = { + 'on_dialog_snippets_response': self.on_dialog_snippets_response, + 'on_dialog_snippets_destroy': self.on_dialog_snippets_destroy, + 'on_button_new_snippet_clicked': self.on_button_new_snippet_clicked, + 'on_button_import_snippets_clicked': self.on_button_import_snippets_clicked, + 'on_button_export_snippets_clicked': self.on_button_export_snippets_clicked, + 'on_button_remove_snippet_clicked': self.on_button_remove_snippet_clicked, + 'on_entry_tab_trigger_focus_out': self.on_entry_tab_trigger_focus_out, + 'on_entry_tab_trigger_changed': self.on_entry_tab_trigger_changed, + 'on_entry_accelerator_focus_out': self.on_entry_accelerator_focus_out, + 'on_entry_accelerator_focus_in': self.on_entry_accelerator_focus_in, + 'on_entry_accelerator_key_press': self.on_entry_accelerator_key_press, + 'on_source_view_snippet_focus_out': self.on_source_view_snippet_focus_out, + 'on_tree_view_snippets_row_expanded': self.on_tree_view_snippets_row_expanded, + 'on_tree_view_snippets_key_press': self.on_tree_view_snippets_key_press} + + self.builder.connect_signals(handlers_dic) + + self.build_tree_view() + self.build_model() + + image = self['image_remove'] + image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_SMALL_TOOLBAR) + + source_view = self['source_view_snippet'] + manager = get_language_manager() + lang = manager.get_language('snippets') + + if lang: + source_view.get_buffer().set_highlight_syntax(True) + source_view.get_buffer().set_language(lang) + self.snippets_doc = Document(None, source_view) + + combo = self['combo_drop_targets'] + combo.set_text_column(0) + + entry = combo.child + entry.connect('focus-out-event', self.on_entry_drop_targets_focus_out) + entry.connect('drag-data-received', self.on_entry_drop_targets_drag_data_received) + + lst = entry.drag_dest_get_target_list() + lst = gtk.target_list_add_uri_targets(entry.drag_dest_get_target_list(), self.TARGET_URI) + entry.drag_dest_set_target_list(lst) + + self.dlg = self['dialog_snippets'] + + if self.default_size: + self.dlg.set_default_size(*self.default_size) + + def __getitem__(self, key): + return self.builder.get_object(key) + + def is_filled(self, piter): + if not self.model.iter_has_child(piter): + return True + + child = self.model.iter_children(piter) + nm = self.model.get_value(child, self.NAME_COLUMN) + obj = self.model.get_value(child, self.OBJ_COLUMN) + + return (obj or nm) + + def fill_if_needed(self, piter, expand=True): + if not self.is_filled(piter): + self.fill_language(piter, expand) + + def find_iter(self, parent, snippet): + self.fill_if_needed(parent) + piter = self.model.iter_children(parent) + + while (piter): + node = self.model.get_value(piter, self.OBJ_COLUMN) + + if node == snippet.data: + return piter + + piter = self.model.iter_next(piter) + + return None + + def selected_snippets_state(self): + snippets = self.selected_snippets(False) + override = False + remove = False + system = False + + for snippet in snippets: + if not snippet: + continue + + if snippet.is_override(): + override = True + elif snippet.can_modify(): + remove = True + else: + system = True + + # No need to continue if both are found + if override and remove: + break + + return (override, remove, system) + + def update_buttons(self): + button_remove = self['button_remove_snippet'] + button_new = self['button_new_snippet'] + image_remove = self['image_remove'] + + button_new.set_sensitive(self.language_path != None) + override, remove, system = self.selected_snippets_state() + + if not (override ^ remove) or system: + button_remove.set_sensitive(False) + image_remove.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON) + else: + button_remove.set_sensitive(True) + + if override: + image_remove.set_from_stock(gtk.STOCK_UNDO, gtk.ICON_SIZE_BUTTON) + tooltip = _('Revert selected snippet') + else: + image_remove.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON) + tooltip = _('Delete selected snippet') + + button_remove.set_tooltip_text(tooltip) + + def snippet_changed(self, piter = None): + if piter: + node = self.model.get_value(piter, self.OBJ_COLUMN) + s = Snippet(node) + else: + s = self.snippet + piter = self.find_iter(self.model.get_iter(self.language_path), s) + + if piter: + nm = s.display() + + self.model.set(piter, self.NAME_COLUMN, nm, self.SORT_COLUMN, nm) + self.update_buttons() + self.entry_tab_trigger_update_valid() + + return piter + + def add_snippet(self, parent, snippet): + piter = self.model.append(parent, ('', '', snippet)) + + return self.snippet_changed(piter) + + def run(self): + if not self.dlg: + self.build() + self.dlg.show() + else: + self.build_model() + self.dlg.present() + + + def snippet_from_iter(self, model, piter): + parent = model.iter_parent(piter) + + if parent: + return model.get_value(piter, self.OBJ_COLUMN) + else: + return None + + def language_snippets(self, model, parent, as_path=False): + self.fill_if_needed(parent, False) + piter = model.iter_children(parent) + snippets = [] + + if not piter: + return snippets + + while piter: + snippet = self.snippet_from_iter(model, piter) + + if snippet: + if as_path: + snippets.append(model.get_path(piter)) + else: + snippets.append(snippet) + + piter = model.iter_next(piter) + + return snippets + + def selected_snippets(self, include_languages=True, as_path=False): + selection = self.tree_view.get_selection() + (model, paths) = selection.get_selected_rows() + snippets = [] + + if paths and len(paths) != 0: + for p in paths: + piter = model.get_iter(p) + parent = model.iter_parent(piter) + + if not piter: + continue + + if parent: + snippet = self.snippet_from_iter(model, piter) + + if not snippet: + continue + + if as_path: + snippets.append(p) + else: + snippets.append(snippet) + elif include_languages: + snippets += self.language_snippets(model, piter, as_path) + + return snippets + + def selected_snippet(self): + selection = self.tree_view.get_selection() + (model, paths) = selection.get_selected_rows() + + if len(paths) == 1: + piter = model.get_iter(paths[0]) + parent = model.iter_parent(piter) + snippet = self.snippet_from_iter(model, piter) + + return parent, piter, snippet + else: + return None, None, None + + def selection_changed(self): + if not self.snippet: + sens = False + + self['entry_tab_trigger'].set_text('') + self['entry_accelerator'].set_text('') + buf = self['source_view_snippet'].get_buffer() + buf.begin_not_undoable_action() + buf.set_text('') + buf.end_not_undoable_action() + self['combo_drop_targets'].child.set_text('') + + else: + sens = True + + self['entry_tab_trigger'].set_text(self.snippet['tag']) + self['entry_accelerator'].set_text( \ + self.snippet.accelerator_display()) + self['combo_drop_targets'].child.set_text(', '.join(self.snippet['drop-targets'])) + + buf = self['source_view_snippet'].get_buffer() + buf.begin_not_undoable_action() + buf.set_text(self.snippet['text']) + buf.end_not_undoable_action() + + + for name in ['source_view_snippet', 'label_tab_trigger', + 'entry_tab_trigger', 'label_accelerator', + 'entry_accelerator', 'label_drop_targets', + 'combo_drop_targets']: + self[name].set_sensitive(sens) + + self.update_buttons() + + def select_iter(self, piter, unselect=True): + selection = self.tree_view.get_selection() + + if unselect: + selection.unselect_all() + + selection.select_iter(piter) + + self.tree_view.scroll_to_cell(self.model.get_path(piter), None, \ + True, 0.5, 0.5) + + def get_language(self, path): + if path[0] == 0: + return None + else: + return self.model.get_value(self.model.get_iter( \ + (path[0],)), self.OBJ_COLUMN).get_id() + + def new_snippet(self, properties=None): + if not self.language_path: + return None + + snippet = Library().new_snippet(self.get_language(self.language_path), properties) + + return Snippet(snippet) + + def get_dummy(self, parent): + if not self.model.iter_n_children(parent) == 1: + return None + + dummy = self.model.iter_children(parent) + + if not self.model.get_value(dummy, self.OBJ_COLUMN): + return dummy + + return None + + def unref_languages(self): + piter = self.model.get_iter_first() + library = Library() + + while piter: + if self.is_filled(piter): + language = self.get_language(self.model.get_path(piter)) + library.save(language) + + library.unref(language) + + piter = self.model.iter_next(piter) + + # Callbacks + def on_dialog_snippets_destroy(self, dlg): + # Remove temporary drag export + if self._temp_export: + shutil.rmtree(os.path.dirname(self._temp_export)) + self._temp_export = None + + if self.snippets_doc: + self.snippets_doc.stop() + + self.default_size = [dlg.allocation.width, dlg.allocation.height] + self.manager = None + + self.unref_languages() + self.snippet = None + self.model = None + self.dlg = None + + def on_dialog_snippets_response(self, dlg, resp): + if resp == gtk.RESPONSE_HELP: + gedit.help_display(self.dlg, 'gedit', 'gedit-snippets-plugin') + return + + self.dlg.destroy() + + def on_cell_editing_started(self, renderer, editable, path): + piter = self.model.get_iter(path) + + if not self.model.iter_parent(piter): + renderer.stop_editing(True) + editable.remove_widget() + elif isinstance(editable, gtk.Entry): + if self.snippet: + editable.set_text(self.snippet['description']) + else: + # This is the `Add a new snippet...` item + editable.set_text('') + + editable.grab_focus() + + def on_cell_edited(self, cell, path, new_text): + if new_text != '': + piter = self.model.get_iter(path) + node = self.model.get_value(piter, self.OBJ_COLUMN) + + if node: + if node == self.snippet.data: + s = self.snippet + else: + s = Snippet(node) + + s['description'] = new_text + self.snippet_changed(piter) + self.select_iter(piter) + else: + # This is the `Add a new snippet...` item + # We create a new snippet + snippet = self.new_snippet({'description': new_text}) + + if snippet: + self.model.set(piter, self.OBJ_COLUMN, snippet.data) + self.snippet_changed(piter) + self.snippet = snippet + self.selection_changed() + + def on_entry_accelerator_focus_out(self, entry, event): + if not self.snippet: + return + + entry.set_text(self.snippet.accelerator_display()) + + def entry_tab_trigger_update_valid(self): + entry = self['entry_tab_trigger'] + text = entry.get_text() + + if text and not Library().valid_tab_trigger(text): + img = self['image_tab_trigger'] + img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_BUTTON) + img.show() + + #self['hbox_tab_trigger'].set_spacing(3) + tip = _('This is not a valid Tab trigger. Triggers can either contain letters or a single (non-alphanumeric) character like: {, [, etc.') + + entry.set_tooltip_text(tip) + img.set_tooltip_text(tip) + else: + self['image_tab_trigger'].hide() + #self['hbox_tab_trigger'].set_spacing(0) + entry.set_tooltip_text(_('Single word the snippet is activated with after pressing Tab')) + + return False + + def on_entry_tab_trigger_focus_out(self, entry, event): + if not self.snippet: + return + + text = entry.get_text() + + # save tag + self.snippet['tag'] = text + self.snippet_changed() + + def on_entry_drop_targets_focus_out(self, entry, event): + if not self.snippet: + return + + text = entry.get_text() + + # save drop targets + self.snippet['drop-targets'] = text + self.snippet_changed() + + def on_entry_tab_trigger_changed(self, entry): + self.entry_tab_trigger_update_valid() + + def on_source_view_snippet_focus_out(self, source_view, event): + if not self.snippet: + return + + buf = source_view.get_buffer() + text = buf.get_text(buf.get_start_iter(), \ + buf.get_end_iter()) + + self.snippet['text'] = text + self.snippet_changed() + + def on_button_new_snippet_clicked(self, button): + snippet = self.new_snippet() + + if not snippet: + return + + parent = self.model.get_iter(self.language_path) + path = self.model.get_path(parent) + + dummy = self.get_dummy(parent) + + if dummy: + # Remove the dummy + self.model.remove(dummy) + + # Add the snippet + piter = self.add_snippet(parent, snippet.data) + self.select_iter(piter) + + if not self.tree_view.row_expanded(path): + self.tree_view.expand_row(path, False) + self.select_iter(piter) + + self.tree_view.grab_focus() + + path = self.model.get_path(piter) + self.tree_view.set_cursor(path, self.column, True) + + def file_filter(self, name, pattern): + fil = gtk.FileFilter() + fil.set_name(name) + + for p in pattern: + fil.add_pattern(p) + + return fil + + def import_snippets(self, filenames): + success = True + + for filename in filenames: + if not gedit.utils.uri_has_file_scheme(filename): + continue + + # Remove file:// + gfile = gio.File(filename) + filename = gfile.get_path() + + importer = Importer(filename) + error = importer.run() + + if error: + message = _('The following error occurred while importing: %s') % error + success = False + message_dialog(self.dlg, gtk.MESSAGE_ERROR, message) + + self.build_model(True) + + if success: + message = _('Import successfully completed') + message_dialog(self.dlg, gtk.MESSAGE_INFO, message) + + def on_import_response(self, dialog, response): + if response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_CLOSE: + dialog.destroy() + return + + f = dialog.get_uris() + dialog.destroy() + + self.import_snippets(f) + + def on_button_import_snippets_clicked(self, button): + dlg = gtk.FileChooserDialog(parent=self.dlg, title=_("Import snippets"), + action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, gtk.RESPONSE_OK)) + + dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar', '*.xml'))) + dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) + dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) + dlg.add_filter(self.file_filter(_('Single snippets file'), ('*.xml',))) + dlg.add_filter(self.file_filter(_('All files'), '*')) + + dlg.connect('response', self.on_import_response) + dlg.set_local_only(True) + + dlg.show() + + def export_snippets_real(self, filename, snippets, show_dialogs=True): + export = Exporter(filename, snippets) + error = export.run() + + if error: + message = _('The following error occurred while exporting: %s') % error + msgtype = gtk.MESSAGE_ERROR + retval = False + else: + message = _('Export successfully completed') + msgtype = gtk.MESSAGE_INFO + retval = True + + if show_dialogs: + message_dialog(self.dlg, msgtype, message) + + return retval + + def on_export_response(self, dialog, response): + filename = dialog.get_filename() + snippets = dialog._export_snippets + + dialog.destroy() + + if response != gtk.RESPONSE_OK: + return + + self.export_snippets_real(filename, snippets); + + def export_snippets(self, filename=None, show_dialogs=True): + snippets = self.selected_snippets() + + if not snippets or len(snippets) == 0: + return False + + usersnippets = [] + systemsnippets = [] + + # Iterate through snippets and look for system snippets + for snippet in snippets: + if snippet.can_modify(): + usersnippets.append(snippet) + else: + systemsnippets.append(snippet) + + export_snippets = snippets + + if len(systemsnippets) != 0 and show_dialogs: + # Ask if system snippets should also be exported + message = _('Do you want to include selected <b>system</b> snippets in your export?') + mes = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, + type=gtk.MESSAGE_QUESTION, + buttons=gtk.BUTTONS_YES_NO, + message_format=message) + mes.set_property('use-markup', True) + resp = mes.run() + mes.destroy() + + if resp == gtk.RESPONSE_NO: + export_snippets = usersnippets + elif resp != gtk.RESPONSE_YES: + return False + + if len(export_snippets) == 0 and show_dialogs: + message = _('There are no snippets selected to be exported') + message_dialog(self.dlg, gtk.MESSAGE_INFORMATION, message) + return False + + if not filename: + dlg = gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK)) + + dlg._export_snippets = export_snippets + dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar'))) + dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) + dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) + + dlg.add_filter(self.file_filter(_('All files'), '*')) + dlg.set_do_overwrite_confirmation(True) + dlg.set_current_name(self.default_export_name) + + dlg.connect('response', self.on_export_response) + dlg.set_local_only(True) + + dlg.show() + return True + else: + return self.export_snippets_real(filename, export_snippets, show_dialogs) + + def on_button_export_snippets_clicked(self, button): + snippets = self.selected_snippets() + + if not snippets or len(snippets) == 0: + return + + usersnippets = [] + systemsnippets = [] + + # Iterate through snippets and look for system snippets + for snippet in snippets: + if snippet.can_modify(): + usersnippets.append(snippet) + else: + systemsnippets.append(snippet) + + dlg = gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK)) + + dlg._export_snippets = snippets + + if len(systemsnippets) != 0: + # Ask if system snippets should also be exported + message = _('Do you want to include selected <b>system</b> snippets in your export?') + mes = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, + type=gtk.MESSAGE_QUESTION, + buttons=gtk.BUTTONS_YES_NO, + message_format=message) + mes.set_property('use-markup', True) + resp = mes.run() + mes.destroy() + + if resp == gtk.RESPONSE_NO: + dlg._export_snippets = usersnippets + elif resp != gtk.RESPONSE_YES: + dlg.destroy() + return + + if len(dlg._export_snippets) == 0: + dlg.destroy() + + message = _('There are no snippets selected to be exported') + message_dialog(self.dlg, gtk.MESSAGE_INFORMATION, message) + return + + dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar'))) + dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) + dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) + + dlg.add_filter(self.file_filter(_('All files'), '*')) + dlg.set_do_overwrite_confirmation(True) + dlg.set_current_name(self.default_export_name) + + dlg.connect('response', self.on_export_response) + dlg.set_local_only(True) + + dlg.show() + + def remove_snippet_revert(self, path, piter): + node = self.snippet_from_iter(self.model, piter) + Library().revert_snippet(node) + + return piter + + def remove_snippet_delete(self, path, piter): + node = self.snippet_from_iter(self.model, piter) + parent = self.model.iter_parent(piter) + + Library().remove_snippet(node) + + if self.model.remove(piter): + return piter + elif path[-1] != 0: + self.select_iter(self.model.get_iter((path[0], path[1] - 1))) + else: + dummy = self.add_new_snippet_node(parent) + self.tree_view.expand_row(self.model.get_path(parent), False) + return dummy + + def on_button_remove_snippet_clicked(self, button): + override, remove, system = self.selected_snippets_state() + + if not (override ^ remove) or system: + return + + paths = self.selected_snippets(include_languages=False, as_path=True) + + if override: + action = self.remove_snippet_revert + else: + action = self.remove_snippet_delete + + # Remove selection + self.tree_view.get_selection().unselect_all() + + # Create tree row references + references = [] + for path in paths: + references.append(gtk.TreeRowReference(self.model, path)) + + # Remove/revert snippets + select = None + for reference in references: + path = reference.get_path() + piter = self.model.get_iter(path) + + res = action(path, piter) + + if res: + select = res + + if select: + self.select_iter(select) + + self.selection_changed() + + def set_accelerator(self, keyval, mod): + accelerator = gtk.accelerator_name(keyval, mod) + self.snippet['accelerator'] = accelerator + + return True + + def on_entry_accelerator_key_press(self, entry, event): + source_view = self['source_view_snippet'] + + if event.keyval == gdk.keyval_from_name('Escape'): + # Reset + entry.set_text(self.snippet.accelerator_display()) + self.tree_view.grab_focus() + + return True + elif event.keyval == gdk.keyval_from_name('Delete') or \ + event.keyval == gdk.keyval_from_name('BackSpace'): + # Remove the accelerator + entry.set_text('') + self.snippet['accelerator'] = '' + self.tree_view.grab_focus() + + self.snippet_changed() + return True + elif Library().valid_accelerator(event.keyval, event.state): + # New accelerator + self.set_accelerator(event.keyval, \ + event.state & gtk.accelerator_get_default_mod_mask()) + entry.set_text(self.snippet.accelerator_display()) + self.snippet_changed() + self.tree_view.grab_focus() + + else: + return True + + def on_entry_accelerator_focus_in(self, entry, event): + if self.snippet['accelerator']: + entry.set_text(_('Type a new shortcut, or press Backspace to clear')) + else: + entry.set_text(_('Type a new shortcut')) + + def update_language_path(self): + model, paths = self.tree_view.get_selection().get_selected_rows() + + # Check if all have the same language parent + current_parent = None + + for path in paths: + piter = model.get_iter(path) + parent = model.iter_parent(piter) + + if parent: + path = model.get_path(parent) + + if current_parent != None and current_parent != path: + current_parent = None + break + else: + current_parent = path + + self.language_path = current_parent + + def on_tree_view_selection_changed(self, selection): + parent, piter, node = self.selected_snippet() + + if self.snippet: + self.on_entry_tab_trigger_focus_out(self['entry_tab_trigger'], + None) + self.on_source_view_snippet_focus_out(self['source_view_snippet'], + None) + self.on_entry_drop_targets_focus_out(self['combo_drop_targets'].child, + None) + + self.update_language_path() + + if node: + self.snippet = Snippet(node) + else: + self.snippet = None + + self.selection_changed() + + def iter_after(self, target, after): + if not after: + return True + + tp = self.model.get_path(target) + ap = self.model.get_path(after) + + if tp[0] > ap[0] or (tp[0] == ap[0] and (len(ap) == 1 or tp[1] > ap[1])): + return True + + return False + + def on_tree_view_snippets_key_press(self, treeview, event): + if event.keyval == gdk.keyval_from_name('Delete'): + self.on_button_remove_snippet_clicked(None) + return True + + def on_tree_view_snippets_row_expanded(self, treeview, piter, path): + # Check if it is already filled + self.fill_if_needed(piter) + self.select_iter(piter) + + def on_entry_drop_targets_drag_data_received(self, entry, context, x, y, selection_data, info, timestamp): + if not gtk.targets_include_uri(context.targets): + return + + uris = drop_get_uris(selection_data) + + if not uris: + return + + if entry.get_text(): + mimes = [entry.get_text()] + else: + mimes = [] + + for uri in uris: + try: + mime = gio.content_type_guess(uri) + except: + mime = None + + if mime: + mimes.append(mime) + + entry.set_text(', '.join(mimes)) + self.on_entry_drop_targets_focus_out(entry, None) + context.finish(True, False, timestamp) + + entry.stop_emission('drag_data_received') +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Parser.py b/plugins/snippets/snippets/Parser.py new file mode 100755 index 00000000..3bbaf6e7 --- /dev/null +++ b/plugins/snippets/snippets/Parser.py @@ -0,0 +1,259 @@ +# Gedit snippets plugin +# Copyright (C) 2006-2007 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import re +import sys +from SubstitutionParser import SubstitutionParser + +class Token: + def __init__(self, klass, data): + self.klass = klass + self.data = data + + def __str__(self): + return '%s: [%s]' % (self.klass, self.data) + + def __eq__(self, other): + return self.klass == other.klass and self.data == other.data + + def __ne__(self, other): + return not self.__eq__(other) + +class Parser: + SREG_ENV = '[A-Z_]+' + SREG_ID = '[0-9]+' + + REG_ESCAPE = re.compile('(\\$(%s|\\(|\\{|<|%s)|`|\\\\)' % (SREG_ENV, SREG_ID)) + + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + self.position = 0 + self.data_length = len(self.data) + + self.RULES = (self._match_env, self._match_regex, self._match_placeholder, self._match_shell, self._match_eval, self._text) + + def remains(self): + return self.data[self.position:] + + def next_char(self): + if self.position + 1 >= self.data_length: + return '' + else: + return self.data[self.position + 1] + + def char(self): + if self.position >= self.data_length: + return '' + else: + return self.data[self.position] + + def token(self): + self.tktext = '' + + while self.position < self.data_length: + try: + # Get first character + func = {'$': self._rule, + '`': self._try_match_shell}[self.char()] + except: + func = self._text + + # Detect end of text token + if func != self._text and self.tktext != '': + return Token('text', self.tktext) + + tk = func() + + if tk: + return tk + + if self.tktext != '': + return Token('text', self.tktext) + + def _need_escape(self): + text = self.remains()[1:] + + if text == '': + return False + + return self.REG_ESCAPE.match(text) + + def _escape(self): + if not self._need_escape(): + return + + # Increase position with 1 + self.position += 1 + + def _text(self): + if self.char() == '\\': + self._escape() + + self.tktext += self.char() + self.position += 1 + + def _rule(self): + for rule in self.RULES: + res = rule() + + if res: + return res + + def _match_env(self): + text = self.remains() + match = re.match('\\$(%s)' % self.SREG_ENV, text) or re.match('\\${(%s)}' % self.SREG_ENV, text) + + if match: + self.position += len(match.group(0)) + return Token('environment', match.group(1)) + + def _parse_list(self, lst): + pos = 0 + length = len(lst) + items = [] + last = None + + while pos < length: + char = lst[pos] + next = pos < length - 1 and lst[pos + 1] + + if char == '\\' and (next == ',' or next == ']'): + char = next + pos += 1 + elif char == ',': + if last != None: + items.append(last) + + last = None + pos += 1 + continue + + last = (last != None and last + char) or char + pos += 1 + + if last != None: + items.append(last) + + return items + + def _parse_default(self, default): + match = re.match('^\\s*(\\\\)?(\\[((\\\\]|[^\\]])+)\\]\\s*)$', default) + + if not match: + return [default] + + groups = match.groups() + + if groups[0]: + return [groups[1]] + + return self._parse_list(groups[2]) + + def _match_placeholder(self): + text = self.remains() + + match = re.match('\\${(%s)(:((\\\\\\}|[^}])+))?}' % self.SREG_ID, text) or re.match('\\$(%s)' % self.SREG_ID, text) + + if not match: + return None + + groups = match.groups() + default = '' + tabstop = int(groups[0]) + self.position += len(match.group(0)) + + if len(groups) > 1 and groups[2]: + default = self._parse_default(groups[2].replace('\\}', '}')) + + return Token('placeholder', {'tabstop': tabstop, 'default': default}) + + def _match_shell(self): + text = self.remains() + match = re.match('`((%s):)?((\\\\`|[^`])+?)`' % self.SREG_ID, text) or re.match('\\$\\(((%s):)?((\\\\\\)|[^\\)])+?)\\)' % self.SREG_ID, text) + + if not match: + return None + + groups = match.groups() + tabstop = (groups[1] and int(groups[1])) or -1 + self.position += len(match.group(0)) + + if text[0] == '`': + contents = groups[2].replace('\\`', '`') + else: + contents = groups[2].replace('\\)', ')') + + return Token('shell', {'tabstop': tabstop, 'contents': contents}) + + def _try_match_shell(self): + return self._match_shell() or self._text() + + def _eval_options(self, options): + reg = re.compile(self.SREG_ID) + tabstop = -1 + depend = [] + + options = options.split(':') + + for opt in options: + if reg.match(opt): + tabstop = int(opt) + else: + depend += self._parse_list(opt[1:-1]) + + return (tabstop, depend) + + def _match_eval(self): + text = self.remains() + + options = '((%s)|\\[([0-9, ]+)\\])' % self.SREG_ID + match = re.match('\\$<((%s:)*)((\\\\>|[^>])+?)>' % options, text) + + if not match: + return None + + groups = match.groups() + (tabstop, depend) = (groups[0] and self._eval_options(groups[0][:-1])) or (-1, []) + self.position += len(match.group(0)) + + return Token('eval', {'tabstop': tabstop, 'dependencies': depend, 'contents': groups[5].replace('\\>', '>')}) + + def _match_regex(self): + text = self.remains() + + content = '((?:\\\\[/]|\\\\}|[^/}])+)' + match = re.match('\\${(?:(%s):)?\\s*(%s|\\$([A-Z_]+))?[/]%s[/]%s(?:[/]([a-zA-Z]*))?}' % (self.SREG_ID, self.SREG_ID, content, content), text) + + if not match: + return None + + groups = match.groups() + tabstop = (groups[0] and int(groups[0])) or -1 + inp = (groups[2] or (groups[1] and int(groups[1]))) or '' + + pattern = re.sub('\\\\([/}])', '\\1', groups[3]) + substitution = re.sub('\\\\([/}])', '\\1', groups[4]) + modifiers = groups[5] or '' + + self.position += len(match.group(0)) + + return Token('regex', {'tabstop': tabstop, 'input': inp, 'pattern': pattern, 'substitution': substitution, 'modifiers': modifiers}) + +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Placeholder.py b/plugins/snippets/snippets/Placeholder.py new file mode 100755 index 00000000..c43eecac --- /dev/null +++ b/plugins/snippets/snippets/Placeholder.py @@ -0,0 +1,700 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import traceback +import re +import os +import sys +import signal +import select +import locale +import subprocess +from SubstitutionParser import SubstitutionParser +import gobject + +from Helper import * + +# These are places in a view where the cursor can go and do things +class Placeholder: + def __init__(self, view, tabstop, defaults, begin): + self.ok = True + self.done = False + self.buf = view.get_buffer() + self.view = view + self.has_references = False + self.mirrors = [] + self.leave_mirrors = [] + self.tabstop = tabstop + self.set_default(defaults) + self.prev_contents = self.default + self.set_mark_gravity() + + if begin: + self.begin = self.buf.create_mark(None, begin, self.mark_gravity[0]) + else: + self.begin = None + + self.end = None + + def __str__(self): + return '%s (%s)' % (str(self.__class__), str(self.default)) + + def set_mark_gravity(self): + self.mark_gravity = [True, False] + + def set_default(self, defaults): + self.default = None + self.defaults = [] + + if not defaults: + return + + for d in defaults: + dm = self.expand_environment(d) + + if dm: + self.defaults.append(dm) + + if not self.default: + self.default = dm + + if dm != d: + break + + + def literal(self, s): + return repr(s) + + def format_environment(self, s): + return s + + def re_environment(self, m): + if m.group(1) or not m.group(2) in os.environ: + return '$' + m.group(2) + else: + return self.format_environment(os.environ[m.group(2)]) + + def expand_environment(self, text): + if not text: + return text + + return re.sub('(\\\\)?\\$([A-Z_]+)', self.re_environment, text) + + def get_iter(self, mark): + if mark and not mark.get_deleted(): + return self.buf.get_iter_at_mark(mark) + else: + return None + + def begin_iter(self): + return self.get_iter(self.begin) + + def end_iter(self): + return self.get_iter(self.end) + + def run_last(self, placeholders): + begin = self.begin_iter() + self.end = self.buf.create_mark(None, begin, self.mark_gravity[1]) + + if self.default: + insert_with_indent(self.view, begin, self.default, False, self) + + def remove(self, force = False): + if self.begin and not self.begin.get_deleted(): + self.buf.delete_mark(self.begin) + + if self.end and not self.end.get_deleted(): + self.buf.delete_mark(self.end) + + # Do something on beginning this placeholder + def enter(self): + if not self.begin or self.begin.get_deleted(): + return + + self.buf.move_mark(self.buf.get_insert(), self.begin_iter()) + + if self.end: + self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter()) + else: + self.buf.move_mark(self.buf.get_selection_bound(), self.begin_iter()) + + def get_text(self): + if self.begin and self.end: + biter = self.begin_iter() + eiter = self.end_iter() + + if biter and eiter: + return self.buf.get_text(self.begin_iter(), self.end_iter()) + else: + return '' + else: + return '' + + def add_mirror(self, mirror, onleave = False): + mirror.has_references = True + + if onleave: + self.leave_mirrors.append(mirror) + else: + self.mirrors.append(mirror) + + def set_text(self, text): + if self.begin.get_deleted() or self.end.get_deleted(): + return + + # Set from self.begin to self.end to text! + self.buf.begin_user_action() + # Remove everything between self.begin and self.end + begin = self.begin_iter() + self.buf.delete(begin, self.end_iter()) + + # Insert the text from the mirror + insert_with_indent(self.view, begin, text, True, self) + self.buf.end_user_action() + + self.update_contents() + + def update_contents(self): + prev = self.prev_contents + self.prev_contents = self.get_text() + + if prev != self.get_text(): + for mirror in self.mirrors: + if not mirror.update(self): + return + + def update_leave_mirrors(self): + # Notify mirrors + for mirror in self.leave_mirrors: + if not mirror.update(self): + return + + # Do something on ending this placeholder + def leave(self): + self.update_leave_mirrors() + + def find_mirrors(self, text, placeholders): + mirrors = [] + + while (True): + m = re.search('(\\\\)?\\$(?:{([0-9]+)}|([0-9]+))', text) + + if not m: + break + + # Skip escaped mirrors + if m.group(1): + text = text[m.end():] + continue + + tabstop = int(m.group(2) or m.group(3)) + + if tabstop in placeholders: + if not tabstop in mirrors: + mirrors.append(tabstop) + + text = text[m.end():] + else: + self.ok = False + return None + + return mirrors + +# This is an placeholder which inserts a mirror of another Placeholder +class PlaceholderMirror(Placeholder): + def __init__(self, view, tabstop, begin): + Placeholder.__init__(self, view, -1, None, begin) + self.mirror_stop = tabstop + + def update(self, mirror): + self.set_text(mirror.get_text()) + return True + + def run_last(self, placeholders): + Placeholder.run_last(self, placeholders) + + if self.mirror_stop in placeholders: + mirror = placeholders[self.mirror_stop] + + mirror.add_mirror(self) + + if mirror.default: + self.set_text(mirror.default) + else: + self.ok = False + +# This placeholder indicates the end of a snippet +class PlaceholderEnd(Placeholder): + def __init__(self, view, begin, default): + Placeholder.__init__(self, view, 0, default, begin) + + def run_last(self, placeholders): + Placeholder.run_last(self, placeholders) + + # Remove the begin mark and set the begin mark + # to the end mark, this is needed so the end placeholder won't contain + # any text + + if not self.default: + self.mark_gravity[0] = False + self.buf.delete_mark(self.begin) + self.begin = self.buf.create_mark(None, self.end_iter(), self.mark_gravity[0]) + + def enter(self): + if self.begin and not self.begin.get_deleted(): + self.buf.move_mark(self.buf.get_insert(), self.begin_iter()) + + if self.end and not self.end.get_deleted(): + self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter()) + + def leave(self): + self.enter() + +# This placeholder is used to expand a command with embedded mirrors +class PlaceholderExpand(Placeholder): + def __init__(self, view, tabstop, begin, s): + Placeholder.__init__(self, view, tabstop, None, begin) + + self.mirror_text = {0: ''} + self.timeout_id = None + self.cmd = s + self.instant_update = False + + def __str__(self): + s = Placeholder.__str__(self) + + return s + ' ' + self.cmd + + def get_mirrors(self, placeholders): + return self.find_mirrors(self.cmd, placeholders) + + # Check if all substitution placeholders are accounted for + def run_last(self, placeholders): + Placeholder.run_last(self, placeholders) + + self.ok = True + mirrors = self.get_mirrors(placeholders) + + if mirrors: + allDefault = True + + for mirror in mirrors: + p = placeholders[mirror] + p.add_mirror(self, not self.instant_update) + self.mirror_text[p.tabstop] = p.default + + if not p.default and not isinstance(p, PlaceholderExpand): + allDefault = False + + if allDefault: + self.update(None) + self.default = self.get_text() or None + else: + self.update(None) + self.default = self.get_text() or None + + if self.tabstop == -1: + self.done = True + + def re_placeholder(self, m, formatter): + if m.group(1): + return '"$' + m.group(2) + '"' + else: + if m.group(3): + index = int(m.group(3)) + else: + index = int(m.group(4)) + + return formatter(self.mirror_text[index]) + + def remove_timeout(self): + if self.timeout_id != None: + gobject.source_remove(self.timeout_id) + self.timeout_id = None + + def install_timeout(self): + self.remove_timeout() + self.timeout_id = gobject.timeout_add(1000, self.timeout_cb) + + def timeout_cb(self): + self.timeout_id = None + + return False + + def format_environment(self, text): + return self.literal(text) + + def substitute(self, text, formatter = None): + formatter = formatter or self.literal + + # substitute all mirrors, but also environmental variables + text = re.sub('(\\\\)?\\$({([0-9]+)}|([0-9]+))', lambda m: self.re_placeholder(m, formatter), + text) + + return self.expand_environment(text) + + def run_update(self): + text = self.substitute(self.cmd) + + if text: + ret = self.expand(text) + + if ret: + self.update_leave_mirrors() + else: + ret = True + + return ret + + def update(self, mirror): + text = None + + if mirror: + self.mirror_text[mirror.tabstop] = mirror.get_text() + + # Check if all substitutions have been made + for tabstop in self.mirror_text: + if tabstop == 0: + continue + + if self.mirror_text[tabstop] == None: + return False + + return self.run_update() + + def expand(self, text): + return True + +# The shell placeholder executes commands in a subshell +class PlaceholderShell(PlaceholderExpand): + def __init__(self, view, tabstop, begin, s): + PlaceholderExpand.__init__(self, view, tabstop, begin, s) + + self.shell = None + self.remove_me = False + + def close_shell(self): + self.shell.stdout.close() + self.shell = None + + def timeout_cb(self): + PlaceholderExpand.timeout_cb(self) + self.remove_timeout() + + if not self.shell: + return False + + gobject.source_remove(self.watch_id) + self.close_shell() + + if self.remove_me: + PlaceholderExpand.remove(self) + + message_dialog(None, gtk.MESSAGE_ERROR, 'Execution of the shell ' \ + 'command (%s) exceeded the maximum time; ' \ + 'execution aborted.' % self.command) + + return False + + def process_close(self): + self.close_shell() + self.remove_timeout() + + self.set_text(str.join('', self.shell_output).rstrip('\n')) + + if self.default == None: + self.default = self.get_text() + self.leave() + + if self.remove_me: + PlaceholderExpand.remove(self, True) + + def process_cb(self, source, condition): + if condition & gobject.IO_IN: + line = source.readline() + + if len(line) > 0: + try: + line = unicode(line, 'utf-8') + except: + line = unicode(line, locale.getdefaultlocale()[1], + 'replace') + + self.shell_output += line + self.install_timeout() + + return True + + self.process_close() + return False + + def literal_replace(self, match): + return "\\%s" % (match.group(0)) + + def literal(self, text): + return '"' + re.sub('([\\\\"])', self.literal_replace, text) + '"' + + def expand(self, text): + self.remove_timeout() + + if self.shell: + gobject.source_remove(self.watch_id) + self.close_shell() + + popen_args = { + 'cwd' : None, + 'shell': True, + 'env' : os.environ, + 'stdout': subprocess.PIPE + } + + self.command = text + self.shell = subprocess.Popen(text, **popen_args) + self.shell_output = '' + self.watch_id = gobject.io_add_watch(self.shell.stdout, gobject.IO_IN | \ + gobject.IO_HUP, self.process_cb) + self.install_timeout() + + return True + + def remove(self, force = False): + if not force and self.shell: + # Still executing shell command + self.remove_me = True + else: + if force: + self.remove_timeout() + + if self.shell: + self.close_shell() + + PlaceholderExpand.remove(self, force) + +class TimeoutError(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +# The python placeholder evaluates commands in python +class PlaceholderEval(PlaceholderExpand): + def __init__(self, view, tabstop, refs, begin, s, namespace): + PlaceholderExpand.__init__(self, view, tabstop, begin, s) + + self.fdread = 0 + self.remove_me = False + self.namespace = namespace + + self.refs = [] + + if refs: + for ref in refs: + self.refs.append(int(ref.strip())) + + def get_mirrors(self, placeholders): + mirrors = PlaceholderExpand.get_mirrors(self, placeholders) + + if not self.ok: + return None + + for ref in self.refs: + if ref in placeholders: + if ref not in mirrors: + mirrors.append(ref) + else: + self.ok = False + return None + + return mirrors + + # SIGALRM is not supported on all platforms (e.g. windows). Timeout + # with SIGALRM will not be used on those platforms. This will + # potentially block gedit if you have a placeholder which gets stuck, + # but it's better than not supporting them at all. At some point we + # might have proper thread support and we can fix this in a better way + def timeout_supported(self): + return hasattr(signal, 'SIGALRM') + + def timeout_cb(self, signum = 0, frame = 0): + raise TimeoutError, "Operation timed out (>2 seconds)" + + def install_timeout(self): + if not self.timeout_supported(): + return + + if self.timeout_id != None: + self.remove_timeout() + + self.timeout_id = signal.signal(signal.SIGALRM, self.timeout_cb) + signal.alarm(2) + + def remove_timeout(self): + if not self.timeout_supported(): + return + + if self.timeout_id != None: + signal.alarm(0) + + signal.signal(signal.SIGALRM, self.timeout_id) + + self.timeout_id = None + + def expand(self, text): + self.remove_timeout() + + text = text.strip() + self.command = text + + if not self.command or self.command == '': + self.set_text('') + return + + text = "def process_snippet():\n\t" + "\n\t".join(text.split("\n")) + + if 'process_snippet' in self.namespace: + del self.namespace['process_snippet'] + + try: + exec text in self.namespace + except: + traceback.print_exc() + + if 'process_snippet' in self.namespace: + try: + # Install a sigalarm signal. This is a HACK to make sure + # gedit doesn't get freezed by someone creating a python + # placeholder which for instance loops indefinately. Since + # the code is executed synchronously it will hang gedit. With + # the alarm signal we raise an exception and catch this + # (see below). We show an error message and return False. + # ___this is a HACK___ and should be fixed properly (I just + # don't know how) + self.install_timeout() + result = self.namespace['process_snippet']() + self.remove_timeout() + except TimeoutError: + self.remove_timeout() + + message_dialog(None, gtk.MESSAGE_ERROR, \ + _('Execution of the Python command (%s) exceeds the maximum ' \ + 'time, execution aborted.') % self.command) + + return False + except Exception, detail: + self.remove_timeout() + + message_dialog(None, gtk.MESSAGE_ERROR, + _('Execution of the Python command (%s) failed: %s') % + (self.command, detail)) + + return False + + if result == None: + # sys.stderr.write("%s:\n>> %s\n" % (_('The following python code, run in a snippet, does not return a value'), "\n>> ".join(self.command.split("\n")))) + result = '' + + self.set_text(str(result)) + + return True + +# Regular expression placeholder +class PlaceholderRegex(PlaceholderExpand): + def __init__(self, view, tabstop, begin, inp, pattern, substitution, modifiers): + PlaceholderExpand.__init__(self, view, tabstop, begin, '') + + self.instant_update = True + self.inp = inp + self.pattern = pattern + self.substitution = substitution + + self.init_modifiers(modifiers) + + def init_modifiers(self, modifiers): + mods = {'I': re.I, + 'L': re.L, + 'M': re.M, + 'S': re.S, + 'U': re.U, + 'X': re.X} + + self.modifiers = 0 + + for modifier in modifiers: + if modifier in mods: + self.modifiers |= mods[modifier] + + def get_mirrors(self, placeholders): + mirrors = self.find_mirrors(self.pattern, placeholders) + self.find_mirrors(self.substitution, placeholders) + + if isinstance(self.inp, int): + if self.inp not in placeholders: + self.ok = False + return None + elif self.inp not in mirrors: + mirrors.append(self.inp) + + return mirrors + + def literal(self, s): + return re.escape(s) + + def get_input(self): + if isinstance(self.inp, int): + return self.mirror_text[self.inp] + elif self.inp in os.environ: + return os.environ[self.inp] + else: + return '' + + def run_update(self): + pattern = self.substitute(self.pattern) + substitution = self.substitute(self.substitution, SubstitutionParser.escape_substitution) + + if pattern: + return self.expand(pattern, substitution) + + return True + + def expand(self, pattern, substitution): + # Try to compile pattern + try: + regex = re.compile(pattern, self.modifiers) + except re.error, message: + sys.stderr.write('Could not compile regular expression: %s\n%s\n' % (pattern, message)) + return False + + inp = self.get_input() + match = regex.search(inp) + + if not match: + self.set_text(inp) + else: + groups = match.groupdict() + + idx = 0 + for group in match.groups(): + groups[str(idx + 1)] = group + idx += 1 + + groups['0'] = match.group(0) + + parser = SubstitutionParser(substitution, groups) + self.set_text(parser.parse()) + + return True +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/Snippet.py b/plugins/snippets/snippets/Snippet.py new file mode 100755 index 00000000..d7baead5 --- /dev/null +++ b/plugins/snippets/snippets/Snippet.py @@ -0,0 +1,355 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import gio + +from Placeholder import * +from Parser import Parser, Token +from Helper import * + +class EvalUtilities: + def __init__(self, view=None): + self.view = view + self._init_namespace() + + def _init_namespace(self): + self.namespace = { + '__builtins__': __builtins__, + 'align': self.util_align, + 'readfile': self.util_readfile, + 'filesize': self.util_filesize + } + + def _real_len(self, s, tablen = 0): + if tablen == 0: + tablen = self.view.get_tab_width() + + return len(s.expandtabs(tablen)) + + def _filename_to_uri(self, filename): + gfile = gio.File(filename) + + return gfile.get_uri() + + def util_readfile(self, filename): + stream = gio.File(filename).read() + + if not stream: + return '' + + res = stream.read() + stream.close() + + return res + + def util_filesize(self, filename): + gfile = gio.File(filename) + info = gfile.query_info(gio.FILE_ATTRIBUTE_STANDARD_SIZE) + + if not info: + return 0 + + return info.get_size() + + def util_align(self, items): + maxlen = [] + tablen = self.view.get_tab_width() + + for row in range(0, len(items)): + for col in range(0, len(items[row]) - 1): + if row == 0: + maxlen.append(0) + + items[row][col] += "\t" + rl = self._real_len(items[row][col], tablen) + + if (rl > maxlen[col]): + maxlen[col] = rl + + result = '' + + for row in range(0, len(items)): + for col in range(0, len(items[row]) - 1): + item = items[row][col] + + result += item + ("\t" * ((maxlen[col] - \ + self._real_len(item, tablen)) / tablen)) + + result += items[row][len(items[row]) - 1] + + if row != len(items) - 1: + result += "\n" + + return result + +class Snippet: + def __init__(self, data): + self.data = data + + def __getitem__(self, prop): + return self.data[prop] + + def __setitem__(self, prop, value): + self.data[prop] = value + + def accelerator_display(self): + accel = self['accelerator'] + + if accel: + keyval, mod = gtk.accelerator_parse(accel) + accel = gtk.accelerator_get_label(keyval, mod) + + return accel or '' + + def display(self): + nm = markup_escape(self['description']) + + tag = self['tag'] + accel = self.accelerator_display() + detail = [] + + if tag and tag != '': + detail.append(tag) + + if accel and accel != '': + detail.append(accel) + + if not detail: + return nm + else: + return nm + ' (<b>' + markup_escape(str.join(', ', detail)) + \ + '</b>)' + + def _add_placeholder(self, placeholder): + if placeholder.tabstop in self.placeholders: + if placeholder.tabstop == -1: + self.placeholders[-1].append(placeholder) + self.plugin_data.ordered_placeholders.append(placeholder) + elif placeholder.tabstop == -1: + self.placeholders[-1] = [placeholder] + self.plugin_data.ordered_placeholders.append(placeholder) + else: + self.placeholders[placeholder.tabstop] = placeholder + self.plugin_data.ordered_placeholders.append(placeholder) + + def _insert_text(self, text): + # Insert text keeping indentation in mind + indented = unicode.join('\n' + unicode(self._indent), spaces_instead_of_tabs(self._view, text).split('\n')) + self._view.get_buffer().insert(self._insert_iter(), indented) + + def _insert_iter(self): + return self._view.get_buffer().get_iter_at_mark(self._insert_mark) + + def _create_environment(self, data): + val = ((data in os.environ) and os.environ[data]) or '' + + # Get all the current indentation + all_indent = compute_indentation(self._view, self._insert_iter()) + + # Substract initial indentation to get the snippet indentation + indent = all_indent[len(self._indent):] + + # Keep indentation + return unicode.join('\n' + unicode(indent), val.split('\n')) + + def _create_placeholder(self, data): + tabstop = data['tabstop'] + begin = self._insert_iter() + + if tabstop == 0: + # End placeholder + return PlaceholderEnd(self._view, begin, data['default']) + elif tabstop in self.placeholders: + # Mirror placeholder + return PlaceholderMirror(self._view, tabstop, begin) + else: + # Default placeholder + return Placeholder(self._view, tabstop, data['default'], begin) + + def _create_shell(self, data): + begin = self._insert_iter() + return PlaceholderShell(self._view, data['tabstop'], begin, data['contents']) + + def _create_eval(self, data): + begin = self._insert_iter() + return PlaceholderEval(self._view, data['tabstop'], data['dependencies'], begin, data['contents'], self._utils.namespace) + + def _create_regex(self, data): + begin = self._insert_iter() + return PlaceholderRegex(self._view, data['tabstop'], begin, data['input'], data['pattern'], data['substitution'], data['modifiers']) + + def _create_text(self, data): + return data + + def _invalid_placeholder(self, placeholder, remove): + buf = self._view.get_buffer() + + # Remove the text because this placeholder is invalid + if placeholder.default and remove: + buf.delete(placeholder.begin_iter(), placeholder.end_iter()) + + placeholder.remove() + + if placeholder.tabstop == -1: + index = self.placeholders[-1].index(placeholder) + del self.placeholders[-1][index] + else: + del self.placeholders[placeholder.tabstop] + + self.plugin_data.ordered_placeholders.remove(placeholder) + + def _parse(self, plugin_data): + # Initialize current variables + self._view = plugin_data.view + self._indent = compute_indentation(self._view, self._view.get_buffer().get_iter_at_mark(self.begin_mark)) + self._utils = EvalUtilities(self._view) + self.placeholders = {} + self._insert_mark = self.end_mark + self.plugin_data = plugin_data + + # Create parser + parser = Parser(data=self['text']) + + # Parse tokens + while (True): + token = parser.token() + + if not token: + break + + try: + val = {'environment': self._create_environment, + 'placeholder': self._create_placeholder, + 'shell': self._create_shell, + 'eval': self._create_eval, + 'regex': self._create_regex, + 'text': self._create_text}[token.klass](token.data) + except: + sys.stderr.write('Token class not supported: %s\n' % token.klass) + continue + + if isinstance(val, basestring): + # Insert text + self._insert_text(val) + else: + # Insert placeholder + self._add_placeholder(val) + + # Create end placeholder if there isn't one yet + if 0 not in self.placeholders: + self.placeholders[0] = PlaceholderEnd(self._view, self.end_iter(), None) + self.plugin_data.ordered_placeholders.append(self.placeholders[0]) + + # Make sure run_last is ran for all placeholders and remove any + # non `ok` placeholders + for tabstop in self.placeholders.copy(): + ph = (tabstop == -1 and list(self.placeholders[-1])) or [self.placeholders[tabstop]] + + for placeholder in ph: + placeholder.run_last(self.placeholders) + + if not placeholder.ok or placeholder.done: + self._invalid_placeholder(placeholder, not placeholder.ok) + + # Remove all the Expand placeholders which have a tabstop because + # they can be used to mirror, but they shouldn't be real tabstops + # (if they have mirrors installed). This is problably a bit of + # a dirty hack :) + if -1 not in self.placeholders: + self.placeholders[-1] = [] + + for tabstop in self.placeholders.copy(): + placeholder = self.placeholders[tabstop] + + if tabstop != -1: + if isinstance(placeholder, PlaceholderExpand) and \ + placeholder.has_references: + # Add to anonymous placeholders + self.placeholders[-1].append(placeholder) + + # Remove placeholder + del self.placeholders[tabstop] + + self.plugin_data = None + + def insert_into(self, plugin_data, insert): + buf = plugin_data.view.get_buffer() + last_index = 0 + + # Find closest mark at current insertion, so that we may insert + # our marks in the correct order + (current, next) = plugin_data.next_placeholder() + + if current: + # Insert AFTER current + last_index = plugin_data.placeholders.index(current) + 1 + elif next: + # Insert BEFORE next + last_index = plugin_data.placeholders.index(next) + else: + # Insert at first position + last_index = 0 + + # lastIndex now contains the position of the last mark + # Create snippet bounding marks + self.begin_mark = buf.create_mark(None, insert, True) + self.end_mark = buf.create_mark(None, insert, False) + + # Now parse the contents of this snippet, create Placeholders + # and insert the placholder marks in the marks array of plugin_data + self._parse(plugin_data) + + # So now all of the snippet is in the buffer, we have all our + # placeholders right here, what's next, put all marks in the + # plugin_data.marks + k = self.placeholders.keys() + k.sort(reverse=True) + + plugin_data.placeholders.insert(last_index, self.placeholders[0]) + last_iter = self.placeholders[0].end_iter() + + for tabstop in k: + if tabstop != -1 and tabstop != 0: + placeholder = self.placeholders[tabstop] + end_iter = placeholder.end_iter() + + if last_iter.compare(end_iter) < 0: + last_iter = end_iter + + # Inserting placeholder + plugin_data.placeholders.insert(last_index, placeholder) + + # Move end mark to last placeholder + buf.move_mark(self.end_mark, last_iter) + + return self + + def deactivate(self): + buf = self.begin_mark.get_buffer() + + buf.delete_mark(self.begin_mark) + buf.delete_mark(self.end_mark) + + self.placeholders = {} + + def begin_iter(self): + return self.begin_mark.get_buffer().get_iter_at_mark(self.begin_mark) + + def end_iter(self): + return self.end_mark.get_buffer().get_iter_at_mark(self.end_mark) +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/SubstitutionParser.py b/plugins/snippets/snippets/SubstitutionParser.py new file mode 100755 index 00000000..6522222b --- /dev/null +++ b/plugins/snippets/snippets/SubstitutionParser.py @@ -0,0 +1,202 @@ +# Gedit snippets plugin +# Copyright (C) 2006-2007 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import re + +class ParseError(Exception): + def __str__(self): + return 'Parse error, resume next' + +class Modifiers: + def _first_char(s): + first = (s != '' and s[0]) or '' + rest = (len(s) > 1 and s[1:]) or '' + + return first, rest + + def upper_first(s): + first, rest = Modifiers._first_char(s) + + return '%s%s' % (first.upper(), rest) + + def upper(s): + return s.upper() + + def lower_first(s): + first, rest = Modifiers._first_char(s) + + return '%s%s' % (first.lower(), rest) + + def lower(s): + return s.lower() + + def title(s): + return s.title() + + upper_first = staticmethod(upper_first) + upper = staticmethod(upper) + lower_first = staticmethod(lower_first) + lower = staticmethod(lower) + title = staticmethod(title) + _first_char = staticmethod(_first_char) + +class SubstitutionParser: + REG_ID = '[0-9]+' + REG_NAME = '[a-zA-Z_]+' + REG_MOD = '[a-zA-Z]+' + REG_ESCAPE = '\\\\|\\(\\?|,|\\)' + + def __init__(self, pattern, groups = {}, modifiers = {}): + self.pattern = pattern + self.groups = groups + + self.REG_GROUP = '(?:(%s)|<(%s|%s)(?:,(%s))?>)' % (self.REG_ID, self.REG_ID, self.REG_NAME, self.REG_MOD) + self.modifiers = {'u': Modifiers.upper_first, + 'U': Modifiers.upper, + 'l': Modifiers.lower_first, + 'L': Modifiers.lower, + 't': Modifiers.title} + + for k, v in modifiers.items(): + self.modifiers[k] = v + + def parse(self): + result, tokens = self._parse(self.pattern, None) + + return result + + def _parse(self, tokens, terminator): + result = '' + + while tokens != '': + if self._peek(tokens) == '' or self._peek(tokens) == terminator: + tokens = self._remains(tokens) + break + + try: + res, tokens = self._expr(tokens, terminator) + except ParseError: + res, tokens = self._text(tokens) + + result += res + + return result, tokens + + def _peek(self, tokens, num = 0): + return (num < len(tokens) and tokens[num]) + + def _token(self, tokens): + if tokens == '': + return '', ''; + + return tokens[0], (len(tokens) > 1 and tokens[1:]) or '' + + def _remains(self, tokens, num = 1): + return (num < len(tokens) and tokens[num:]) or '' + + def _expr(self, tokens, terminator): + if tokens == '': + return '' + + try: + return {'\\': self._escape, + '(': self._condition}[self._peek(tokens)](tokens, terminator) + except KeyError: + raise ParseError + + def _text(self, tokens): + return self._token(tokens) + + def _substitute(self, group, modifiers = ''): + result = (self.groups.has_key(group) and self.groups[group]) or '' + + for modifier in modifiers: + if self.modifiers.has_key(modifier): + result = self.modifiers[modifier](result) + + return result + + def _match_group(self, tokens): + match = re.match('\\\\%s' % self.REG_GROUP, tokens) + + if not match: + return None, tokens + + return self._substitute(match.group(1) or match.group(2), match.group(3) or ''), tokens[match.end():] + + def _escape(self, tokens, terminator): + # Try to match a group + result, tokens = self._match_group(tokens) + + if result != None: + return result, tokens + + s = self.REG_GROUP + + if terminator: + s += '|%s' % re.escape(terminator) + + match = re.match('\\\\(\\\\%s|%s)' % (s, self.REG_ESCAPE), tokens) + + if not match: + raise ParseError + + return match.group(1), tokens[match.end():] + + def _condition_value(self, tokens): + match = re.match('\\\\?%s\s*' % self.REG_GROUP, tokens) + + if not match: + return None, tokens + + groups = match.groups() + name = groups[0] or groups[1] + + return self.groups.has_key(name) and self.groups[name] != None, tokens[match.end():] + + def _condition(self, tokens, terminator): + # Match ? after ( + if self._peek(tokens, 1) != '?': + raise ParseError + + # Remove initial (? token + tokens = self._remains(tokens, 2) + condition, tokens = self._condition_value(tokens) + + if condition == None or self._peek(tokens) != ',': + raise ParseError + + truepart, tokens = self._parse(self._remains(tokens), ',') + + if truepart == None: + raise ParseError + + falsepart, tokens = self._parse(tokens, ')') + + if falsepart == None: + raise ParseError + + if condition: + return truepart, tokens + else: + return falsepart, tokens + + def escape_substitution(substitution): + return re.sub('(%s|%s)' % (self.REG_GROUP, self.REG_ESCAPE), '\\\\\\1', substitution) + + escapesubstitution = staticmethod(escape_substitution) +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/WindowHelper.py b/plugins/snippets/snippets/WindowHelper.py new file mode 100755 index 00000000..29bf3a58 --- /dev/null +++ b/plugins/snippets/snippets/WindowHelper.py @@ -0,0 +1,209 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import re +import os +import gettext + +import gtk +from gtk import gdk +import gedit + +from Document import Document +from Library import Library + +class WindowHelper: + def __init__(self, plugin): + self.plugin = plugin + self.current_controller = None + self.current_language = None + self.signal_ids = {} + + def run(self, window): + self.window = window + + self.insert_menu() + self.register_messages() + + self.accel_group = Library().get_accel_group(None) + + window.add_accel_group(self.accel_group) + window.connect('tab-added', self.on_tab_added) + + # Add controllers to all the current views + for view in self.window.get_views(): + if isinstance(view, gedit.View) and not self.has_controller(view): + view._snippet_controller = Document(self, view) + + self.update() + + def stop(self): + self.window.remove_accel_group(self.accel_group) + self.accel_group = None + + #self.window.remove_accel_group(accel) + self.remove_menu() + self.unregister_messages() + + # Iterate over all the tabs and remove every controller + for view in self.window.get_views(): + if isinstance(view, gedit.View) and self.has_controller(view): + view._snippet_controller.stop() + view._snippet_controller = None + + self.window = None + self.plugin = None + + def register_messages(self): + bus = self.window.get_message_bus() + + self.messages = { + 'activate': bus.register('/plugins/snippets', 'activate', ('view', 'iter'), trigger=str, view=gedit.View, iter=gtk.TextIter), + 'parse-and-activate': bus.register('/plugins/snippets', 'parse-and-activate', ('view', 'iter'), snippet=str, view=gedit.View, iter=gtk.TextIter) + } + + bus.connect('/plugins/snippets', 'activate', self.on_message_activate) + bus.connect('/plugins/snippets', 'parse-and-activate', self.on_message_parse_and_activate) + + def unregister_messages(self): + bus = self.window.get_message_bus() + + for name in self.messages: + bus.unregister(self.messages[name]) + + self.messages = {} + + def on_message_activate(self, bus, message): + if message.has_key('view'): + view = message.view + else: + view = self.window.get_active_view() + + if not self.has_controller(view): + return + + if message.has_key('iter'): + iter = message.iter + else: + iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert()) + + controller = view._snippet_controller + controller.run_snippet_trigger(message.trigger, (iter, iter)) + + def on_message_parse_and_activate(self, bus, message): + if message.has_key('view'): + view = message.view + else: + view = self.window.get_active_view() + + if not self.has_controller(view): + return + + if message.has_key('iter'): + iter = message.iter + else: + iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert()) + + controller = view._snippet_controller + controller.parse_and_run_snippet(message.snippet, iter) + + def insert_menu(self): + manager = self.window.get_ui_manager() + + self.action_group = gtk.ActionGroup("GeditSnippetPluginActions") + self.action_group.set_translation_domain('gedit') + self.action_group.add_actions([('ManageSnippets', None, + _('Manage _Snippets...'), \ + None, _('Manage snippets'), \ + self.on_action_snippets_activate)]) + + self.merge_id = manager.new_merge_id() + manager.insert_action_group(self.action_group, -1) + manager.add_ui(self.merge_id, '/MenuBar/ToolsMenu/ToolsOps_5', \ + 'ManageSnippets', 'ManageSnippets', gtk.UI_MANAGER_MENUITEM, False) + + def remove_menu(self): + manager = self.window.get_ui_manager() + manager.remove_ui(self.merge_id) + manager.remove_action_group(self.action_group) + self.action_group = None + + def find_snippet(self, snippets, tag): + result = [] + + for snippet in snippets: + if Snippet(snippet)['tag'] == tag: + result.append(snippet) + + return result + + def has_controller(self, view): + return hasattr(view, '_snippet_controller') and view._snippet_controller + + def update_language(self): + if not self.window: + return + + if self.current_language: + accel_group = Library().get_accel_group( \ + self.current_language) + self.window.remove_accel_group(accel_group) + + if self.current_controller: + self.current_language = self.current_controller.language_id + + if self.current_language != None: + accel_group = Library().get_accel_group( \ + self.current_language) + self.window.add_accel_group(accel_group) + else: + self.current_language = None + + def language_changed(self, controller): + if controller == self.current_controller: + self.update_language() + + def update(self): + view = self.window.get_active_view() + + if not view or not self.has_controller(view): + return + + controller = view._snippet_controller + + if controller != self.current_controller: + self.current_controller = controller + self.update_language() + + # Callbacks + + def on_tab_added(self, window, tab): + # Create a new controller for this tab if it has a standard gedit view + view = tab.get_view() + + if isinstance(view, gedit.View) and not self.has_controller(view): + view._snippet_controller = Document(self, view) + + self.update() + + def on_action_snippets_activate(self, item): + self.plugin.create_configure_dialog() + + def accelerator_activated(self, keyval, mod): + return self.current_controller.accelerator_activate(keyval, mod) + +# ex:ts=8:et: diff --git a/plugins/snippets/snippets/__init__.py b/plugins/snippets/snippets/__init__.py new file mode 100755 index 00000000..b21da508 --- /dev/null +++ b/plugins/snippets/snippets/__init__.py @@ -0,0 +1,101 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import os +import shutil + +import gtk +from gtk import gdk +import gedit +import platform + +from WindowHelper import WindowHelper +from Library import Library +from Manager import Manager +from Snippet import Snippet + +class SnippetsPlugin(gedit.Plugin): + def __init__(self): + gedit.Plugin.__init__(self) + + self.dlg = None + + library = Library() + library.set_accelerator_callback(self.accelerator_activated) + + if platform.platform() == 'Windows': + snippetsdir = os.path.expanduser('~/gedit/snippets') + else: + userdir = os.getenv('MATE22_USER_DIR') + if userdir: + snippetsdir = os.path.join(userdir, 'gedit/snippets') + else: + snippetsdir = os.path.expanduser('~/.mate2/gedit/snippets') + + library.set_dirs(snippetsdir, self.system_dirs()) + + def system_dirs(self): + if platform.platform() != 'Windows': + if 'XDG_DATA_DIRS' in os.environ: + datadirs = os.environ['XDG_DATA_DIRS'] + else: + datadirs = '/usr/local/share' + os.pathsep + '/usr/share' + + dirs = [] + + for d in datadirs.split(os.pathsep): + d = os.path.join(d, 'gedit-2', 'plugins', 'snippets') + + if os.path.isdir(d): + dirs.append(d) + + dirs.append(self.get_data_dir()) + return dirs + + def activate(self, window): + data = WindowHelper(self) + window._snippets_plugin_data = data + data.run(window) + + def deactivate(self, window): + window._snippets_plugin_data.stop() + window._snippets_plugin_data = None + + def update_ui(self, window): + window._snippets_plugin_data.update() + + def create_configure_dialog(self): + if not self.dlg: + self.dlg = Manager(self.get_data_dir()) + else: + self.dlg.run() + + window = gedit.app_get_default().get_active_window() + + if window: + self.dlg.dlg.set_transient_for(window) + + return self.dlg.dlg + + def accelerator_activated(self, group, obj, keyval, mod): + ret = False + + if hasattr(obj, '_snippets_plugin_data'): + ret = obj._snippets_plugin_data.accelerator_activated(keyval, mod) + + return ret diff --git a/plugins/snippets/snippets/snippets.ui b/plugins/snippets/snippets/snippets.ui new file mode 100755 index 00000000..426df886 --- /dev/null +++ b/plugins/snippets/snippets/snippets.ui @@ -0,0 +1,647 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <object class="GtkListStore" id="model1"> + <columns> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0">text</col> + </row> + <row> + <col id="0">text/plain</col> + </row> + <row> + <col id="0">text/xml</col> + </row> + <row> + <col id="0">image</col> + </row> + <row> + <col id="0">image/png</col> + </row> + <row> + <col id="0">image/jpeg</col> + </row> + <row> + <col id="0">audio</col> + </row> + <row> + <col id="0">video</col> + </row> + </data> + </object> + <object class="GeditDocument" id="source_buffer"> + <property name="highlight-matching-brackets">True</property> + </object> + <object class="GtkDialog" id="dialog_snippets"> + <property name="title" translatable="yes">Snippets Manager</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="default_width">750</property> + <property name="default_height">500</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">True</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">True</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> + <signal handler="on_dialog_snippets_response" last_modification_time="Mon, 19 Dec 2005 11:20:00 GMT" name="response"/> + <signal handler="on_dialog_snippets_destroy" last_modification_time="Sun, 22 Jun 2008 13:22:00 GMT" name="destroy"/> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox1"> + <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_area1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <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-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="button1"> + <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> + <property name="focus_on_click">True</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="GtkHPaned" id="hpaned_paned"> + <property name="border_width">6</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="position">275</property> + <child> + <object class="GtkVBox" id="vbox_selection"> + <property name="width_request">230</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Snippets:</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">tree_view_snippets</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolled_window_snippets"> + <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_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + <child> + <object class="GtkTreeView" id="tree_view_snippets"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">True</property> + <property name="fixed_height_mode">False</property> + <property name="hover_selection">False</property> + <property name="hover_expand">False</property> + <signal handler="on_tree_view_snippets_row_expanded" last_modification_time="Tue, 03 Jan 2006 22:06:02 GMT" name="row_expanded"/> + <signal handler="on_tree_view_snippets_key_press" last_modification_time="Tue, 03 Jan 2006 22:07:00 GMT" name="key_press_event"/> + </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="hbox_buttons"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton" id="button_new_snippet"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Create new snippet</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <signal handler="on_button_new_snippet_clicked" last_modification_time="Tue, 20 Dec 2005 19:50:58 GMT" name="clicked"/> + <child> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="stock">gtk-new</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button_import_snippets"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Import snippets</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <signal handler="on_button_import_snippets_clicked" last_modification_time="Tue, 10 Jul 2007 18:37:11 GMT" name="clicked"/> + <child> + <object class="GtkImage" id="image5"> + <property name="visible">True</property> + <property name="stock">gtk-open</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button_export_snippets"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Export selected snippets</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <signal handler="on_button_export_snippets_clicked" last_modification_time="Tue, 10 Jul 2007 18:37:25 GMT" name="clicked"/> + <child> + <object class="GtkImage" id="image4"> + <property name="visible">True</property> + <property name="stock">gtk-save</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button_remove_snippet"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="tooltip-text" translatable="yes">Delete selected snippet</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <signal handler="on_button_remove_snippet_clicked" last_modification_time="Mon, 19 Dec 2005 13:15:14 GMT" name="clicked"/> + <child> + <object class="GtkImage" id="image_remove"> + <property name="visible">True</property> + <property name="stock">gtk-delete</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</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> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="shrink">False</property> + <property name="resize">False</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox_snippet"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Edit:</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolled_window_snippet"> + <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_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + <child> + <object class="GeditView" id="source_view_snippet"> + <property name="buffer">source_buffer</property> + <property name="visible">True</property> + <property name="auto-indent">True</property> + <property name="insert-spaces-instead-of-tabs">False</property> + <property name="smart-home-end">GTK_SOURCE_SMART_HOME_END_AFTER</property> + <property name="tab-width">2</property> + <property name="highlight-current-line">True</property> + <property name="show-right-margin">False</property> + <property name="show-line-numbers">False</property> + + <signal handler="on_source_view_snippet_focus_out" last_modification_time="Sat, 07 Jan 2006 17:13:24 GMT" name="focus_out_event"/> + </object> + </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> + <child> + <object class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Activation</property> + <property name="use_underline">False</property> + <property name="use_markup">True</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> + <attributes> + <attribute name="weight" value="PANGO_WEIGHT_BOLD"/> + </attributes> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes"> </property> + <property name="use_underline">False</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.5</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">3</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="label_tab_trigger"> + <property name="visible">True</property> + <property comments=""tab" here means the tab key, not the notebook tab!" name="label" translatable="yes">_Tab trigger:</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">entry_tab_trigger</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="GtkHBox" id="hbox_tab_trigger"> + <property name="visible">True</property> + <child> + <object class="GtkEntry" id="entry_tab_trigger"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="tooltip-text" translatable="yes">Single word the snippet is activated with after pressing Tab</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"/> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + <signal handler="on_entry_tab_trigger_focus_out" last_modification_time="Wed, 04 Jan 2006 14:07:29 GMT" name="focus_out_event"/> + <signal handler="on_entry_tab_trigger_changed" last_modification_time="Fri, 28 Apr 2006 16:50:34 GMT" name="changed"/> + </object> + <packing> + <property name="expand">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkImage" id="image_tab_trigger"> + <property name="visible">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + <property name="padding">3</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkEntry" id="entry_accelerator"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="tooltip-text" translatable="yes">Shortcut key with which the snippet is activated</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"/> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + <signal handler="on_entry_accelerator_focus_out" last_modification_time="Wed, 04 Jan 2006 14:07:20 GMT" name="focus_out_event"/> + <signal handler="on_entry_accelerator_key_press" last_modification_time="Wed, 04 Jan 2006 14:07:23 GMT" name="key_press_event"/> + <signal handler="on_entry_accelerator_focus_in" last_modification_time="Wed, 04 Jan 2006 14:09:06 GMT" name="focus_in_event"/> + </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="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label_accelerator"> + <property name="visible">True</property> + <property name="label" translatable="yes">S_hortcut key:</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">entry_accelerator</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> + <child> + <object class="GtkLabel" id="label_drop_targets"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Drop targets:</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">entry_accelerator</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">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkComboBoxEntry" id="combo_drop_targets"> + <property name="visible">True</property> + <property name="add_tearoffs">False</property> + <property name="has_frame">True</property> + <property name="focus_on_click">True</property> + <property name="model">model1</property> + <child> + <object class="GtkCellRendererText" id="renderer1"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </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">fill</property> + <property name="y_options">fill</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> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="shrink">True</property> + <property name="resize">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="-7">closebutton1</action-widget> + <action-widget response="-11">button1</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/sort/Makefile.am b/plugins/sort/Makefile.am new file mode 100755 index 00000000..8fa39143 --- /dev/null +++ b/plugins/sort/Makefile.am @@ -0,0 +1,34 @@ +# sort plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libsort.la + +libsort_la_SOURCES = \ + gedit-sort-plugin.h \ + gedit-sort-plugin.c + +libsort_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libsort_la_LIBADD = $(GEDIT_LIBS) + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/sort +ui_DATA = sort.ui + +plugin_in_files = sort.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(ui_DATA) $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/sort/gedit-sort-plugin.c b/plugins/sort/gedit-sort-plugin.c new file mode 100755 index 00000000..8fc6c959 --- /dev/null +++ b/plugins/sort/gedit-sort-plugin.c @@ -0,0 +1,588 @@ +/* + * gedit-sort-plugin.c + * + * Original author: Carlo Borreo <[email protected]> + * Ported to Gedit2 by Lee Mallabone <[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, 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$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-sort-plugin.h" + +#include <string.h> +#include <glib/gi18n-lib.h> +#include <gmodule.h> + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-help.h> + +#define GEDIT_SORT_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginPrivate)) + +/* Key in case the plugin ever needs any settings. */ +#define SORT_BASE_KEY "/apps/gedit-2/plugins/sort" + +#define WINDOW_DATA_KEY "GeditSortPluginWindowData" +#define MENU_PATH "/MenuBar/EditMenu/EditOps_6" + +GEDIT_PLUGIN_REGISTER_TYPE(GeditSortPlugin, gedit_sort_plugin) + +typedef struct +{ + GtkWidget *dialog; + GtkWidget *col_num_spinbutton; + GtkWidget *reverse_order_checkbutton; + GtkWidget *ignore_case_checkbutton; + GtkWidget *remove_dups_checkbutton; + + GeditDocument *doc; + + GtkTextIter start, end; /* selection */ +} SortDialog; + +typedef struct +{ + GtkActionGroup *ui_action_group; + guint ui_id; +} WindowData; + +typedef struct +{ + GeditPlugin *plugin; + GeditWindow *window; +} ActionData; + +typedef struct +{ + gboolean ignore_case; + gboolean reverse_order; + gboolean remove_duplicates; + gint starting_column; +} SortInfo; + +static void sort_cb (GtkAction *action, ActionData *action_data); +static void sort_real (SortDialog *dialog); + +static const GtkActionEntry action_entries[] = +{ + { "Sort", + GTK_STOCK_SORT_ASCENDING, + N_("S_ort..."), + NULL, + N_("Sort the current document or selection"), + G_CALLBACK (sort_cb) } +}; + +static void +sort_dialog_destroy (GtkObject *obj, + gpointer dialog_pointer) +{ + gedit_debug (DEBUG_PLUGINS); + + g_slice_free (SortDialog, dialog_pointer); +} + +static void +sort_dialog_response_handler (GtkDialog *widget, + gint res_id, + SortDialog *dialog) +{ + gedit_debug (DEBUG_PLUGINS); + + switch (res_id) + { + case GTK_RESPONSE_OK: + sort_real (dialog); + gtk_widget_destroy (dialog->dialog); + break; + + case GTK_RESPONSE_HELP: + gedit_help_display (GTK_WINDOW (widget), + NULL, + "gedit-sort-plugin"); + break; + + case GTK_RESPONSE_CANCEL: + gtk_widget_destroy (dialog->dialog); + break; + } +} + +/* NOTE: we store the current selection in the dialog since focusing + * the text field (like the combo box) looses the documnent selection. + * Storing the selection ONLY works because the dialog is modal */ +static void +get_current_selection (GeditWindow *window, SortDialog *dialog) +{ + GeditDocument *doc; + + gedit_debug (DEBUG_PLUGINS); + + doc = gedit_window_get_active_document (window); + + if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), + &dialog->start, + &dialog->end)) + { + /* No selection, get the whole document. */ + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), + &dialog->start, + &dialog->end); + } +} + +static SortDialog * +get_sort_dialog (ActionData *action_data) +{ + SortDialog *dialog; + GtkWidget *error_widget; + gboolean ret; + gchar *data_dir; + gchar *ui_file; + + gedit_debug (DEBUG_PLUGINS); + + dialog = g_slice_new (SortDialog); + + data_dir = gedit_plugin_get_data_dir (action_data->plugin); + ui_file = g_build_filename (data_dir, "sort.ui", NULL); + g_free (data_dir); + ret = gedit_utils_get_ui_objects (ui_file, + NULL, + &error_widget, + "sort_dialog", &dialog->dialog, + "reverse_order_checkbutton", &dialog->reverse_order_checkbutton, + "col_num_spinbutton", &dialog->col_num_spinbutton, + "ignore_case_checkbutton", &dialog->ignore_case_checkbutton, + "remove_dups_checkbutton", &dialog->remove_dups_checkbutton, + NULL); + g_free (ui_file); + + if (!ret) + { + const gchar *err_message; + + err_message = gtk_label_get_label (GTK_LABEL (error_widget)); + gedit_warning (GTK_WINDOW (action_data->window), + "%s", err_message); + + g_free (dialog); + gtk_widget_destroy (error_widget); + + return NULL; + } + + gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog), + GTK_RESPONSE_OK); + + g_signal_connect (dialog->dialog, + "destroy", + G_CALLBACK (sort_dialog_destroy), + dialog); + + g_signal_connect (dialog->dialog, + "response", + G_CALLBACK (sort_dialog_response_handler), + dialog); + + get_current_selection (action_data->window, dialog); + + return dialog; +} + +static void +sort_cb (GtkAction *action, + ActionData *action_data) +{ + GeditDocument *doc; + GtkWindowGroup *wg; + SortDialog *dialog; + + gedit_debug (DEBUG_PLUGINS); + + doc = gedit_window_get_active_document (action_data->window); + g_return_if_fail (doc != NULL); + + dialog = get_sort_dialog (action_data); + g_return_if_fail (dialog != NULL); + + wg = gedit_window_get_group (action_data->window); + gtk_window_group_add_window (wg, + GTK_WINDOW (dialog->dialog)); + + dialog->doc = doc; + + gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), + GTK_WINDOW (action_data->window)); + + gtk_window_set_modal (GTK_WINDOW (dialog->dialog), + TRUE); + + gtk_widget_show (GTK_WIDGET (dialog->dialog)); +} + +/* Compares two strings for the sorting algorithm. Uses the UTF-8 processing + * functions in GLib to be as correct as possible.*/ +static gint +compare_algorithm (gconstpointer s1, + gconstpointer s2, + gpointer data) +{ + gint length1, length2; + gint ret; + gchar *string1, *string2; + gchar *substring1, *substring2; + gchar *key1, *key2; + SortInfo *sort_info; + + gedit_debug (DEBUG_PLUGINS); + + sort_info = (SortInfo *) data; + g_return_val_if_fail (sort_info != NULL, -1); + + if (!sort_info->ignore_case) + { + string1 = *((gchar **) s1); + string2 = *((gchar **) s2); + } + else + { + string1 = g_utf8_casefold (*((gchar **) s1), -1); + string2 = g_utf8_casefold (*((gchar **) s2), -1); + } + + length1 = g_utf8_strlen (string1, -1); + length2 = g_utf8_strlen (string2, -1); + + if ((length1 < sort_info->starting_column) && + (length2 < sort_info->starting_column)) + { + ret = 0; + } + else if (length1 < sort_info->starting_column) + { + ret = -1; + } + else if (length2 < sort_info->starting_column) + { + ret = 1; + } + else if (sort_info->starting_column < 1) + { + key1 = g_utf8_collate_key (string1, -1); + key2 = g_utf8_collate_key (string2, -1); + ret = strcmp (key1, key2); + + g_free (key1); + g_free (key2); + } + else + { + /* A character column offset is required, so figure out + * the correct offset into the UTF-8 string. */ + substring1 = g_utf8_offset_to_pointer (string1, sort_info->starting_column); + substring2 = g_utf8_offset_to_pointer (string2, sort_info->starting_column); + + key1 = g_utf8_collate_key (substring1, -1); + key2 = g_utf8_collate_key (substring2, -1); + ret = strcmp (key1, key2); + + g_free (key1); + g_free (key2); + } + + /* Do the necessary cleanup. */ + if (sort_info->ignore_case) + { + g_free (string1); + g_free (string2); + } + + if (sort_info->reverse_order) + { + ret = -1 * ret; + } + + return ret; +} + +static gchar * +get_line_slice (GtkTextBuffer *buf, + gint line) +{ + GtkTextIter start, end; + char *ret; + + gtk_text_buffer_get_iter_at_line (buf, &start, line); + end = start; + + if (!gtk_text_iter_ends_line (&start)) + gtk_text_iter_forward_to_line_end (&end); + + ret= gtk_text_buffer_get_slice (buf, + &start, + &end, + TRUE); + + g_assert (ret != NULL); + + return ret; +} + +static void +sort_real (SortDialog *dialog) +{ + GeditDocument *doc; + GtkTextIter start, end; + gint start_line, end_line; + gint i; + gchar *last_row = NULL; + gint num_lines; + gchar **lines; + SortInfo *sort_info; + + gedit_debug (DEBUG_PLUGINS); + + doc = dialog->doc; + g_return_if_fail (doc != NULL); + + sort_info = g_new0 (SortInfo, 1); + sort_info->ignore_case = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->ignore_case_checkbutton)); + sort_info->reverse_order = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->reverse_order_checkbutton)); + sort_info->remove_duplicates = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->remove_dups_checkbutton)); + sort_info->starting_column = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (dialog->col_num_spinbutton)) - 1; + + start = dialog->start; + end = dialog->end; + start_line = gtk_text_iter_get_line (&start); + end_line = gtk_text_iter_get_line (&end); + + /* if we are at line start our last line is the previus one. + * Otherwise the last line is the current one but we try to + * move the iter after the line terminator */ + if (gtk_text_iter_get_line_offset (&end) == 0) + end_line = MAX (start_line, end_line - 1); + else + gtk_text_iter_forward_line (&end); + + num_lines = end_line - start_line + 1; + lines = g_new0 (gchar *, num_lines + 1); + + gedit_debug_message (DEBUG_PLUGINS, "Building list..."); + + for (i = 0; i < num_lines; i++) + { + lines[i] = get_line_slice (GTK_TEXT_BUFFER (doc), start_line + i); + } + + lines[num_lines] = NULL; + + gedit_debug_message (DEBUG_PLUGINS, "Sort list..."); + + g_qsort_with_data (lines, + num_lines, + sizeof (gpointer), + compare_algorithm, + sort_info); + + gedit_debug_message (DEBUG_PLUGINS, "Rebuilding document..."); + + gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (doc)); + + gtk_text_buffer_delete (GTK_TEXT_BUFFER (doc), + &start, + &end); + + for (i = 0; i < num_lines; i++) + { + if (sort_info->remove_duplicates && + last_row != NULL && + (strcmp (last_row, lines[i]) == 0)) + continue; + + gtk_text_buffer_insert (GTK_TEXT_BUFFER (doc), + &start, + lines[i], + -1); + gtk_text_buffer_insert (GTK_TEXT_BUFFER (doc), + &start, + "\n", + -1); + + last_row = lines[i]; + } + + gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (doc)); + + g_strfreev (lines); + g_free (sort_info); + + gedit_debug_message (DEBUG_PLUGINS, "Done."); +} + +static void +free_window_data (WindowData *data) +{ + g_return_if_fail (data != NULL); + + g_object_unref (data->ui_action_group); + g_slice_free (WindowData, data); +} + +static void +free_action_data (ActionData *data) +{ + g_return_if_fail (data != NULL); + + g_slice_free (ActionData, data); +} + +static void +update_ui_real (GeditWindow *window, + WindowData *data) +{ + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (window); + + gtk_action_group_set_sensitive (data->ui_action_group, + (view != NULL) && + gtk_text_view_get_editable (GTK_TEXT_VIEW (view))); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + ActionData *action_data; + + gedit_debug (DEBUG_PLUGINS); + + data = g_slice_new (WindowData); + action_data = g_slice_new (ActionData); + action_data->window = window; + action_data->plugin = plugin; + + manager = gedit_window_get_ui_manager (window); + + data->ui_action_group = gtk_action_group_new ("GeditSortPluginActions"); + gtk_action_group_set_translation_domain (data->ui_action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions_full (data->ui_action_group, + action_entries, + G_N_ELEMENTS (action_entries), + action_data, + (GDestroyNotify) free_action_data); + + gtk_ui_manager_insert_action_group (manager, + data->ui_action_group, + -1); + + data->ui_id = gtk_ui_manager_new_merge_id (manager); + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + (GDestroyNotify) free_window_data); + + gtk_ui_manager_add_ui (manager, + data->ui_id, + MENU_PATH, + "Sort", + "Sort", + GTK_UI_MANAGER_MENUITEM, + FALSE); + + update_ui_real (window, + data); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + manager = gedit_window_get_ui_manager (window); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + gtk_ui_manager_remove_ui (manager, + data->ui_id); + gtk_ui_manager_remove_action_group (manager, + data->ui_action_group); + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + update_ui_real (window, + data); +} + +static void +gedit_sort_plugin_init (GeditSortPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin initializing"); +} + +static void +gedit_sort_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin finalizing"); + + G_OBJECT_CLASS (gedit_sort_plugin_parent_class)->finalize (object); +} + +static void +gedit_sort_plugin_class_init (GeditSortPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_sort_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; +} diff --git a/plugins/sort/gedit-sort-plugin.h b/plugins/sort/gedit-sort-plugin.h new file mode 100755 index 00000000..c10280bf --- /dev/null +++ b/plugins/sort/gedit-sort-plugin.h @@ -0,0 +1,73 @@ +/* + * gedit-sort-plugin.h + * + * 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, 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$ + */ + +#ifndef __GEDIT_SORT_PLUGIN_H__ +#define __GEDIT_SORT_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_SORT_PLUGIN (gedit_sort_plugin_get_type ()) +#define GEDIT_SORT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_SORT_PLUGIN, GeditSortPlugin)) +#define GEDIT_SORT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginClass)) +#define GEDIT_IS_SORT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_SORT_PLUGIN)) +#define GEDIT_IS_SORT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_SORT_PLUGIN)) +#define GEDIT_SORT_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginClass)) + +/* Private structure type */ +typedef struct _GeditSortPluginPrivate GeditSortPluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditSortPlugin GeditSortPlugin; + +struct _GeditSortPlugin +{ + GeditPlugin parent_instance; +}; + +/* + * Class definition + */ +typedef struct _GeditSortPluginClass GeditSortPluginClass; + +struct _GeditSortPluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_sort_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_SORT_PLUGIN_H__ */ diff --git a/plugins/sort/sort.gedit-plugin.desktop.in b/plugins/sort/sort.gedit-plugin.desktop.in new file mode 100755 index 00000000..9ee9634a --- /dev/null +++ b/plugins/sort/sort.gedit-plugin.desktop.in @@ -0,0 +1,9 @@ +[Gedit Plugin] +Module=sort +IAge=2 +_Name=Sort +_Description=Sorts a document or selected text. +Icon=gtk-sort-ascending +Authors=Carlo Borreo <[email protected]>;Lee Mallabone <[email protected]>;Paolo Maggi <[email protected]>;Jorge Alberto Torres H. <[email protected]> +Copyright=Copyright © 2001 Carlo Borreo\nCopyright © 2002-2003 Lee Mallabone, Paolo Maggi\nCopyright © 2004-2005 Paolo Maggi +Website=http://www.gedit.org diff --git a/plugins/sort/sort.ui b/plugins/sort/sort.ui new file mode 100755 index 00000000..4d1fe091 --- /dev/null +++ b/plugins/sort/sort.ui @@ -0,0 +1,275 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <object class="GtkAdjustment" id="adjustment1"> + <property name="upper">100</property> + <property name="lower">1</property> + <property name="page_increment">10</property> + <property name="step_increment">1</property> + <property name="page_size">0</property> + <property name="value">1</property> + </object> + <object class="GtkImage" id="sort_image"> + <property name="stock">gtk-sort-ascending</property> + <property name="icon_size">4</property> + </object> + <object class="GtkDialog" id="sort_dialog"> + <property name="title" translatable="yes">Sort</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">True</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="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">0</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog_action_area1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <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-cancel</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="button2"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="image">sort_image</property> + <property name="label" translatable="yes">_Sort</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkButton" id="button3"> + <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> + <property name="focus_on_click">True</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="GtkVBox" id="vbox1"> + <property name="border_width">10</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">18</property> + <child> + <object class="GtkVBox" id="vbox5"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkCheckButton" id="reverse_order_checkbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Reverse order</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="remove_dups_checkbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">R_emove duplicates</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="ignore_case_checkbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Ignore 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">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> + <child> + <object class="GtkHBox" id="hbox13"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label18"> + <property name="visible">True</property> + <property name="label" translatable="yes">S_tart at column:</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.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">col_num_spinbutton</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="col_num_spinbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="climb_rate">1</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update_policy">GTK_UPDATE_ALWAYS</property> + <property name="snap_to_ticks">False</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment1</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</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> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox14"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage" id="image7"> + <property name="visible">True</property> + <property name="stock">gtk-dialog-warning</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label17"> + <property name="visible">True</property> + <property name="label" translatable="yes">You cannot undo a sort operation</property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">True</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="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> + <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="-6">button1</action-widget> + <action-widget response="-5">button2</action-widget> + <action-widget response="-11">button3</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/spell/Makefile.am b/plugins/spell/Makefile.am new file mode 100755 index 00000000..9d332f95 --- /dev/null +++ b/plugins/spell/Makefile.am @@ -0,0 +1,63 @@ +# Spell checker plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(ENCHANT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +BUILT_SOURCES = \ + gedit-spell-marshal.c \ + gedit-spell-marshal.h + +plugin_LTLIBRARIES = libspell.la + +libspell_la_SOURCES = \ + gedit-spell-plugin.c \ + gedit-spell-plugin.h \ + gedit-spell-checker.c \ + gedit-spell-checker.h \ + gedit-spell-checker-dialog.c \ + gedit-spell-checker-dialog.h \ + gedit-spell-checker-language.c \ + gedit-spell-checker-language.h \ + gedit-spell-language-dialog.c \ + gedit-spell-language-dialog.h \ + gedit-automatic-spell-checker.c \ + gedit-automatic-spell-checker.h \ + gedit-spell-utils.c \ + gedit-spell-utils.h \ + $(BUILT_SOURCES) + +libspell_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libspell_la_LIBADD = $(GEDIT_LIBS) $(ENCHANT_LIBS) + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/spell +ui_DATA = spell-checker.ui languages-dialog.ui + +gedit-spell-marshal.h: gedit-spell-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=gedit_marshal > $@ + +gedit-spell-marshal.c: gedit-spell-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) echo "#include \"gedit-spell-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=gedit_marshal >> $@ + +plugin_in_files = spell.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = \ + $(ui_DATA) \ + $(plugin_in_files) \ + gedit-spell-marshal.list + +CLEANFILES = $(BUILT_SOURCES) $(plugin_DATA) + +dist-hook: + cd $(distdir); rm -f $(BUILT_SOURCES) + +-include $(top_srcdir)/git.mk diff --git a/plugins/spell/gedit-automatic-spell-checker.c b/plugins/spell/gedit-automatic-spell-checker.c new file mode 100755 index 00000000..96b2fae9 --- /dev/null +++ b/plugins/spell/gedit-automatic-spell-checker.c @@ -0,0 +1,1015 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-automatic-spell-checker.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. + */ + +/* This is a modified version of gtkspell 2.0.5 (gtkspell.sf.net) */ +/* gtkspell - a spell-checking addon for GTK's TextView widget + * Copyright (c) 2002 Evan Martin. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <glib/gi18n.h> + +#include "gedit-automatic-spell-checker.h" +#include "gedit-spell-utils.h" + +struct _GeditAutomaticSpellChecker { + GeditDocument *doc; + GSList *views; + + GtkTextMark *mark_insert_start; + GtkTextMark *mark_insert_end; + gboolean deferred_check; + + GtkTextTag *tag_highlight; + GtkTextMark *mark_click; + + GeditSpellChecker *spell_checker; +}; + +static GQuark automatic_spell_checker_id = 0; +static GQuark suggestion_id = 0; + +static void gedit_automatic_spell_checker_free_internal (GeditAutomaticSpellChecker *spell); + +static void +view_destroy (GeditView *view, GeditAutomaticSpellChecker *spell) +{ + gedit_automatic_spell_checker_detach_view (spell, view); +} + +static void +check_word (GeditAutomaticSpellChecker *spell, GtkTextIter *start, GtkTextIter *end) +{ + gchar *word; + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), start, end, FALSE); + + /* + g_print ("Check word: %s [%d - %d]\n", word, gtk_text_iter_get_offset (start), + gtk_text_iter_get_offset (end)); + */ + + if (!gedit_spell_checker_check_word (spell->spell_checker, word, -1)) + { + /* + g_print ("Apply tag: [%d - %d]\n", gtk_text_iter_get_offset (start), + gtk_text_iter_get_offset (end)); + */ + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (spell->doc), + spell->tag_highlight, + start, + end); + } + + g_free (word); +} + +static void +check_range (GeditAutomaticSpellChecker *spell, + GtkTextIter start, + GtkTextIter end, + gboolean force_all) +{ + /* we need to "split" on word boundaries. + * luckily, Pango knows what "words" are + * so we don't have to figure it out. */ + + GtkTextIter wstart; + GtkTextIter wend; + GtkTextIter cursor; + GtkTextIter precursor; + gboolean highlight; + + /* + g_print ("Check range: [%d - %d]\n", gtk_text_iter_get_offset (&start), + gtk_text_iter_get_offset (&end)); + */ + + if (gtk_text_iter_inside_word (&end)) + gtk_text_iter_forward_word_end (&end); + + if (!gtk_text_iter_starts_word (&start)) + { + if (gtk_text_iter_inside_word (&start) || + gtk_text_iter_ends_word (&start)) + { + gtk_text_iter_backward_word_start (&start); + } + else + { + /* if we're neither at the beginning nor inside a word, + * me must be in some spaces. + * skip forward to the beginning of the next word. */ + + if (gtk_text_iter_forward_word_end (&start)) + gtk_text_iter_backward_word_start (&start); + } + } + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc), + &cursor, + gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (spell->doc))); + + precursor = cursor; + gtk_text_iter_backward_char (&precursor); + + highlight = gtk_text_iter_has_tag (&cursor, spell->tag_highlight) || + gtk_text_iter_has_tag (&precursor, spell->tag_highlight); + + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc), + spell->tag_highlight, + &start, + &end); + + /* Fix a corner case when replacement occurs at beginning of buffer: + * An iter at offset 0 seems to always be inside a word, + * even if it's not. Possibly a pango bug. + */ + if (gtk_text_iter_get_offset (&start) == 0) + { + gtk_text_iter_forward_word_end(&start); + gtk_text_iter_backward_word_start(&start); + } + + wstart = start; + + while (gedit_spell_utils_skip_no_spell_check (&wstart, &end) && + gtk_text_iter_compare (&wstart, &end) < 0) + { + gboolean inword; + + /* move wend to the end of the current word. */ + wend = wstart; + + gtk_text_iter_forward_word_end (&wend); + + inword = (gtk_text_iter_compare (&wstart, &cursor) < 0) && + (gtk_text_iter_compare (&cursor, &wend) <= 0); + + if (inword && !force_all) + { + /* this word is being actively edited, + * only check if it's already highligted, + * otherwise defer this check until later. */ + if (highlight) + check_word (spell, &wstart, &wend); + else + spell->deferred_check = TRUE; + } + else + { + check_word (spell, &wstart, &wend); + spell->deferred_check = FALSE; + } + + /* now move wend to the beginning of the next word, */ + gtk_text_iter_forward_word_end (&wend); + gtk_text_iter_backward_word_start (&wend); + + /* make sure we've actually advanced + * (we don't advance in some corner cases), */ + if (gtk_text_iter_equal (&wstart, &wend)) + break; /* we're done in these cases.. */ + + /* and then pick this as the new next word beginning. */ + wstart = wend; + } +} + +static void +check_deferred_range (GeditAutomaticSpellChecker *spell, + gboolean force_all) +{ + GtkTextIter start, end; + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc), + &start, + spell->mark_insert_start); + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc), + &end, + spell->mark_insert_end); + + check_range (spell, start, end, force_all); +} + +/* insertion works like this: + * - before the text is inserted, we mark the position in the buffer. + * - after the text is inserted, we see where our mark is and use that and + * the current position to check the entire range of inserted text. + * + * this may be overkill for the common case (inserting one character). */ + +static void +insert_text_before (GtkTextBuffer *buffer, GtkTextIter *iter, + gchar *text, gint len, GeditAutomaticSpellChecker *spell) +{ + gtk_text_buffer_move_mark (buffer, spell->mark_insert_start, iter); +} + +static void +insert_text_after (GtkTextBuffer *buffer, GtkTextIter *iter, + gchar *text, gint len, GeditAutomaticSpellChecker *spell) +{ + GtkTextIter start; + + /* we need to check a range of text. */ + gtk_text_buffer_get_iter_at_mark (buffer, &start, spell->mark_insert_start); + + check_range (spell, start, *iter, FALSE); + + gtk_text_buffer_move_mark (buffer, spell->mark_insert_end, iter); +} + +/* deleting is more simple: we're given the range of deleted text. + * after deletion, the start and end iters should be at the same position + * (because all of the text between them was deleted!). + * this means we only really check the words immediately bounding the + * deletion. + */ + +static void +delete_range_after (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, + GeditAutomaticSpellChecker *spell) +{ + check_range (spell, *start, *end, FALSE); +} + +static void +mark_set (GtkTextBuffer *buffer, + GtkTextIter *iter, + GtkTextMark *mark, + GeditAutomaticSpellChecker *spell) +{ + /* if the cursor has moved and there is a deferred check so handle it now */ + if ((mark == gtk_text_buffer_get_insert (buffer)) && spell->deferred_check) + check_deferred_range (spell, FALSE); +} + +static void +get_word_extents_from_mark (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + GtkTextMark *mark) +{ + gtk_text_buffer_get_iter_at_mark(buffer, start, mark); + + if (!gtk_text_iter_starts_word (start)) + gtk_text_iter_backward_word_start (start); + + *end = *start; + + if (gtk_text_iter_inside_word (end)) + gtk_text_iter_forward_word_end (end); +} + +static void +remove_tag_to_word (GeditAutomaticSpellChecker *spell, const gchar *word) +{ + GtkTextIter iter; + GtkTextIter match_start, match_end; + + gboolean found; + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (spell->doc), &iter, 0); + + found = TRUE; + + while (found) + { + found = gtk_text_iter_forward_search (&iter, + word, + GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY, + &match_start, + &match_end, + NULL); + + if (found) + { + if (gtk_text_iter_starts_word (&match_start) && + gtk_text_iter_ends_word (&match_end)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc), + spell->tag_highlight, + &match_start, + &match_end); + } + + iter = match_end; + } + } +} + +static void +add_to_dictionary (GtkWidget *menuitem, GeditAutomaticSpellChecker *spell) +{ + gchar *word; + + GtkTextIter start, end; + + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click); + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), + &start, + &end, + FALSE); + + gedit_spell_checker_add_word_to_personal (spell->spell_checker, word, -1); + + g_free (word); +} + +static void +ignore_all (GtkWidget *menuitem, GeditAutomaticSpellChecker *spell) +{ + gchar *word; + + GtkTextIter start, end; + + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click); + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), + &start, + &end, + FALSE); + + gedit_spell_checker_add_word_to_session (spell->spell_checker, word, -1); + + g_free (word); +} + +static void +replace_word (GtkWidget *menuitem, GeditAutomaticSpellChecker *spell) +{ + gchar *oldword; + const gchar *newword; + + GtkTextIter start, end; + + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click); + + oldword = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), &start, &end, FALSE); + + newword = g_object_get_qdata (G_OBJECT (menuitem), suggestion_id); + g_return_if_fail (newword != NULL); + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (spell->doc)); + + gtk_text_buffer_delete (GTK_TEXT_BUFFER (spell->doc), &start, &end); + gtk_text_buffer_insert (GTK_TEXT_BUFFER (spell->doc), &start, newword, -1); + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (spell->doc)); + + gedit_spell_checker_set_correction (spell->spell_checker, + oldword, strlen (oldword), + newword, strlen (newword)); + + g_free (oldword); +} + +static GtkWidget * +build_suggestion_menu (GeditAutomaticSpellChecker *spell, const gchar *word) +{ + GtkWidget *topmenu, *menu; + GtkWidget *mi; + GSList *suggestions; + GSList *list; + gchar *label_text; + + topmenu = menu = gtk_menu_new(); + + suggestions = gedit_spell_checker_get_suggestions (spell->spell_checker, word, -1); + + list = suggestions; + + if (suggestions == NULL) + { + /* no suggestions. put something in the menu anyway... */ + GtkWidget *label; + /* Translators: Displayed in the "Check Spelling" dialog if there are no suggestions for the current misspelled word */ + label = gtk_label_new (_("(no suggested words)")); + + mi = gtk_menu_item_new (); + gtk_widget_set_sensitive (mi, FALSE); + gtk_container_add (GTK_CONTAINER(mi), label); + gtk_widget_show_all (mi); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); + } + else + { + gint count = 0; + + /* build a set of menus with suggestions. */ + while (suggestions != NULL) + { + GtkWidget *label; + + if (count == 10) + { + /* Separator */ + mi = gtk_menu_item_new (); + gtk_widget_show (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + + mi = gtk_menu_item_new_with_mnemonic (_("_More...")); + gtk_widget_show (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + + menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu); + count = 0; + } + + label_text = g_strdup_printf ("<b>%s</b>", (gchar*) suggestions->data); + + label = gtk_label_new (label_text); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + mi = gtk_menu_item_new (); + gtk_container_add (GTK_CONTAINER(mi), label); + + gtk_widget_show_all (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + + g_object_set_qdata_full (G_OBJECT (mi), + suggestion_id, + g_strdup (suggestions->data), + (GDestroyNotify)g_free); + + g_free (label_text); + g_signal_connect (mi, + "activate", + G_CALLBACK (replace_word), + spell); + + count++; + + suggestions = g_slist_next (suggestions); + } + } + + /* free the suggestion list */ + suggestions = list; + + while (list) + { + g_free (list->data); + list = g_slist_next (list); + } + + g_slist_free (suggestions); + + /* Separator */ + mi = gtk_menu_item_new (); + gtk_widget_show (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); + + /* Ignore all */ + mi = gtk_image_menu_item_new_with_mnemonic (_("_Ignore All")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), + gtk_image_new_from_stock (GTK_STOCK_GOTO_BOTTOM, + GTK_ICON_SIZE_MENU)); + + g_signal_connect (mi, + "activate", + G_CALLBACK(ignore_all), + spell); + + gtk_widget_show_all (mi); + + gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); + + /* + Add to Dictionary */ + mi = gtk_image_menu_item_new_with_mnemonic (_("_Add")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), + gtk_image_new_from_stock (GTK_STOCK_ADD, + GTK_ICON_SIZE_MENU)); + + g_signal_connect (mi, + "activate", + G_CALLBACK (add_to_dictionary), + spell); + + gtk_widget_show_all (mi); + + gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); + + return topmenu; +} + +static void +populate_popup (GtkTextView *textview, GtkMenu *menu, GeditAutomaticSpellChecker *spell) +{ + GtkWidget *img, *mi; + GtkTextIter start, end; + char *word; + + /* we need to figure out if they picked a misspelled word. */ + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click); + + /* if our highlight algorithm ever messes up, + * this isn't correct, either. */ + if (!gtk_text_iter_has_tag (&start, spell->tag_highlight)) + return; /* word wasn't misspelled. */ + + /* menu separator comes first. */ + mi = gtk_menu_item_new (); + gtk_widget_show (mi); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); + + /* then, on top of it, the suggestions menu. */ + img = gtk_image_new_from_stock (GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU); + mi = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), img); + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), &start, &end, FALSE); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), + build_suggestion_menu (spell, word)); + g_free(word); + + gtk_widget_show_all (mi); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); +} + +void +gedit_automatic_spell_checker_recheck_all (GeditAutomaticSpellChecker *spell) +{ + GtkTextIter start, end; + + g_return_if_fail (spell != NULL); + + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (spell->doc), &start, &end); + + check_range (spell, start, end, TRUE); +} + +static void +add_word_signal_cb (GeditSpellChecker *checker, + const gchar *word, + gint len, + GeditAutomaticSpellChecker *spell) +{ + gchar *w; + + if (len < 0) + w = g_strdup (word); + else + w = g_strndup (word, len); + + remove_tag_to_word (spell, w); + + g_free (w); +} + +static void +set_language_cb (GeditSpellChecker *checker, + const GeditSpellCheckerLanguage *lang, + GeditAutomaticSpellChecker *spell) +{ + gedit_automatic_spell_checker_recheck_all (spell); +} + +static void +clear_session_cb (GeditSpellChecker *checker, + GeditAutomaticSpellChecker *spell) +{ + gedit_automatic_spell_checker_recheck_all (spell); +} + +/* When the user right-clicks on a word, they want to check that word. + * Here, we do NOT move the cursor to the location of the clicked-upon word + * since that prevents the use of edit functions on the context menu. + */ +static gboolean +button_press_event (GtkTextView *view, + GdkEventButton *event, + GeditAutomaticSpellChecker *spell) +{ + if (event->button == 3) + { + gint x, y; + GtkTextIter iter; + + GtkTextBuffer *buffer = gtk_text_view_get_buffer (view); + + /* handle deferred check if it exists */ + if (spell->deferred_check) + check_deferred_range (spell, TRUE); + + gtk_text_view_window_to_buffer_coords (view, + GTK_TEXT_WINDOW_TEXT, + event->x, event->y, + &x, &y); + + gtk_text_view_get_iter_at_location (view, &iter, x, y); + + gtk_text_buffer_move_mark (buffer, spell->mark_click, &iter); + } + + return FALSE; /* false: let gtk process this event, too. + we don't want to eat any events. */ +} + +/* Move the insert mark before popping up the menu, otherwise it + * will contain the wrong set of suggestions. + */ +static gboolean +popup_menu_event (GtkTextView *view, GeditAutomaticSpellChecker *spell) +{ + GtkTextIter iter; + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer (view); + + /* handle deferred check if it exists */ + if (spell->deferred_check) + check_deferred_range (spell, TRUE); + + gtk_text_buffer_get_iter_at_mark (buffer, &iter, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_move_mark (buffer, spell->mark_click, &iter); + + return FALSE; +} + +static void +tag_table_changed (GtkTextTagTable *table, + GeditAutomaticSpellChecker *spell) +{ + g_return_if_fail (spell->tag_highlight != NULL); + + gtk_text_tag_set_priority (spell->tag_highlight, + gtk_text_tag_table_get_size (table) - 1); +} + +static void +tag_added_or_removed (GtkTextTagTable *table, + GtkTextTag *tag, + GeditAutomaticSpellChecker *spell) +{ + tag_table_changed (table, spell); +} + +static void +tag_changed (GtkTextTagTable *table, + GtkTextTag *tag, + gboolean size_changed, + GeditAutomaticSpellChecker *spell) +{ + tag_table_changed (table, spell); +} + +static void +highlight_updated (GtkSourceBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + GeditAutomaticSpellChecker *spell) +{ + check_range (spell, *start, *end, FALSE); +} + +static void +spell_tag_destroyed (GeditAutomaticSpellChecker *spell, + GObject *where_the_object_was) +{ + spell->tag_highlight = NULL; +} + +GeditAutomaticSpellChecker * +gedit_automatic_spell_checker_new (GeditDocument *doc, + GeditSpellChecker *checker) +{ + GeditAutomaticSpellChecker *spell; + GtkTextTagTable *tag_table; + GtkTextIter start, end; + + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (checker), NULL); + g_return_val_if_fail ((spell = gedit_automatic_spell_checker_get_from_document (doc)) == NULL, + spell); + + /* attach to the widget */ + spell = g_new0 (GeditAutomaticSpellChecker, 1); + + spell->doc = doc; + spell->spell_checker = g_object_ref (checker); + + if (automatic_spell_checker_id == 0) + { + automatic_spell_checker_id = + g_quark_from_string ("GeditAutomaticSpellCheckerID"); + } + if (suggestion_id == 0) + { + suggestion_id = g_quark_from_string ("GeditAutoSuggestionID"); + } + + g_object_set_qdata_full (G_OBJECT (doc), + automatic_spell_checker_id, + spell, + (GDestroyNotify)gedit_automatic_spell_checker_free_internal); + + g_signal_connect (doc, + "insert-text", + G_CALLBACK (insert_text_before), + spell); + g_signal_connect_after (doc, + "insert-text", + G_CALLBACK (insert_text_after), + spell); + g_signal_connect_after (doc, + "delete-range", + G_CALLBACK (delete_range_after), + spell); + g_signal_connect (doc, + "mark-set", + G_CALLBACK (mark_set), + spell); + + g_signal_connect (doc, + "highlight-updated", + G_CALLBACK (highlight_updated), + spell); + + g_signal_connect (spell->spell_checker, + "add_word_to_session", + G_CALLBACK (add_word_signal_cb), + spell); + g_signal_connect (spell->spell_checker, + "add_word_to_personal", + G_CALLBACK (add_word_signal_cb), + spell); + g_signal_connect (spell->spell_checker, + "clear_session", + G_CALLBACK (clear_session_cb), + spell); + g_signal_connect (spell->spell_checker, + "set_language", + G_CALLBACK (set_language_cb), + spell); + + spell->tag_highlight = gtk_text_buffer_create_tag ( + GTK_TEXT_BUFFER (doc), + "gtkspell-misspelled", + "underline", PANGO_UNDERLINE_ERROR, + NULL); + + g_object_weak_ref (G_OBJECT (spell->tag_highlight), + (GWeakNotify)spell_tag_destroyed, + spell); + + tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (doc)); + + gtk_text_tag_set_priority (spell->tag_highlight, + gtk_text_tag_table_get_size (tag_table) - 1); + + g_signal_connect (tag_table, + "tag-added", + G_CALLBACK (tag_added_or_removed), + spell); + g_signal_connect (tag_table, + "tag-removed", + G_CALLBACK (tag_added_or_removed), + spell); + g_signal_connect (tag_table, + "tag-changed", + G_CALLBACK (tag_changed), + spell); + + /* we create the mark here, but we don't use it until text is + * inserted, so we don't really care where iter points. */ + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), &start, &end); + + spell->mark_insert_start = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc), + "gedit-automatic-spell-checker-insert-start"); + + if (spell->mark_insert_start == NULL) + { + spell->mark_insert_start = + gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "gedit-automatic-spell-checker-insert-start", + &start, + TRUE); + } + else + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + spell->mark_insert_start, + &start); + } + + spell->mark_insert_end = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc), + "gedit-automatic-spell-checker-insert-end"); + + if (spell->mark_insert_end == NULL) + { + spell->mark_insert_end = + gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "gedit-automatic-spell-checker-insert-end", + &start, + TRUE); + } + else + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + spell->mark_insert_end, + &start); + } + + spell->mark_click = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc), + "gedit-automatic-spell-checker-click"); + + if (spell->mark_click == NULL) + { + spell->mark_click = + gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "gedit-automatic-spell-checker-click", + &start, + TRUE); + } + else + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + spell->mark_click, + &start); + } + + spell->deferred_check = FALSE; + + return spell; +} + +GeditAutomaticSpellChecker * +gedit_automatic_spell_checker_get_from_document (const GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + if (automatic_spell_checker_id == 0) + return NULL; + + return g_object_get_qdata (G_OBJECT (doc), automatic_spell_checker_id); +} + +void +gedit_automatic_spell_checker_free (GeditAutomaticSpellChecker *spell) +{ + g_return_if_fail (spell != NULL); + g_return_if_fail (gedit_automatic_spell_checker_get_from_document (spell->doc) == spell); + + if (automatic_spell_checker_id == 0) + return; + + g_object_set_qdata (G_OBJECT (spell->doc), automatic_spell_checker_id, NULL); +} + +static void +gedit_automatic_spell_checker_free_internal (GeditAutomaticSpellChecker *spell) +{ + GtkTextTagTable *table; + GtkTextIter start, end; + GSList *list; + + g_return_if_fail (spell != NULL); + + table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (spell->doc)); + + if (table != NULL && spell->tag_highlight != NULL) + { + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (spell->doc), + &start, + &end); + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc), + spell->tag_highlight, + &start, + &end); + + g_signal_handlers_disconnect_matched (G_OBJECT (table), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + gtk_text_tag_table_remove (table, spell->tag_highlight); + } + + g_signal_handlers_disconnect_matched (G_OBJECT (spell->doc), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_signal_handlers_disconnect_matched (G_OBJECT (spell->spell_checker), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_object_unref (spell->spell_checker); + + list = spell->views; + while (list != NULL) + { + GeditView *view = GEDIT_VIEW (list->data); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + list = g_slist_next (list); + } + + g_slist_free (spell->views); + + g_free (spell); +} + +void +gedit_automatic_spell_checker_attach_view ( + GeditAutomaticSpellChecker *spell, + GeditView *view) +{ + g_return_if_fail (spell != NULL); + g_return_if_fail (GEDIT_IS_VIEW (view)); + + g_return_if_fail (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)) == + GTK_TEXT_BUFFER (spell->doc)); + + g_signal_connect (view, + "button-press-event", + G_CALLBACK (button_press_event), + spell); + g_signal_connect (view, + "popup-menu", + G_CALLBACK (popup_menu_event), + spell); + g_signal_connect (view, + "populate-popup", + G_CALLBACK (populate_popup), + spell); + g_signal_connect (view, + "destroy", + G_CALLBACK (view_destroy), + spell); + + spell->views = g_slist_prepend (spell->views, view); +} + +void +gedit_automatic_spell_checker_detach_view ( + GeditAutomaticSpellChecker *spell, + GeditView *view) +{ + g_return_if_fail (spell != NULL); + g_return_if_fail (GEDIT_IS_VIEW (view)); + + g_return_if_fail (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)) == + GTK_TEXT_BUFFER (spell->doc)); + g_return_if_fail (spell->views != NULL); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + spell->views = g_slist_remove (spell->views, view); +} + diff --git a/plugins/spell/gedit-automatic-spell-checker.h b/plugins/spell/gedit-automatic-spell-checker.h new file mode 100755 index 00000000..cc634424 --- /dev/null +++ b/plugins/spell/gedit-automatic-spell-checker.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-automatic-spell-checker.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. + */ + +/* This is a modified version of gtkspell 2.0.2 (gtkspell.sf.net) */ +/* gtkspell - a spell-checking addon for GTK's TextView widget + * Copyright (c) 2002 Evan Martin. + */ + +#ifndef __GEDIT_AUTOMATIC_SPELL_CHECKER_H__ +#define __GEDIT_AUTOMATIC_SPELL_CHECKER_H__ + +#include <gedit/gedit-document.h> +#include <gedit/gedit-view.h> + +#include "gedit-spell-checker.h" + +typedef struct _GeditAutomaticSpellChecker GeditAutomaticSpellChecker; + +GeditAutomaticSpellChecker *gedit_automatic_spell_checker_new ( + GeditDocument *doc, + GeditSpellChecker *checker); + +GeditAutomaticSpellChecker *gedit_automatic_spell_checker_get_from_document ( + const GeditDocument *doc); + +void gedit_automatic_spell_checker_free ( + GeditAutomaticSpellChecker *spell); + +void gedit_automatic_spell_checker_attach_view ( + GeditAutomaticSpellChecker *spell, + GeditView *view); + +void gedit_automatic_spell_checker_detach_view ( + GeditAutomaticSpellChecker *spell, + GeditView *view); + +void gedit_automatic_spell_checker_recheck_all ( + GeditAutomaticSpellChecker *spell); + +#endif /* __GEDIT_AUTOMATIC_SPELL_CHECKER_H__ */ + diff --git a/plugins/spell/gedit-spell-checker-dialog.c b/plugins/spell/gedit-spell-checker-dialog.c new file mode 100755 index 00000000..0488d160 --- /dev/null +++ b/plugins/spell/gedit-spell-checker-dialog.c @@ -0,0 +1,722 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-checker-dialog.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 <gtk/gtk.h> +#include <gedit/gedit-utils.h> +#include "gedit-spell-checker-dialog.h" +#include "gedit-spell-marshal.h" + +struct _GeditSpellCheckerDialog +{ + GtkWindow parent_instance; + + GeditSpellChecker *spell_checker; + + gchar *misspelled_word; + + GtkWidget *misspelled_word_label; + GtkWidget *word_entry; + GtkWidget *check_word_button; + GtkWidget *ignore_button; + GtkWidget *ignore_all_button; + GtkWidget *change_button; + GtkWidget *change_all_button; + GtkWidget *add_word_button; + GtkWidget *close_button; + GtkWidget *suggestions_list; + GtkWidget *language_label; + + GtkTreeModel *suggestions_list_model; +}; + +enum +{ + IGNORE, + IGNORE_ALL, + CHANGE, + CHANGE_ALL, + ADD_WORD_TO_PERSONAL, + LAST_SIGNAL +}; + +enum +{ + COLUMN_SUGGESTIONS, + NUM_COLUMNS +}; + +static void update_suggestions_list_model (GeditSpellCheckerDialog *dlg, + GSList *suggestions); + +static void word_entry_changed_handler (GtkEditable *editable, + GeditSpellCheckerDialog *dlg); +static void close_button_clicked_handler (GtkButton *button, + GeditSpellCheckerDialog *dlg); +static void suggestions_list_selection_changed_handler (GtkTreeSelection *selection, + GeditSpellCheckerDialog *dlg); +static void check_word_button_clicked_handler (GtkButton *button, + GeditSpellCheckerDialog *dlg); +static void add_word_button_clicked_handler (GtkButton *button, + GeditSpellCheckerDialog *dlg); +static void ignore_button_clicked_handler (GtkButton *button, + GeditSpellCheckerDialog *dlg); +static void ignore_all_button_clicked_handler (GtkButton *button, + GeditSpellCheckerDialog *dlg); +static void change_button_clicked_handler (GtkButton *button, + GeditSpellCheckerDialog *dlg); +static void change_all_button_clicked_handler (GtkButton *button, + GeditSpellCheckerDialog *dlg); +static void suggestions_list_row_activated_handler (GtkTreeView *view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GeditSpellCheckerDialog *dlg); + + +static guint signals [LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE(GeditSpellCheckerDialog, gedit_spell_checker_dialog, GTK_TYPE_WINDOW) + +static void +gedit_spell_checker_dialog_destroy (GtkObject *object) +{ + GeditSpellCheckerDialog *dlg = GEDIT_SPELL_CHECKER_DIALOG (object); + + if (dlg->spell_checker != NULL) + { + g_object_unref (dlg->spell_checker); + dlg->spell_checker = NULL; + } + + if (dlg->misspelled_word != NULL) + { + g_free (dlg->misspelled_word); + dlg->misspelled_word = NULL; + } + + GTK_OBJECT_CLASS (gedit_spell_checker_dialog_parent_class)->destroy (object); +} + +static void +gedit_spell_checker_dialog_class_init (GeditSpellCheckerDialogClass * klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + GTK_OBJECT_CLASS (object_class)->destroy = gedit_spell_checker_dialog_destroy; + + signals[IGNORE] = + g_signal_new ("ignore", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, ignore), + NULL, NULL, + gedit_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + signals[IGNORE_ALL] = + g_signal_new ("ignore_all", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, ignore_all), + NULL, NULL, + gedit_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + signals[CHANGE] = + g_signal_new ("change", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, change), + NULL, NULL, + gedit_marshal_VOID__STRING_STRING, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[CHANGE_ALL] = + g_signal_new ("change_all", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, change_all), + NULL, NULL, + gedit_marshal_VOID__STRING_STRING, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[ADD_WORD_TO_PERSONAL] = + g_signal_new ("add_word_to_personal", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, add_word_to_personal), + NULL, NULL, + gedit_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} + +static void +create_dialog (GeditSpellCheckerDialog *dlg, + const gchar *data_dir) +{ + GtkWidget *error_widget; + GtkWidget *content; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkTreeSelection *selection; + gchar *root_objects[] = { + "content", + "check_word_image", + "add_word_image", + "ignore_image", + "change_image", + "ignore_all_image", + "change_all_image", + NULL + }; + gboolean ret; + gchar *ui_file; + + g_return_if_fail (dlg != NULL); + + dlg->spell_checker = NULL; + dlg->misspelled_word = NULL; + + ui_file = g_build_filename (data_dir, "spell-checker.ui", NULL); + ret = gedit_utils_get_ui_objects (ui_file, + root_objects, + &error_widget, + + "content", &content, + "misspelled_word_label", &dlg->misspelled_word_label, + "word_entry", &dlg->word_entry, + "check_word_button", &dlg->check_word_button, + "ignore_button", &dlg->ignore_button, + "ignore_all_button", &dlg->ignore_all_button, + "change_button", &dlg->change_button, + "change_all_button", &dlg->change_all_button, + "add_word_button", &dlg->add_word_button, + "close_button", &dlg->close_button, + "suggestions_list", &dlg->suggestions_list, + "language_label", &dlg->language_label, + NULL); + g_free (ui_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_label_set_label (GTK_LABEL (dlg->misspelled_word_label), ""); + gtk_widget_set_sensitive (dlg->word_entry, FALSE); + gtk_widget_set_sensitive (dlg->check_word_button, FALSE); + gtk_widget_set_sensitive (dlg->ignore_button, FALSE); + gtk_widget_set_sensitive (dlg->ignore_all_button, FALSE); + gtk_widget_set_sensitive (dlg->change_button, FALSE); + gtk_widget_set_sensitive (dlg->change_all_button, FALSE); + gtk_widget_set_sensitive (dlg->add_word_button, FALSE); + + gtk_label_set_label (GTK_LABEL (dlg->language_label), ""); + + gtk_container_add (GTK_CONTAINER (dlg), content); + g_object_unref (content); + + gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE); + gtk_window_set_title (GTK_WINDOW (dlg), _("Check Spelling")); + + /* Suggestion list */ + dlg->suggestions_list_model = GTK_TREE_MODEL ( + gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING)); + + gtk_tree_view_set_model (GTK_TREE_VIEW (dlg->suggestions_list), + dlg->suggestions_list_model); + + /* Add the suggestions column */ + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Suggestions"), cell, + "text", COLUMN_SUGGESTIONS, NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->suggestions_list), column); + + gtk_tree_view_set_search_column (GTK_TREE_VIEW (dlg->suggestions_list), + COLUMN_SUGGESTIONS); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->suggestions_list)); + + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + /* Set default button */ + GTK_WIDGET_SET_FLAGS (dlg->change_button, GTK_CAN_DEFAULT); + gtk_widget_grab_default (dlg->change_button); + + gtk_entry_set_activates_default (GTK_ENTRY (dlg->word_entry), TRUE); + + /* Connect signals */ + g_signal_connect (dlg->word_entry, "changed", + G_CALLBACK (word_entry_changed_handler), dlg); + g_signal_connect (dlg->close_button, "clicked", + G_CALLBACK (close_button_clicked_handler), dlg); + g_signal_connect (selection, "changed", + G_CALLBACK (suggestions_list_selection_changed_handler), + dlg); + g_signal_connect (dlg->check_word_button, "clicked", + G_CALLBACK (check_word_button_clicked_handler), dlg); + g_signal_connect (dlg->add_word_button, "clicked", + G_CALLBACK (add_word_button_clicked_handler), dlg); + g_signal_connect (dlg->ignore_button, "clicked", + G_CALLBACK (ignore_button_clicked_handler), dlg); + g_signal_connect (dlg->ignore_all_button, "clicked", + G_CALLBACK (ignore_all_button_clicked_handler), dlg); + g_signal_connect (dlg->change_button, "clicked", + G_CALLBACK (change_button_clicked_handler), dlg); + g_signal_connect (dlg->change_all_button, "clicked", + G_CALLBACK (change_all_button_clicked_handler), dlg); + g_signal_connect (dlg->suggestions_list, "row-activated", + G_CALLBACK (suggestions_list_row_activated_handler), dlg); +} + +static void +gedit_spell_checker_dialog_init (GeditSpellCheckerDialog *dlg) +{ +} + +GtkWidget * +gedit_spell_checker_dialog_new (const gchar *data_dir) +{ + GeditSpellCheckerDialog *dlg; + + dlg = GEDIT_SPELL_CHECKER_DIALOG ( + g_object_new (GEDIT_TYPE_SPELL_CHECKER_DIALOG, NULL)); + + g_return_val_if_fail (dlg != NULL, NULL); + + create_dialog (dlg, data_dir); + + return GTK_WIDGET (dlg); +} + +GtkWidget * +gedit_spell_checker_dialog_new_from_spell_checker (GeditSpellChecker *spell, + const gchar *data_dir) +{ + GeditSpellCheckerDialog *dlg; + + g_return_val_if_fail (spell != NULL, NULL); + + dlg = GEDIT_SPELL_CHECKER_DIALOG ( + g_object_new (GEDIT_TYPE_SPELL_CHECKER_DIALOG, NULL)); + + g_return_val_if_fail (dlg != NULL, NULL); + + create_dialog (dlg, data_dir); + + gedit_spell_checker_dialog_set_spell_checker (dlg, spell); + + return GTK_WIDGET (dlg); +} + +void +gedit_spell_checker_dialog_set_spell_checker (GeditSpellCheckerDialog *dlg, GeditSpellChecker *spell) +{ + const GeditSpellCheckerLanguage* language; + const gchar *lang; + gchar *tmp; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (spell != NULL); + + if (dlg->spell_checker != NULL) + g_object_unref (dlg->spell_checker); + + dlg->spell_checker = spell; + g_object_ref (dlg->spell_checker); + + language = gedit_spell_checker_get_language (dlg->spell_checker); + + lang = gedit_spell_checker_language_to_string (language); + tmp = g_strdup_printf("<b>%s</b>", lang); + + gtk_label_set_label (GTK_LABEL (dlg->language_label), tmp); + g_free (tmp); + + if (dlg->misspelled_word != NULL) + gedit_spell_checker_dialog_set_misspelled_word (dlg, dlg->misspelled_word, -1); + else + gtk_list_store_clear (GTK_LIST_STORE (dlg->suggestions_list_model)); + + /* TODO: reset all widgets */ +} + +void +gedit_spell_checker_dialog_set_misspelled_word (GeditSpellCheckerDialog *dlg, + const gchar *word, + gint len) +{ + gchar *tmp; + GSList *sug; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (word != NULL); + + g_return_if_fail (dlg->spell_checker != NULL); + g_return_if_fail (!gedit_spell_checker_check_word (dlg->spell_checker, word, -1)); + + /* build_suggestions_list */ + if (dlg->misspelled_word != NULL) + g_free (dlg->misspelled_word); + + dlg->misspelled_word = g_strdup (word); + + tmp = g_strdup_printf("<b>%s</b>", word); + gtk_label_set_label (GTK_LABEL (dlg->misspelled_word_label), tmp); + g_free (tmp); + + sug = gedit_spell_checker_get_suggestions (dlg->spell_checker, + dlg->misspelled_word, + -1); + + update_suggestions_list_model (dlg, sug); + + /* free the suggestion list */ + g_slist_foreach (sug, (GFunc)g_free, NULL); + g_slist_free (sug); + + gtk_widget_set_sensitive (dlg->ignore_button, TRUE); + gtk_widget_set_sensitive (dlg->ignore_all_button, TRUE); + gtk_widget_set_sensitive (dlg->add_word_button, TRUE); +} + +static void +update_suggestions_list_model (GeditSpellCheckerDialog *dlg, GSList *suggestions) +{ + GtkListStore *store; + GtkTreeIter iter; + GtkTreeSelection *sel; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (GTK_IS_LIST_STORE (dlg->suggestions_list_model)); + + store = GTK_LIST_STORE (dlg->suggestions_list_model); + gtk_list_store_clear (store); + + gtk_widget_set_sensitive (dlg->word_entry, TRUE); + + if (suggestions == NULL) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + /* Translators: Displayed in the "Check Spelling" dialog if there are no suggestions + * for the current misspelled word */ + COLUMN_SUGGESTIONS, _("(no suggested words)"), + -1); + + gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), ""); + + gtk_widget_set_sensitive (dlg->suggestions_list, FALSE); + + return; + } + + gtk_widget_set_sensitive (dlg->suggestions_list, TRUE); + + gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), (gchar*)suggestions->data); + + while (suggestions != NULL) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_SUGGESTIONS, (gchar*)suggestions->data, + -1); + + suggestions = g_slist_next (suggestions); + } + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->suggestions_list)); + gtk_tree_model_get_iter_first (dlg->suggestions_list_model, &iter); + gtk_tree_selection_select_iter (sel, &iter); +} + +static void +word_entry_changed_handler (GtkEditable *editable, GeditSpellCheckerDialog *dlg) +{ + const gchar *text; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + + text = gtk_entry_get_text (GTK_ENTRY (dlg->word_entry)); + + if (g_utf8_strlen (text, -1) > 0) + { + gtk_widget_set_sensitive (dlg->check_word_button, TRUE); + gtk_widget_set_sensitive (dlg->change_button, TRUE); + gtk_widget_set_sensitive (dlg->change_all_button, TRUE); + } + else + { + gtk_widget_set_sensitive (dlg->check_word_button, FALSE); + gtk_widget_set_sensitive (dlg->change_button, FALSE); + gtk_widget_set_sensitive (dlg->change_all_button, FALSE); + } +} + +static void +close_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg) +{ + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + + gtk_widget_destroy (GTK_WIDGET (dlg)); +} + +static void +suggestions_list_selection_changed_handler (GtkTreeSelection *selection, + GeditSpellCheckerDialog *dlg) +{ + GtkTreeIter iter; + GValue value = {0, }; + const gchar *text; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + + if (! gtk_tree_selection_get_selected (selection, NULL, &iter)) + return; + + gtk_tree_model_get_value (dlg->suggestions_list_model, &iter, + COLUMN_SUGGESTIONS, + &value); + + text = g_value_get_string (&value); + + gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), text); + + g_value_unset (&value); +} + +static void +check_word_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg) +{ + const gchar *word; + gssize len; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + + word = gtk_entry_get_text (GTK_ENTRY (dlg->word_entry)); + len = strlen (word); + g_return_if_fail (len > 0); + + if (gedit_spell_checker_check_word (dlg->spell_checker, word, len)) + { + GtkListStore *store; + GtkTreeIter iter; + + store = GTK_LIST_STORE (dlg->suggestions_list_model); + gtk_list_store_clear (store); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + /* Translators: Displayed in the "Check Spelling" dialog if the current word isn't misspelled */ + COLUMN_SUGGESTIONS, _("(correct spelling)"), + -1); + + gtk_widget_set_sensitive (dlg->suggestions_list, FALSE); + } + else + { + GSList *sug; + + sug = gedit_spell_checker_get_suggestions (dlg->spell_checker, + word, + len); + + update_suggestions_list_model (dlg, sug); + + /* free the suggestion list */ + g_slist_foreach (sug, (GFunc)g_free, NULL); + g_slist_free (sug); + } +} + +static void +add_word_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg) +{ + gchar *word; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (dlg->misspelled_word != NULL); + + gedit_spell_checker_add_word_to_personal (dlg->spell_checker, + dlg->misspelled_word, + -1); + + word = g_strdup (dlg->misspelled_word); + + g_signal_emit (G_OBJECT (dlg), signals [ADD_WORD_TO_PERSONAL], 0, word); + + g_free (word); +} + +static void +ignore_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg) +{ + gchar *word; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (dlg->misspelled_word != NULL); + + word = g_strdup (dlg->misspelled_word); + + g_signal_emit (G_OBJECT (dlg), signals [IGNORE], 0, word); + + g_free (word); +} + +static void +ignore_all_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg) +{ + gchar *word; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (dlg->misspelled_word != NULL); + + gedit_spell_checker_add_word_to_session (dlg->spell_checker, + dlg->misspelled_word, + -1); + + word = g_strdup (dlg->misspelled_word); + + g_signal_emit (G_OBJECT (dlg), signals [IGNORE_ALL], 0, word); + + g_free (word); +} + +static void +change_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg) +{ + gchar *word; + gchar *change; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (dlg->misspelled_word != NULL); + + change = g_strdup (gtk_entry_get_text (GTK_ENTRY (dlg->word_entry))); + g_return_if_fail (change != NULL); + g_return_if_fail (*change != '\0'); + + gedit_spell_checker_set_correction (dlg->spell_checker, + dlg->misspelled_word, -1, + change, -1); + + word = g_strdup (dlg->misspelled_word); + + g_signal_emit (G_OBJECT (dlg), signals [CHANGE], 0, word, change); + + g_free (word); + g_free (change); +} + +/* double click on one of the suggestions is like clicking on "change" */ +static void +suggestions_list_row_activated_handler (GtkTreeView *view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GeditSpellCheckerDialog *dlg) +{ + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + + change_button_clicked_handler (GTK_BUTTON (dlg->change_button), dlg); +} + +static void +change_all_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg) +{ + gchar *word; + gchar *change; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + g_return_if_fail (dlg->misspelled_word != NULL); + + change = g_strdup (gtk_entry_get_text (GTK_ENTRY (dlg->word_entry))); + g_return_if_fail (change != NULL); + g_return_if_fail (*change != '\0'); + + gedit_spell_checker_set_correction (dlg->spell_checker, + dlg->misspelled_word, -1, + change, -1); + + word = g_strdup (dlg->misspelled_word); + + g_signal_emit (G_OBJECT (dlg), signals [CHANGE_ALL], 0, word, change); + + g_free (word); + g_free (change); +} + +void +gedit_spell_checker_dialog_set_completed (GeditSpellCheckerDialog *dlg) +{ + gchar *tmp; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg)); + + tmp = g_strdup_printf("<b>%s</b>", _("Completed spell checking")); + gtk_label_set_label (GTK_LABEL (dlg->misspelled_word_label), + tmp); + g_free (tmp); + + gtk_list_store_clear (GTK_LIST_STORE (dlg->suggestions_list_model)); + gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), ""); + + gtk_widget_set_sensitive (dlg->word_entry, FALSE); + gtk_widget_set_sensitive (dlg->check_word_button, FALSE); + gtk_widget_set_sensitive (dlg->ignore_button, FALSE); + gtk_widget_set_sensitive (dlg->ignore_all_button, FALSE); + gtk_widget_set_sensitive (dlg->change_button, FALSE); + gtk_widget_set_sensitive (dlg->change_all_button, FALSE); + gtk_widget_set_sensitive (dlg->add_word_button, FALSE); + gtk_widget_set_sensitive (dlg->suggestions_list, FALSE); +} + diff --git a/plugins/spell/gedit-spell-checker-dialog.h b/plugins/spell/gedit-spell-checker-dialog.h new file mode 100755 index 00000000..257c2b75 --- /dev/null +++ b/plugins/spell/gedit-spell-checker-dialog.h @@ -0,0 +1,92 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-checker-dialog.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_SPELL_CHECKER_DIALOG_H__ +#define __GEDIT_SPELL_CHECKER_DIALOG_H__ + +#include <gtk/gtk.h> +#include "gedit-spell-checker.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_SPELL_CHECKER_DIALOG (gedit_spell_checker_dialog_get_type ()) +#define GEDIT_SPELL_CHECKER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_SPELL_CHECKER_DIALOG, GeditSpellCheckerDialog)) +#define GEDIT_SPELL_CHECKER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_SPELL_CHECKER_DIALOG, GeditSpellCheckerDialog)) +#define GEDIT_IS_SPELL_CHECKER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_SPELL_CHECKER_DIALOG)) +#define GEDIT_IS_SPELL_CHECKER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SPELL_CHECKER_DIALOG)) +#define GEDIT_SPELL_CHECKER_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_SPELL_CHECKER_DIALOG, GeditSpellCheckerDialog)) + + +typedef struct _GeditSpellCheckerDialog GeditSpellCheckerDialog; + +typedef struct _GeditSpellCheckerDialogClass GeditSpellCheckerDialogClass; + +struct _GeditSpellCheckerDialogClass +{ + GtkWindowClass parent_class; + + /* Signals */ + void (*ignore) (GeditSpellCheckerDialog *dlg, + const gchar *word); + void (*ignore_all) (GeditSpellCheckerDialog *dlg, + const gchar *word); + void (*change) (GeditSpellCheckerDialog *dlg, + const gchar *word, + const gchar *change_to); + void (*change_all) (GeditSpellCheckerDialog *dlg, + const gchar *word, + const gchar *change_to); + void (*add_word_to_personal) (GeditSpellCheckerDialog *dlg, + const gchar *word); + +}; + +GType gedit_spell_checker_dialog_get_type (void) G_GNUC_CONST; + +/* Constructors */ +GtkWidget *gedit_spell_checker_dialog_new (const gchar *data_dir); +GtkWidget *gedit_spell_checker_dialog_new_from_spell_checker + (GeditSpellChecker *spell, + const gchar *data_dir); + +void gedit_spell_checker_dialog_set_spell_checker + (GeditSpellCheckerDialog *dlg, + GeditSpellChecker *spell); +void gedit_spell_checker_dialog_set_misspelled_word + (GeditSpellCheckerDialog *dlg, + const gchar* word, + gint len); + +void gedit_spell_checker_dialog_set_completed + (GeditSpellCheckerDialog *dlg); + +G_END_DECLS + +#endif /* __GEDIT_SPELL_CHECKER_DIALOG_H__ */ + diff --git a/plugins/spell/gedit-spell-checker-language.c b/plugins/spell/gedit-spell-checker-language.c new file mode 100755 index 00000000..ae7e2422 --- /dev/null +++ b/plugins/spell/gedit-spell-checker-language.c @@ -0,0 +1,439 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-checker-language.c + * This file is part of gedit + * + * Copyright (C) 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, 2006. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +/* Part of the code taked from Epiphany. + * + * Copyright (C) 2003, 2004 Christian Persch + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <enchant.h> + +#include <glib/gi18n.h> +#include <libxml/xmlreader.h> + +#include "gedit-spell-checker-language.h" + +#include <gedit/gedit-debug.h> + +#define ISO_639_DOMAIN "iso_639" +#define ISO_3166_DOMAIN "iso_3166" + +#define ISOCODESLOCALEDIR ISO_CODES_PREFIX "/share/locale" + +struct _GeditSpellCheckerLanguage +{ + gchar *abrev; + gchar *name; +}; + +static gboolean available_languages_initialized = FALSE; +static GSList *available_languages = NULL; + +static GHashTable *iso_639_table = NULL; +static GHashTable *iso_3166_table = NULL; + +static void +bind_iso_domains (void) +{ + static gboolean bound = FALSE; + + if (bound == FALSE) + { + bindtextdomain (ISO_639_DOMAIN, ISOCODESLOCALEDIR); + bind_textdomain_codeset (ISO_639_DOMAIN, "UTF-8"); + + bindtextdomain(ISO_3166_DOMAIN, ISOCODESLOCALEDIR); + bind_textdomain_codeset (ISO_3166_DOMAIN, "UTF-8"); + + bound = TRUE; + } +} + +static void +read_iso_639_entry (xmlTextReaderPtr reader, + GHashTable *table) +{ + xmlChar *code, *name; + + code = xmlTextReaderGetAttribute (reader, (const xmlChar *) "iso_639_1_code"); + name = xmlTextReaderGetAttribute (reader, (const xmlChar *) "name"); + + /* Get iso-639-2 code */ + if (code == NULL || code[0] == '\0') + { + xmlFree (code); + /* FIXME: use the 2T or 2B code? */ + code = xmlTextReaderGetAttribute (reader, (const xmlChar *) "iso_639_2T_code"); + } + + if (code != NULL && code[0] != '\0' && name != NULL && name[0] != '\0') + { + g_hash_table_insert (table, code, name); + } + else + { + xmlFree (code); + xmlFree (name); + } +} + +static void +read_iso_3166_entry (xmlTextReaderPtr reader, + GHashTable *table) +{ + xmlChar *code, *name; + + code = xmlTextReaderGetAttribute (reader, (const xmlChar *) "alpha_2_code"); + name = xmlTextReaderGetAttribute (reader, (const xmlChar *) "name"); + + if (code != NULL && code[0] != '\0' && name != NULL && name[0] != '\0') + { + char *lcode; + + lcode = g_ascii_strdown ((char *) code, -1); + xmlFree (code); + + /* g_print ("%s -> %s\n", lcode, name); */ + + g_hash_table_insert (table, lcode, name); + } + else + { + xmlFree (code); + xmlFree (name); + } +} + +typedef enum +{ + STATE_START, + STATE_STOP, + STATE_ENTRIES, +} ParserState; + +static void +load_iso_entries (int iso, + GFunc read_entry_func, + gpointer user_data) +{ + xmlTextReaderPtr reader; + ParserState state = STATE_START; + xmlChar iso_entries[32], iso_entry[32]; + char *filename; + int ret = -1; + + gedit_debug_message (DEBUG_PLUGINS, "Loading ISO-%d codes", iso); + + filename = g_strdup_printf (ISO_CODES_PREFIX "/share/xml/iso-codes/iso_%d.xml", iso); + reader = xmlNewTextReaderFilename (filename); + if (reader == NULL) goto out; + + xmlStrPrintf (iso_entries, sizeof (iso_entries), (const xmlChar *)"iso_%d_entries", iso); + xmlStrPrintf (iso_entry, sizeof (iso_entry), (const xmlChar *)"iso_%d_entry", iso); + + ret = xmlTextReaderRead (reader); + + while (ret == 1) + { + const xmlChar *tag; + xmlReaderTypes type; + + tag = xmlTextReaderConstName (reader); + type = xmlTextReaderNodeType (reader); + + if (state == STATE_ENTRIES && + type == XML_READER_TYPE_ELEMENT && + xmlStrEqual (tag, iso_entry)) + { + read_entry_func (reader, user_data); + } + else if (state == STATE_START && + type == XML_READER_TYPE_ELEMENT && + xmlStrEqual (tag, iso_entries)) + { + state = STATE_ENTRIES; + } + else if (state == STATE_ENTRIES && + type == XML_READER_TYPE_END_ELEMENT && + xmlStrEqual (tag, iso_entries)) + { + state = STATE_STOP; + } + else if (type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE || + type == XML_READER_TYPE_WHITESPACE || + type == XML_READER_TYPE_TEXT || + type == XML_READER_TYPE_COMMENT) + { + /* eat it */ + } + else + { + /* ignore it */ + } + + ret = xmlTextReaderRead (reader); + } + + xmlFreeTextReader (reader); + +out: + if (ret < 0 || state != STATE_STOP) + { + g_warning ("Failed to load ISO-%d codes from %s!\n", + iso, filename); + } + + g_free (filename); +} + +static GHashTable * +create_iso_639_table (void) +{ + GHashTable *table; + + bind_iso_domains (); + table = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) xmlFree, + (GDestroyNotify) xmlFree); + + load_iso_entries (639, (GFunc) read_iso_639_entry, table); + + return table; +} + +static GHashTable * +create_iso_3166_table (void) +{ + GHashTable *table; + + bind_iso_domains (); + table = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) xmlFree); + + load_iso_entries (3166, (GFunc) read_iso_3166_entry, table); + + return table; +} + +static char * +create_name_for_language (const char *code) +{ + char **str; + char *name = NULL; + const char *langname, *localename; + int len; + + g_return_val_if_fail (iso_639_table != NULL, NULL); + g_return_val_if_fail (iso_3166_table != NULL, NULL); + + str = g_strsplit (code, "_", -1); + len = g_strv_length (str); + g_return_val_if_fail (len != 0, NULL); + + langname = (const char *) g_hash_table_lookup (iso_639_table, str[0]); + + if (len == 1 && langname != NULL) + { + name = g_strdup (dgettext (ISO_639_DOMAIN, langname)); + } + else if (len == 2 && langname != NULL) + { + gchar *locale_code = g_ascii_strdown (str[1], -1); + + localename = (const char *) g_hash_table_lookup (iso_3166_table, locale_code); + g_free (locale_code); + + if (localename != NULL) + { + /* Translators: the first %s is the language name, and + * the second %s is the locale name. Example: + * "French (France)" + */ + name = g_strdup_printf (C_("language", "%s (%s)"), + dgettext (ISO_639_DOMAIN, langname), + dgettext (ISO_3166_DOMAIN, localename)); + } + else + { + name = g_strdup_printf (C_("language", "%s (%s)"), + dgettext (ISO_639_DOMAIN, langname), str[1]); + } + } + else + { + /* Translators: this refers to an unknown language code + * (one which isn't in our built-in list). + */ + name = g_strdup_printf (C_("language", "Unknown (%s)"), code); + } + + g_strfreev (str); + + return name; +} + +static void +enumerate_dicts (const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void * user_data) +{ + gchar *lang_name; + + GTree *dicts = (GTree *)user_data; + + lang_name = create_name_for_language (lang_tag); + g_return_if_fail (lang_name != NULL); + + /* g_print ("%s - %s\n", lang_tag, lang_name); */ + + g_tree_replace (dicts, g_strdup (lang_tag), lang_name); +} + +static gint +key_cmp (gconstpointer a, gconstpointer b, gpointer user_data) +{ + return strcmp (a, b); +} + +static gint +lang_cmp (const GeditSpellCheckerLanguage *a, + const GeditSpellCheckerLanguage *b) +{ + return g_utf8_collate (a->name, b->name); +} + +static gboolean +build_langs_list (const gchar *key, + const gchar *value, + gpointer data) +{ + GeditSpellCheckerLanguage *lang = g_new (GeditSpellCheckerLanguage, 1); + + lang->abrev = g_strdup (key); + lang->name = g_strdup (value); + + available_languages = g_slist_insert_sorted (available_languages, + lang, + (GCompareFunc)lang_cmp); + + return FALSE; +} + +const GSList * +gedit_spell_checker_get_available_languages (void) +{ + EnchantBroker *broker; + GTree *dicts; + + if (available_languages_initialized) + return available_languages; + + g_return_val_if_fail (available_languages == NULL, NULL); + + available_languages_initialized = TRUE; + + broker = enchant_broker_init (); + g_return_val_if_fail (broker != NULL, NULL); + + /* Use a GTree to efficiently remove duplicates while building the list */ + dicts = g_tree_new_full (key_cmp, + NULL, + (GDestroyNotify)g_free, + (GDestroyNotify)g_free); + + iso_639_table = create_iso_639_table (); + iso_3166_table = create_iso_3166_table (); + + enchant_broker_list_dicts (broker, enumerate_dicts, dicts); + + enchant_broker_free (broker); + + g_hash_table_destroy (iso_639_table); + g_hash_table_destroy (iso_3166_table); + + iso_639_table = NULL; + iso_3166_table = NULL; + + g_tree_foreach (dicts, (GTraverseFunc)build_langs_list, NULL); + + g_tree_destroy (dicts); + + return available_languages; +} + +const gchar * +gedit_spell_checker_language_to_string (const GeditSpellCheckerLanguage *lang) +{ + if (lang == NULL) + /* Translators: this refers the Default language used by the + * spell checker + */ + return C_("language", "Default"); + + return lang->name; +} + +const gchar * +gedit_spell_checker_language_to_key (const GeditSpellCheckerLanguage *lang) +{ + g_return_val_if_fail (lang != NULL, NULL); + + return lang->abrev; +} + +const GeditSpellCheckerLanguage * +gedit_spell_checker_language_from_key (const gchar *key) +{ + const GSList *langs; + + g_return_val_if_fail (key != NULL, NULL); + + langs = gedit_spell_checker_get_available_languages (); + + while (langs != NULL) + { + const GeditSpellCheckerLanguage *l = (const GeditSpellCheckerLanguage *)langs->data; + + if (g_ascii_strcasecmp (key, l->abrev) == 0) + return l; + + langs = g_slist_next (langs); + } + + return NULL; +} diff --git a/plugins/spell/gedit-spell-checker-language.h b/plugins/spell/gedit-spell-checker-language.h new file mode 100755 index 00000000..7e62de65 --- /dev/null +++ b/plugins/spell/gedit-spell-checker-language.h @@ -0,0 +1,51 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-checker-language.h + * This file is part of gedit + * + * Copyright (C) 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, 2006. See the AUTHORS file for a + * list of people on the gedit Team. + * See the ChangeLog files for a list of changes. + */ + +#ifndef __GEDIT_SPELL_CHECKER_LANGUAGE_H__ +#define __GEDIT_SPELL_CHECKER_LANGUAGE_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +typedef struct _GeditSpellCheckerLanguage GeditSpellCheckerLanguage; + +const gchar *gedit_spell_checker_language_to_string (const GeditSpellCheckerLanguage *lang); + +const gchar *gedit_spell_checker_language_to_key (const GeditSpellCheckerLanguage *lang); + +const GeditSpellCheckerLanguage *gedit_spell_checker_language_from_key (const gchar *key); + +/* GSList contains "GeditSpellCheckerLanguage*" items */ +const GSList *gedit_spell_checker_get_available_languages + (void); + +G_END_DECLS + +#endif /* __GEDIT_SPELL_CHECKER_LANGUAGE_H__ */ diff --git a/plugins/spell/gedit-spell-checker.c b/plugins/spell/gedit-spell-checker.c new file mode 100755 index 00000000..51b8d8a8 --- /dev/null +++ b/plugins/spell/gedit-spell-checker.c @@ -0,0 +1,520 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-checker.c + * This file is part of gedit + * + * 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, 2002-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 <enchant.h> + +#include <glib/gi18n.h> +#include <glib.h> + +#include "gedit-spell-checker.h" +#include "gedit-spell-utils.h" +#include "gedit-spell-marshal.h" + +struct _GeditSpellChecker +{ + GObject parent_instance; + + EnchantDict *dict; + EnchantBroker *broker; + const GeditSpellCheckerLanguage *active_lang; +}; + +/* GObject properties */ +enum { + PROP_0 = 0, + PROP_LANGUAGE, + LAST_PROP +}; + +/* Signals */ +enum { + ADD_WORD_TO_PERSONAL = 0, + ADD_WORD_TO_SESSION, + SET_LANGUAGE, + CLEAR_SESSION, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE(GeditSpellChecker, gedit_spell_checker, G_TYPE_OBJECT) + +static void +gedit_spell_checker_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + /* + GeditSpellChecker *spell = GEDIT_SPELL_CHECKER (object); + */ + + switch (prop_id) + { + case PROP_LANGUAGE: + /* TODO */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gedit_spell_checker_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + /* + GeditSpellChecker *spell = GEDIT_SPELL_CHECKER (object); + */ + + switch (prop_id) + { + case PROP_LANGUAGE: + /* TODO */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gedit_spell_checker_finalize (GObject *object) +{ + GeditSpellChecker *spell_checker; + + g_return_if_fail (GEDIT_IS_SPELL_CHECKER (object)); + + spell_checker = GEDIT_SPELL_CHECKER (object); + + if (spell_checker->dict != NULL) + enchant_broker_free_dict (spell_checker->broker, spell_checker->dict); + + if (spell_checker->broker != NULL) + enchant_broker_free (spell_checker->broker); + + G_OBJECT_CLASS (gedit_spell_checker_parent_class)->finalize (object); +} + +static void +gedit_spell_checker_class_init (GeditSpellCheckerClass * klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gedit_spell_checker_set_property; + object_class->get_property = gedit_spell_checker_get_property; + + object_class->finalize = gedit_spell_checker_finalize; + + g_object_class_install_property (object_class, + PROP_LANGUAGE, + g_param_spec_pointer ("language", + "Language", + "The language used by the spell checker", + G_PARAM_READWRITE)); + + signals[ADD_WORD_TO_PERSONAL] = + g_signal_new ("add_word_to_personal", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerClass, add_word_to_personal), + NULL, NULL, + gedit_marshal_VOID__STRING_INT, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_INT); + + signals[ADD_WORD_TO_SESSION] = + g_signal_new ("add_word_to_session", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerClass, add_word_to_session), + NULL, NULL, + gedit_marshal_VOID__STRING_INT, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_INT); + + signals[SET_LANGUAGE] = + g_signal_new ("set_language", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerClass, set_language), + NULL, NULL, + gedit_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[CLEAR_SESSION] = + g_signal_new ("clear_session", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditSpellCheckerClass, clear_session), + NULL, NULL, + gedit_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +static void +gedit_spell_checker_init (GeditSpellChecker *spell_checker) +{ + spell_checker->broker = enchant_broker_init (); + spell_checker->dict = NULL; + spell_checker->active_lang = NULL; +} + +GeditSpellChecker * +gedit_spell_checker_new (void) +{ + GeditSpellChecker *spell; + + spell = GEDIT_SPELL_CHECKER ( + g_object_new (GEDIT_TYPE_SPELL_CHECKER, NULL)); + + g_return_val_if_fail (spell != NULL, NULL); + + return spell; +} + +static gboolean +lazy_init (GeditSpellChecker *spell, + const GeditSpellCheckerLanguage *language) +{ + if (spell->dict != NULL) + return TRUE; + + g_return_val_if_fail (spell->broker != NULL, FALSE); + + spell->active_lang = NULL; + + if (language != NULL) + { + spell->active_lang = language; + } + else + { + /* First try to get a default language */ + const GeditSpellCheckerLanguage *l; + gint i = 0; + const gchar * const *lang_tags = g_get_language_names (); + + while (lang_tags [i]) + { + l = gedit_spell_checker_language_from_key (lang_tags [i]); + + if (l != NULL) + { + spell->active_lang = l; + break; + } + + i++; + } + } + + /* Second try to get a default language */ + if (spell->active_lang == NULL) + spell->active_lang = gedit_spell_checker_language_from_key ("en_US"); + + /* Last try to get a default language */ + if (spell->active_lang == NULL) + { + const GSList *langs; + langs = gedit_spell_checker_get_available_languages (); + if (langs != NULL) + spell->active_lang = (const GeditSpellCheckerLanguage *)langs->data; + } + + if (spell->active_lang != NULL) + { + const gchar *key; + + key = gedit_spell_checker_language_to_key (spell->active_lang); + + spell->dict = enchant_broker_request_dict (spell->broker, + key); + } + + if (spell->dict == NULL) + { + spell->active_lang = NULL; + + if (language != NULL) + g_warning ("Spell checker plugin: cannot select a default language."); + + return FALSE; + } + + return TRUE; +} + +gboolean +gedit_spell_checker_set_language (GeditSpellChecker *spell, + const GeditSpellCheckerLanguage *language) +{ + gboolean ret; + + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE); + + if (spell->dict != NULL) + { + enchant_broker_free_dict (spell->broker, spell->dict); + spell->dict = NULL; + } + + ret = lazy_init (spell, language); + + if (ret) + g_signal_emit (G_OBJECT (spell), signals[SET_LANGUAGE], 0, language); + else + g_warning ("Spell checker plugin: cannot use language %s.", + gedit_spell_checker_language_to_string (language)); + + return ret; +} + +const GeditSpellCheckerLanguage * +gedit_spell_checker_get_language (GeditSpellChecker *spell) +{ + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), NULL); + + if (!lazy_init (spell, spell->active_lang)) + return NULL; + + return spell->active_lang; +} + +gboolean +gedit_spell_checker_check_word (GeditSpellChecker *spell, + const gchar *word, + gssize len) +{ + gint enchant_result; + gboolean res = FALSE; + + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE); + g_return_val_if_fail (word != NULL, FALSE); + + if (!lazy_init (spell, spell->active_lang)) + return FALSE; + + if (len < 0) + len = strlen (word); + + if (strcmp (word, "gedit") == 0) + return TRUE; + + if (gedit_spell_utils_is_digit (word, len)) + return TRUE; + + g_return_val_if_fail (spell->dict != NULL, FALSE); + enchant_result = enchant_dict_check (spell->dict, word, len); + + switch (enchant_result) + { + case -1: + /* error */ + res = FALSE; + + g_warning ("Spell checker plugin: error checking word '%s' (%s).", + word, enchant_dict_get_error (spell->dict)); + + break; + case 1: + /* it is not in the directory */ + res = FALSE; + break; + case 0: + /* is is in the directory */ + res = TRUE; + break; + default: + g_return_val_if_reached (FALSE); + } + + return res; +} + + +/* return NULL on error or if no suggestions are found */ +GSList * +gedit_spell_checker_get_suggestions (GeditSpellChecker *spell, + const gchar *word, + gssize len) +{ + gchar **suggestions; + size_t n_suggestions = 0; + GSList *suggestions_list = NULL; + gint i; + + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), NULL); + g_return_val_if_fail (word != NULL, NULL); + + if (!lazy_init (spell, spell->active_lang)) + return NULL; + + g_return_val_if_fail (spell->dict != NULL, NULL); + + if (len < 0) + len = strlen (word); + + suggestions = enchant_dict_suggest (spell->dict, word, len, &n_suggestions); + + if (n_suggestions == 0) + return NULL; + + g_return_val_if_fail (suggestions != NULL, NULL); + + for (i = 0; i < (gint)n_suggestions; i++) + { + suggestions_list = g_slist_prepend (suggestions_list, + suggestions[i]); + } + + /* The single suggestions will be freed by the caller */ + g_free (suggestions); + + suggestions_list = g_slist_reverse (suggestions_list); + + return suggestions_list; +} + +gboolean +gedit_spell_checker_add_word_to_personal (GeditSpellChecker *spell, + const gchar *word, + gssize len) +{ + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE); + g_return_val_if_fail (word != NULL, FALSE); + + if (!lazy_init (spell, spell->active_lang)) + return FALSE; + + g_return_val_if_fail (spell->dict != NULL, FALSE); + + if (len < 0) + len = strlen (word); + + enchant_dict_add_to_pwl (spell->dict, word, len); + + g_signal_emit (G_OBJECT (spell), signals[ADD_WORD_TO_PERSONAL], 0, word, len); + + return TRUE; +} + +gboolean +gedit_spell_checker_add_word_to_session (GeditSpellChecker *spell, + const gchar *word, + gssize len) +{ + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE); + g_return_val_if_fail (word != NULL, FALSE); + + if (!lazy_init (spell, spell->active_lang)) + return FALSE; + + g_return_val_if_fail (spell->dict != NULL, FALSE); + + if (len < 0) + len = strlen (word); + + enchant_dict_add_to_session (spell->dict, word, len); + + g_signal_emit (G_OBJECT (spell), signals[ADD_WORD_TO_SESSION], 0, word, len); + + return TRUE; +} + +gboolean +gedit_spell_checker_clear_session (GeditSpellChecker *spell) +{ + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE); + + /* free and re-request dictionary */ + if (spell->dict != NULL) + { + enchant_broker_free_dict (spell->broker, spell->dict); + spell->dict = NULL; + } + + if (!lazy_init (spell, spell->active_lang)) + return FALSE; + + g_signal_emit (G_OBJECT (spell), signals[CLEAR_SESSION], 0); + + return TRUE; +} + +/* + * Informs dictionary, that word 'word' will be replaced/corrected by word + * 'replacement' + */ +gboolean +gedit_spell_checker_set_correction (GeditSpellChecker *spell, + const gchar *word, + gssize w_len, + const gchar *replacement, + gssize r_len) +{ + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE); + g_return_val_if_fail (word != NULL, FALSE); + g_return_val_if_fail (replacement != NULL, FALSE); + + if (!lazy_init (spell, spell->active_lang)) + return FALSE; + + g_return_val_if_fail (spell->dict != NULL, FALSE); + + if (w_len < 0) + w_len = strlen (word); + + if (r_len < 0) + r_len = strlen (replacement); + + enchant_dict_store_replacement (spell->dict, + word, + w_len, + replacement, + r_len); + + return TRUE; +} + diff --git a/plugins/spell/gedit-spell-checker.h b/plugins/spell/gedit-spell-checker.h new file mode 100755 index 00000000..0cc69d31 --- /dev/null +++ b/plugins/spell/gedit-spell-checker.h @@ -0,0 +1,109 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-checker.h + * This file is part of gedit + * + * 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, 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_SPELL_CHECKER_H__ +#define __GEDIT_SPELL_CHECKER_H__ + +#include <glib.h> +#include <glib-object.h> + +#include "gedit-spell-checker-language.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_SPELL_CHECKER (gedit_spell_checker_get_type ()) +#define GEDIT_SPELL_CHECKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_SPELL_CHECKER, GeditSpellChecker)) +#define GEDIT_SPELL_CHECKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_SPELL_CHECKER, GeditSpellChecker)) +#define GEDIT_IS_SPELL_CHECKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_SPELL_CHECKER)) +#define GEDIT_IS_SPELL_CHECKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SPELL_CHECKER)) +#define GEDIT_SPELL_CHECKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_SPELL_CHECKER, GeditSpellChecker)) + +typedef struct _GeditSpellChecker GeditSpellChecker; + +typedef struct _GeditSpellCheckerClass GeditSpellCheckerClass; + +struct _GeditSpellCheckerClass +{ + GObjectClass parent_class; + + /* Signals */ + void (*add_word_to_personal) (GeditSpellChecker *spell, + const gchar *word, + gint len); + + void (*add_word_to_session) (GeditSpellChecker *spell, + const gchar *word, + gint len); + + void (*set_language) (GeditSpellChecker *spell, + const GeditSpellCheckerLanguage *lang); + + void (*clear_session) (GeditSpellChecker *spell); +}; + + +GType gedit_spell_checker_get_type (void) G_GNUC_CONST; + +/* Constructors */ +GeditSpellChecker *gedit_spell_checker_new (void); + +gboolean gedit_spell_checker_set_language (GeditSpellChecker *spell, + const GeditSpellCheckerLanguage *lang); +const GeditSpellCheckerLanguage + *gedit_spell_checker_get_language (GeditSpellChecker *spell); + +gboolean gedit_spell_checker_check_word (GeditSpellChecker *spell, + const gchar *word, + gssize len); + +GSList *gedit_spell_checker_get_suggestions (GeditSpellChecker *spell, + const gchar *word, + gssize len); + +gboolean gedit_spell_checker_add_word_to_personal + (GeditSpellChecker *spell, + const gchar *word, + gssize len); + +gboolean gedit_spell_checker_add_word_to_session + (GeditSpellChecker *spell, + const gchar *word, + gssize len); + +gboolean gedit_spell_checker_clear_session (GeditSpellChecker *spell); + +gboolean gedit_spell_checker_set_correction (GeditSpellChecker *spell, + const gchar *word, + gssize w_len, + const gchar *replacement, + gssize r_len); +G_END_DECLS + +#endif /* __GEDIT_SPELL_CHECKER_H__ */ + diff --git a/plugins/spell/gedit-spell-language-dialog.c b/plugins/spell/gedit-spell-language-dialog.c new file mode 100755 index 00000000..1abba17f --- /dev/null +++ b/plugins/spell/gedit-spell-language-dialog.c @@ -0,0 +1,309 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-language-dialog.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 <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-help.h> +#include "gedit-spell-language-dialog.h" +#include "gedit-spell-checker-language.h" + + +enum +{ + COLUMN_LANGUAGE_NAME = 0, + COLUMN_LANGUAGE_POINTER, + ENCODING_NUM_COLS +}; + + +struct _GeditSpellLanguageDialog +{ + GtkDialog dialog; + + GtkWidget *languages_treeview; + GtkTreeModel *model; +}; + +G_DEFINE_TYPE(GeditSpellLanguageDialog, gedit_spell_language_dialog, GTK_TYPE_DIALOG) + + +static void +gedit_spell_language_dialog_class_init (GeditSpellLanguageDialogClass *klass) +{ + /* GObjectClass *object_class = G_OBJECT_CLASS (klass); */ +} + +static void +dialog_response_handler (GtkDialog *dlg, + gint res_id) +{ + if (res_id == GTK_RESPONSE_HELP) + { + gedit_help_display (GTK_WINDOW (dlg), + NULL, + "gedit-spell-checker-plugin"); + + g_signal_stop_emission_by_name (dlg, "response"); + } +} + +static void +scroll_to_selected (GtkTreeView *tree_view) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (tree_view); + g_return_if_fail (model != NULL); + + /* Scroll to selected */ + selection = gtk_tree_view_get_selection (tree_view); + g_return_if_fail (selection != NULL); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + GtkTreePath* path; + + path = gtk_tree_model_get_path (model, &iter); + g_return_if_fail (path != NULL); + + gtk_tree_view_scroll_to_cell (tree_view, + path, NULL, TRUE, 1.0, 0.0); + gtk_tree_path_free (path); + } +} + +static void +language_row_activated (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GeditSpellLanguageDialog *dialog) +{ + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); +} + +static void +create_dialog (GeditSpellLanguageDialog *dlg, + const gchar *data_dir) +{ + GtkWidget *error_widget; + GtkWidget *content; + gboolean ret; + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + gchar *ui_file; + gchar *root_objects[] = { + "content", + NULL + }; + + 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), _("Set language")); + gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE); + gtk_window_set_modal (GTK_WINDOW (dlg), TRUE); + 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); + + ui_file = g_build_filename (data_dir, "languages-dialog.ui", NULL); + ret = gedit_utils_get_ui_objects (ui_file, + root_objects, + &error_widget, + "content", &content, + "languages_treeview", &dlg->languages_treeview, + NULL); + g_free (ui_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))), + content, TRUE, TRUE, 0); + g_object_unref (content); + gtk_container_set_border_width (GTK_CONTAINER (content), 5); + + dlg->model = GTK_TREE_MODEL (gtk_list_store_new (ENCODING_NUM_COLS, + G_TYPE_STRING, + G_TYPE_POINTER)); + + gtk_tree_view_set_model (GTK_TREE_VIEW (dlg->languages_treeview), + dlg->model); + + g_object_unref (dlg->model); + + /* Add the encoding column */ + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Languages"), + cell, + "text", + COLUMN_LANGUAGE_NAME, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->languages_treeview), + column); + + gtk_tree_view_set_search_column (GTK_TREE_VIEW (dlg->languages_treeview), + COLUMN_LANGUAGE_NAME); + + g_signal_connect (dlg->languages_treeview, + "realize", + G_CALLBACK (scroll_to_selected), + dlg); + g_signal_connect (dlg->languages_treeview, + "row-activated", + G_CALLBACK (language_row_activated), + dlg); +} + +static void +gedit_spell_language_dialog_init (GeditSpellLanguageDialog *dlg) +{ + +} + +static void +populate_language_list (GeditSpellLanguageDialog *dlg, + const GeditSpellCheckerLanguage *cur_lang) +{ + GtkListStore *store; + GtkTreeIter iter; + + const GSList* langs; + + /* create list store */ + store = GTK_LIST_STORE (dlg->model); + + langs = gedit_spell_checker_get_available_languages (); + + while (langs) + { + const gchar *name; + + name = gedit_spell_checker_language_to_string ((const GeditSpellCheckerLanguage*)langs->data); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_LANGUAGE_NAME, name, + COLUMN_LANGUAGE_POINTER, langs->data, + -1); + + if (langs->data == cur_lang) + { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->languages_treeview)); + g_return_if_fail (selection != NULL); + + gtk_tree_selection_select_iter (selection, &iter); + } + + langs = g_slist_next (langs); + } +} + +GtkWidget * +gedit_spell_language_dialog_new (GtkWindow *parent, + const GeditSpellCheckerLanguage *cur_lang, + const gchar *data_dir) +{ + GeditSpellLanguageDialog *dlg; + + g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL); + + dlg = g_object_new (GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, NULL); + + create_dialog (dlg, data_dir); + + populate_language_list (dlg, cur_lang); + + gtk_window_set_transient_for (GTK_WINDOW (dlg), parent); + gtk_widget_grab_focus (dlg->languages_treeview); + + return GTK_WIDGET (dlg); +} + +const GeditSpellCheckerLanguage * +gedit_spell_language_get_selected_language (GeditSpellLanguageDialog *dlg) +{ + GValue value = {0, }; + const GeditSpellCheckerLanguage* lang; + + GtkTreeIter iter; + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->languages_treeview)); + g_return_val_if_fail (selection != NULL, NULL); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + return NULL; + + gtk_tree_model_get_value (dlg->model, + &iter, + COLUMN_LANGUAGE_POINTER, + &value); + + lang = (const GeditSpellCheckerLanguage* ) g_value_get_pointer (&value); + + return lang; +} + diff --git a/plugins/spell/gedit-spell-language-dialog.h b/plugins/spell/gedit-spell-language-dialog.h new file mode 100755 index 00000000..4ae9c97d --- /dev/null +++ b/plugins/spell/gedit-spell-language-dialog.h @@ -0,0 +1,67 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gedit-spell-language-dialog.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_SPELL_LANGUAGE_DIALOG_H__ +#define __GEDIT_SPELL_LANGUAGE_DIALOG_H__ + +#include <gtk/gtk.h> +#include "gedit-spell-checker-language.h" + +G_BEGIN_DECLS + +#define GEDIT_TYPE_SPELL_LANGUAGE_DIALOG (gedit_spell_language_dialog_get_type()) +#define GEDIT_SPELL_LANGUAGE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, GeditSpellLanguageDialog)) +#define GEDIT_SPELL_LANGUAGE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, GeditSpellLanguageDialogClass)) +#define GEDIT_IS_SPELL_LANGUAGE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG)) +#define GEDIT_IS_SPELL_LANGUAGE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG)) +#define GEDIT_SPELL_LANGUAGE_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, GeditSpellLanguageDialogClass)) + + +typedef struct _GeditSpellLanguageDialog GeditSpellLanguageDialog; + +typedef struct _GeditSpellLanguageDialogClass GeditSpellLanguageDialogClass; + +struct _GeditSpellLanguageDialogClass +{ + GtkDialogClass parent_class; +}; + +GType gedit_spell_language_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_spell_language_dialog_new (GtkWindow *parent, + const GeditSpellCheckerLanguage *cur_lang, + const gchar *data_dir); + +const GeditSpellCheckerLanguage * + gedit_spell_language_get_selected_language (GeditSpellLanguageDialog *dlg); + +G_END_DECLS + +#endif /* __GEDIT_SPELL_LANGUAGE_DIALOG_H__ */ + diff --git a/plugins/spell/gedit-spell-marshal.list b/plugins/spell/gedit-spell-marshal.list new file mode 100755 index 00000000..007dcf7d --- /dev/null +++ b/plugins/spell/gedit-spell-marshal.list @@ -0,0 +1,6 @@ +VOID:STRING +VOID:STRING,STRING +VOID:STRING,INT +VOID:POINTER +VOID:VOID + diff --git a/plugins/spell/gedit-spell-plugin.c b/plugins/spell/gedit-spell-plugin.c new file mode 100755 index 00000000..6ef78e75 --- /dev/null +++ b/plugins/spell/gedit-spell-plugin.c @@ -0,0 +1,1217 @@ +/* + * gedit-spell-plugin.c + * + * 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, 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$ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gedit-spell-plugin.h" +#include "gedit-spell-utils.h" + +#include <string.h> /* For strlen */ + +#include <glib/gi18n.h> +#include <gmodule.h> + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-prefs-manager.h> +#include <gedit/gedit-statusbar.h> + +#include "gedit-spell-checker.h" +#include "gedit-spell-checker-dialog.h" +#include "gedit-spell-language-dialog.h" +#include "gedit-automatic-spell-checker.h" + +#ifdef G_OS_WIN32 +#include <gedit/gedit-metadata-manager.h> +#define GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE "spell-language" +#define GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED "spell-enabled" +#else +#define GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE "metadata::gedit-spell-language" +#define GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED "metadata::gedit-spell-enabled" +#endif + +#define WINDOW_DATA_KEY "GeditSpellPluginWindowData" +#define MENU_PATH "/MenuBar/ToolsMenu/ToolsOps_1" + +#define GEDIT_SPELL_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_SPELL_PLUGIN, \ + GeditSpellPluginPrivate)) + +GEDIT_PLUGIN_REGISTER_TYPE(GeditSpellPlugin, gedit_spell_plugin) + +typedef struct +{ + GtkActionGroup *action_group; + guint ui_id; + guint message_cid; + gulong tab_added_id; + gulong tab_removed_id; +} WindowData; + +typedef struct +{ + GeditPlugin *plugin; + GeditWindow *window; +} ActionData; + +static void spell_cb (GtkAction *action, ActionData *action_data); +static void set_language_cb (GtkAction *action, ActionData *action_data); +static void auto_spell_cb (GtkAction *action, GeditWindow *window); + +/* UI actions. */ +static const GtkActionEntry action_entries[] = +{ + { "CheckSpell", + GTK_STOCK_SPELL_CHECK, + N_("_Check Spelling..."), + "<shift>F7", + N_("Check the current document for incorrect spelling"), + G_CALLBACK (spell_cb) + }, + + { "ConfigSpell", + NULL, + N_("Set _Language..."), + NULL, + N_("Set the language of the current document"), + G_CALLBACK (set_language_cb) + } +}; + +static const GtkToggleActionEntry toggle_action_entries[] = +{ + { "AutoSpell", + NULL, + N_("_Autocheck Spelling"), + NULL, + N_("Automatically spell-check the current document"), + G_CALLBACK (auto_spell_cb), + FALSE + } +}; + +typedef struct _CheckRange CheckRange; + +struct _CheckRange +{ + GtkTextMark *start_mark; + GtkTextMark *end_mark; + + gint mw_start; /* misspelled word start */ + gint mw_end; /* end */ + + GtkTextMark *current_mark; +}; + +static GQuark spell_checker_id = 0; +static GQuark check_range_id = 0; + +static void +gedit_spell_plugin_init (GeditSpellPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin initializing"); +} + +static void +gedit_spell_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin finalizing"); + + G_OBJECT_CLASS (gedit_spell_plugin_parent_class)->finalize (object); +} + +static void +set_spell_language_cb (GeditSpellChecker *spell, + const GeditSpellCheckerLanguage *lang, + GeditDocument *doc) +{ + const gchar *key; + + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + g_return_if_fail (lang != NULL); + + key = gedit_spell_checker_language_to_key (lang); + g_return_if_fail (key != NULL); + + gedit_document_set_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE, + key, NULL); +} + +static void +set_language_from_metadata (GeditSpellChecker *spell, + GeditDocument *doc) +{ + const GeditSpellCheckerLanguage *lang = NULL; + gchar *value = NULL; + + value = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE); + + if (value != NULL) + { + lang = gedit_spell_checker_language_from_key (value); + g_free (value); + } + + if (lang != NULL) + { + g_signal_handlers_block_by_func (spell, set_spell_language_cb, doc); + gedit_spell_checker_set_language (spell, lang); + g_signal_handlers_unblock_by_func (spell, set_spell_language_cb, doc); + } +} + +static GeditSpellChecker * +get_spell_checker_from_document (GeditDocument *doc) +{ + GeditSpellChecker *spell; + gpointer data; + + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (doc != NULL, NULL); + + data = g_object_get_qdata (G_OBJECT (doc), spell_checker_id); + + if (data == NULL) + { + spell = gedit_spell_checker_new (); + + set_language_from_metadata (spell, doc); + + g_object_set_qdata_full (G_OBJECT (doc), + spell_checker_id, + spell, + (GDestroyNotify) g_object_unref); + + g_signal_connect (spell, + "set_language", + G_CALLBACK (set_spell_language_cb), + doc); + } + else + { + g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (data), NULL); + spell = GEDIT_SPELL_CHECKER (data); + } + + return spell; +} + +static CheckRange * +get_check_range (GeditDocument *doc) +{ + CheckRange *range; + + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (doc != NULL, NULL); + + range = (CheckRange *) g_object_get_qdata (G_OBJECT (doc), check_range_id); + + return range; +} + +static void +update_current (GeditDocument *doc, + gint current) +{ + CheckRange *range; + GtkTextIter iter; + GtkTextIter end_iter; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (doc != NULL); + g_return_if_fail (current >= 0); + + range = get_check_range (doc); + g_return_if_fail (range != NULL); + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), + &iter, current); + + if (!gtk_text_iter_inside_word (&iter)) + { + /* if we're not inside a word, + * we must be in some spaces. + * skip forward to the beginning of the next word. */ + if (!gtk_text_iter_is_end (&iter)) + { + gtk_text_iter_forward_word_end (&iter); + gtk_text_iter_backward_word_start (&iter); + } + } + else + { + if (!gtk_text_iter_starts_word (&iter)) + gtk_text_iter_backward_word_start (&iter); + } + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc), + &end_iter, + range->end_mark); + + if (gtk_text_iter_compare (&end_iter, &iter) < 0) + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + range->current_mark, + &end_iter); + } + else + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + range->current_mark, + &iter); + } +} + +static void +set_check_range (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end) +{ + CheckRange *range; + GtkTextIter iter; + + gedit_debug (DEBUG_PLUGINS); + + range = get_check_range (doc); + + if (range == NULL) + { + gedit_debug_message (DEBUG_PLUGINS, "There was not a previous check range"); + + gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &iter); + + range = g_new0 (CheckRange, 1); + + range->start_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "check_range_start_mark", &iter, TRUE); + + range->end_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "check_range_end_mark", &iter, FALSE); + + range->current_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "check_range_current_mark", &iter, TRUE); + + g_object_set_qdata_full (G_OBJECT (doc), + check_range_id, + range, + (GDestroyNotify)g_free); + } + + if (gedit_spell_utils_skip_no_spell_check (start, end)) + { + if (!gtk_text_iter_inside_word (end)) + { + /* if we're neither inside a word, + * we must be in some spaces. + * skip backward to the end of the previous word. */ + if (!gtk_text_iter_is_end (end)) + { + gtk_text_iter_backward_word_start (end); + gtk_text_iter_forward_word_end (end); + } + } + else + { + if (!gtk_text_iter_ends_word (end)) + gtk_text_iter_forward_word_end (end); + } + } + else + { + /* no spell checking in the specified range */ + start = end; + } + + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + range->start_mark, + start); + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + range->end_mark, + end); + + range->mw_start = -1; + range->mw_end = -1; + + update_current (doc, gtk_text_iter_get_offset (start)); +} + +static gchar * +get_current_word (GeditDocument *doc, gint *start, gint *end) +{ + const CheckRange *range; + GtkTextIter end_iter; + GtkTextIter current_iter; + gint range_end; + + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (doc != NULL, NULL); + g_return_val_if_fail (start != NULL, NULL); + g_return_val_if_fail (end != NULL, NULL); + + range = get_check_range (doc); + g_return_val_if_fail (range != NULL, NULL); + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc), + &end_iter, range->end_mark); + + range_end = gtk_text_iter_get_offset (&end_iter); + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc), + ¤t_iter, range->current_mark); + + end_iter = current_iter; + + if (!gtk_text_iter_is_end (&end_iter)) + { + gedit_debug_message (DEBUG_PLUGINS, "Current is not end"); + + gtk_text_iter_forward_word_end (&end_iter); + } + + *start = gtk_text_iter_get_offset (¤t_iter); + *end = MIN (gtk_text_iter_get_offset (&end_iter), range_end); + + gedit_debug_message (DEBUG_PLUGINS, "Current word extends [%d, %d]", *start, *end); + + if (!(*start < *end)) + return NULL; + + return gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), + ¤t_iter, + &end_iter, + TRUE); +} + +static gboolean +goto_next_word (GeditDocument *doc) +{ + CheckRange *range; + GtkTextIter current_iter; + GtkTextIter old_current_iter; + GtkTextIter end_iter; + + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (doc != NULL, FALSE); + + range = get_check_range (doc); + g_return_val_if_fail (range != NULL, FALSE); + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc), + ¤t_iter, + range->current_mark); + gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &end_iter); + + old_current_iter = current_iter; + + gtk_text_iter_forward_word_ends (¤t_iter, 2); + gtk_text_iter_backward_word_start (¤t_iter); + + if (gedit_spell_utils_skip_no_spell_check (¤t_iter, &end_iter) && + (gtk_text_iter_compare (&old_current_iter, ¤t_iter) < 0) && + (gtk_text_iter_compare (¤t_iter, &end_iter) < 0)) + { + update_current (doc, gtk_text_iter_get_offset (¤t_iter)); + return TRUE; + } + + return FALSE; +} + +static gchar * +get_next_misspelled_word (GeditView *view) +{ + GeditDocument *doc; + CheckRange *range; + gint start, end; + gchar *word; + GeditSpellChecker *spell; + + g_return_val_if_fail (view != NULL, NULL); + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + g_return_val_if_fail (doc != NULL, NULL); + + range = get_check_range (doc); + g_return_val_if_fail (range != NULL, NULL); + + spell = get_spell_checker_from_document (doc); + g_return_val_if_fail (spell != NULL, NULL); + + word = get_current_word (doc, &start, &end); + if (word == NULL) + return NULL; + + gedit_debug_message (DEBUG_PLUGINS, "Word to check: %s", word); + + while (gedit_spell_checker_check_word (spell, word, -1)) + { + g_free (word); + + if (!goto_next_word (doc)) + return NULL; + + /* may return null if we reached the end of the selection */ + word = get_current_word (doc, &start, &end); + if (word == NULL) + return NULL; + + gedit_debug_message (DEBUG_PLUGINS, "Word to check: %s", word); + } + + if (!goto_next_word (doc)) + update_current (doc, gtk_text_buffer_get_char_count (GTK_TEXT_BUFFER (doc))); + + if (word != NULL) + { + GtkTextIter s, e; + + range->mw_start = start; + range->mw_end = end; + + gedit_debug_message (DEBUG_PLUGINS, "Select [%d, %d]", start, end); + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &s, start); + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &e, end); + + gtk_text_buffer_select_range (GTK_TEXT_BUFFER (doc), &s, &e); + + gedit_view_scroll_to_cursor (view); + } + else + { + range->mw_start = -1; + range->mw_end = -1; + } + + return word; +} + +static void +ignore_cb (GeditSpellCheckerDialog *dlg, + const gchar *w, + GeditView *view) +{ + gchar *word = NULL; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (w != NULL); + g_return_if_fail (view != NULL); + + word = get_next_misspelled_word (view); + if (word == NULL) + { + gedit_spell_checker_dialog_set_completed (dlg); + + return; + } + + gedit_spell_checker_dialog_set_misspelled_word (GEDIT_SPELL_CHECKER_DIALOG (dlg), + word, + -1); + + g_free (word); +} + +static void +change_cb (GeditSpellCheckerDialog *dlg, + const gchar *word, + const gchar *change, + GeditView *view) +{ + GeditDocument *doc; + CheckRange *range; + gchar *w = NULL; + GtkTextIter start, end; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (view != NULL); + g_return_if_fail (word != NULL); + g_return_if_fail (change != NULL); + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + g_return_if_fail (doc != NULL); + + range = get_check_range (doc); + g_return_if_fail (range != NULL); + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &start, range->mw_start); + if (range->mw_end < 0) + gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &end); + else + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &end, range->mw_end); + + w = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), &start, &end, TRUE); + g_return_if_fail (w != NULL); + + if (strcmp (w, word) != 0) + { + g_free (w); + return; + } + + g_free (w); + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER(doc)); + + gtk_text_buffer_delete (GTK_TEXT_BUFFER (doc), &start, &end); + gtk_text_buffer_insert (GTK_TEXT_BUFFER (doc), &start, change, -1); + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER(doc)); + + update_current (doc, range->mw_start + g_utf8_strlen (change, -1)); + + /* go to next misspelled word */ + ignore_cb (dlg, word, view); +} + +static void +change_all_cb (GeditSpellCheckerDialog *dlg, + const gchar *word, + const gchar *change, + GeditView *view) +{ + GeditDocument *doc; + CheckRange *range; + gchar *w = NULL; + GtkTextIter start, end; + gint flags = 0; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (view != NULL); + g_return_if_fail (word != NULL); + g_return_if_fail (change != NULL); + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + g_return_if_fail (doc != NULL); + + range = get_check_range (doc); + g_return_if_fail (range != NULL); + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &start, range->mw_start); + if (range->mw_end < 0) + gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &end); + else + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &end, range->mw_end); + + w = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), &start, &end, TRUE); + g_return_if_fail (w != NULL); + + if (strcmp (w, word) != 0) + { + g_free (w); + return; + } + + g_free (w); + + GEDIT_SEARCH_SET_CASE_SENSITIVE (flags, TRUE); + GEDIT_SEARCH_SET_ENTIRE_WORD (flags, TRUE); + + /* CHECK: currently this function does escaping etc */ + gedit_document_replace_all (doc, word, change, flags); + + update_current (doc, range->mw_start + g_utf8_strlen (change, -1)); + + /* go to next misspelled word */ + ignore_cb (dlg, word, view); +} + +static void +add_word_cb (GeditSpellCheckerDialog *dlg, + const gchar *word, + GeditView *view) +{ + g_return_if_fail (view != NULL); + g_return_if_fail (word != NULL); + + /* go to next misspelled word */ + ignore_cb (dlg, word, view); +} + +static void +language_dialog_response (GtkDialog *dlg, + gint res_id, + GeditSpellChecker *spell) +{ + if (res_id == GTK_RESPONSE_OK) + { + const GeditSpellCheckerLanguage *lang; + + lang = gedit_spell_language_get_selected_language (GEDIT_SPELL_LANGUAGE_DIALOG (dlg)); + if (lang != NULL) + gedit_spell_checker_set_language (spell, lang); + } + + gtk_widget_destroy (GTK_WIDGET (dlg)); +} + +static void +set_language_cb (GtkAction *action, + ActionData *action_data) +{ + GeditDocument *doc; + GeditSpellChecker *spell; + const GeditSpellCheckerLanguage *lang; + GtkWidget *dlg; + GtkWindowGroup *wg; + gchar *data_dir; + + gedit_debug (DEBUG_PLUGINS); + + doc = gedit_window_get_active_document (action_data->window); + g_return_if_fail (doc != NULL); + + spell = get_spell_checker_from_document (doc); + g_return_if_fail (spell != NULL); + + lang = gedit_spell_checker_get_language (spell); + + data_dir = gedit_plugin_get_data_dir (action_data->plugin); + dlg = gedit_spell_language_dialog_new (GTK_WINDOW (action_data->window), + lang, + data_dir); + g_free (data_dir); + + wg = gedit_window_get_group (action_data->window); + + gtk_window_group_add_window (wg, GTK_WINDOW (dlg)); + + gtk_window_set_modal (GTK_WINDOW (dlg), TRUE); + + g_signal_connect (dlg, + "response", + G_CALLBACK (language_dialog_response), + spell); + + gtk_widget_show (dlg); +} + +static void +spell_cb (GtkAction *action, + ActionData *action_data) +{ + GeditView *view; + GeditDocument *doc; + GeditSpellChecker *spell; + GtkWidget *dlg; + GtkTextIter start, end; + gchar *word; + gchar *data_dir; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (action_data->window); + g_return_if_fail (view != NULL); + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + g_return_if_fail (doc != NULL); + + spell = get_spell_checker_from_document (doc); + g_return_if_fail (spell != NULL); + + if (gtk_text_buffer_get_char_count (GTK_TEXT_BUFFER (doc)) <= 0) + { + WindowData *data; + GtkWidget *statusbar; + + data = (WindowData *) g_object_get_data (G_OBJECT (action_data->window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + statusbar = gedit_window_get_statusbar (action_data->window); + gedit_statusbar_flash_message (GEDIT_STATUSBAR (statusbar), + data->message_cid, + _("The document is empty.")); + + return; + } + + if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), + &start, + &end)) + { + /* no selection, get the whole doc */ + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), + &start, + &end); + } + + set_check_range (doc, &start, &end); + + word = get_next_misspelled_word (view); + if (word == NULL) + { + WindowData *data; + GtkWidget *statusbar; + + data = (WindowData *) g_object_get_data (G_OBJECT (action_data->window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + statusbar = gedit_window_get_statusbar (action_data->window); + gedit_statusbar_flash_message (GEDIT_STATUSBAR (statusbar), + data->message_cid, + _("No misspelled words")); + + return; + } + + data_dir = gedit_plugin_get_data_dir (action_data->plugin); + dlg = gedit_spell_checker_dialog_new_from_spell_checker (spell, data_dir); + g_free (data_dir); + gtk_window_set_modal (GTK_WINDOW (dlg), TRUE); + gtk_window_set_transient_for (GTK_WINDOW (dlg), + GTK_WINDOW (action_data->window)); + + g_signal_connect (dlg, "ignore", G_CALLBACK (ignore_cb), view); + g_signal_connect (dlg, "ignore_all", G_CALLBACK (ignore_cb), view); + + g_signal_connect (dlg, "change", G_CALLBACK (change_cb), view); + g_signal_connect (dlg, "change_all", G_CALLBACK (change_all_cb), view); + + g_signal_connect (dlg, "add_word_to_personal", G_CALLBACK (add_word_cb), view); + + gedit_spell_checker_dialog_set_misspelled_word (GEDIT_SPELL_CHECKER_DIALOG (dlg), + word, + -1); + + g_free (word); + + gtk_widget_show (dlg); +} + +static void +set_auto_spell (GeditWindow *window, + GeditDocument *doc, + gboolean active) +{ + GeditAutomaticSpellChecker *autospell; + GeditSpellChecker *spell; + + spell = get_spell_checker_from_document (doc); + g_return_if_fail (spell != NULL); + + autospell = gedit_automatic_spell_checker_get_from_document (doc); + + if (active) + { + if (autospell == NULL) + { + GeditView *active_view; + + active_view = gedit_window_get_active_view (window); + g_return_if_fail (active_view != NULL); + + autospell = gedit_automatic_spell_checker_new (doc, spell); + gedit_automatic_spell_checker_attach_view (autospell, active_view); + gedit_automatic_spell_checker_recheck_all (autospell); + } + } + else + { + if (autospell != NULL) + gedit_automatic_spell_checker_free (autospell); + } +} + +static void +auto_spell_cb (GtkAction *action, + GeditWindow *window) +{ + + GeditDocument *doc; + gboolean active; + + gedit_debug (DEBUG_PLUGINS); + + active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + gedit_debug_message (DEBUG_PLUGINS, active ? "Auto Spell activated" : "Auto Spell deactivated"); + + doc = gedit_window_get_active_document (window); + if (doc == NULL) + return; + + gedit_document_set_metadata (doc, + GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED, + active ? "1" : NULL, NULL); + + set_auto_spell (window, doc, active); +} + +static void +free_window_data (WindowData *data) +{ + g_return_if_fail (data != NULL); + + g_object_unref (data->action_group); + g_slice_free (WindowData, data); +} + +static void +free_action_data (gpointer data) +{ + g_return_if_fail (data != NULL); + + g_slice_free (ActionData, data); +} + +static void +update_ui_real (GeditWindow *window, + WindowData *data) +{ + GeditDocument *doc; + GeditView *view; + gboolean autospell; + GtkAction *action; + + gedit_debug (DEBUG_PLUGINS); + + doc = gedit_window_get_active_document (window); + view = gedit_window_get_active_view (window); + + autospell = (doc != NULL && + gedit_automatic_spell_checker_get_from_document (doc) != NULL); + + if (doc != NULL) + { + GeditTab *tab; + GeditTabState state; + + tab = gedit_window_get_active_tab (window); + state = gedit_tab_get_state (tab); + + /* If the document is loading we can't get the metadata so we + endup with an useless speller */ + if (state == GEDIT_TAB_STATE_NORMAL) + { + action = gtk_action_group_get_action (data->action_group, + "AutoSpell"); + + g_signal_handlers_block_by_func (action, auto_spell_cb, + window); + set_auto_spell (window, doc, autospell); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + autospell); + g_signal_handlers_unblock_by_func (action, auto_spell_cb, + window); + } + } + + gtk_action_group_set_sensitive (data->action_group, + (view != NULL) && + gtk_text_view_get_editable (GTK_TEXT_VIEW (view))); +} + +static void +set_auto_spell_from_metadata (GeditWindow *window, + GeditDocument *doc, + GtkActionGroup *action_group) +{ + gboolean active = FALSE; + gchar *active_str; + GeditDocument *active_doc; + + active_str = gedit_document_get_metadata (doc, + GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED); + + if (active_str) + { + active = *active_str == '1'; + + g_free (active_str); + } + + set_auto_spell (window, doc, active); + + /* In case that the doc is the active one we mark the spell action */ + active_doc = gedit_window_get_active_document (window); + + if (active_doc == doc && action_group != NULL) + { + GtkAction *action; + + action = gtk_action_group_get_action (action_group, + "AutoSpell"); + + g_signal_handlers_block_by_func (action, auto_spell_cb, + window); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + active); + g_signal_handlers_unblock_by_func (action, auto_spell_cb, + window); + } +} + +static void +on_document_loaded (GeditDocument *doc, + const GError *error, + GeditWindow *window) +{ + if (error == NULL) + { + WindowData *data; + GeditSpellChecker *spell; + + spell = GEDIT_SPELL_CHECKER (g_object_get_qdata (G_OBJECT (doc), + spell_checker_id)); + if (spell != NULL) + { + set_language_from_metadata (spell, doc); + } + + data = g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + + set_auto_spell_from_metadata (window, doc, data->action_group); + } +} + +static void +on_document_saved (GeditDocument *doc, + const GError *error, + GeditWindow *window) +{ + GeditAutomaticSpellChecker *autospell; + GeditSpellChecker *spell; + const gchar *key; + + if (error != NULL) + { + return; + } + + /* Make sure to save the metadata here too */ + autospell = gedit_automatic_spell_checker_get_from_document (doc); + spell = GEDIT_SPELL_CHECKER (g_object_get_qdata (G_OBJECT (doc), spell_checker_id)); + + if (spell != NULL) + { + key = gedit_spell_checker_language_to_key (gedit_spell_checker_get_language (spell)); + } + else + { + key = NULL; + } + + gedit_document_set_metadata (doc, + GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED, + autospell != NULL ? "1" : NULL, + GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE, + key, + NULL); +} + +static void +tab_added_cb (GeditWindow *window, + GeditTab *tab, + gpointer useless) +{ + GeditDocument *doc; + GeditView *view; + + doc = gedit_tab_get_document (tab); + view = gedit_tab_get_view (tab); + + g_signal_connect (doc, "loaded", + G_CALLBACK (on_document_loaded), + window); + + g_signal_connect (doc, "saved", + G_CALLBACK (on_document_saved), + window); +} + +static void +tab_removed_cb (GeditWindow *window, + GeditTab *tab, + gpointer useless) +{ + GeditDocument *doc; + GeditView *view; + + doc = gedit_tab_get_document (tab); + view = gedit_tab_get_view (tab); + + g_signal_handlers_disconnect_by_func (doc, on_document_loaded, window); + g_signal_handlers_disconnect_by_func (doc, on_document_saved, window); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + ActionData *action_data; + GList *docs, *l; + + gedit_debug (DEBUG_PLUGINS); + + data = g_slice_new (WindowData); + action_data = g_slice_new (ActionData); + action_data->plugin = plugin; + action_data->window = window; + + manager = gedit_window_get_ui_manager (window); + + data->action_group = gtk_action_group_new ("GeditSpellPluginActions"); + gtk_action_group_set_translation_domain (data->action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions_full (data->action_group, + action_entries, + G_N_ELEMENTS (action_entries), + action_data, + (GDestroyNotify) free_action_data); + gtk_action_group_add_toggle_actions (data->action_group, + toggle_action_entries, + G_N_ELEMENTS (toggle_action_entries), + window); + + gtk_ui_manager_insert_action_group (manager, data->action_group, -1); + + data->ui_id = gtk_ui_manager_new_merge_id (manager); + + data->message_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR (gedit_window_get_statusbar (window)), + "spell_plugin_message"); + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + (GDestroyNotify) free_window_data); + + gtk_ui_manager_add_ui (manager, + data->ui_id, + MENU_PATH, + "CheckSpell", + "CheckSpell", + GTK_UI_MANAGER_MENUITEM, + FALSE); + + gtk_ui_manager_add_ui (manager, + data->ui_id, + MENU_PATH, + "AutoSpell", + "AutoSpell", + GTK_UI_MANAGER_MENUITEM, + FALSE); + + gtk_ui_manager_add_ui (manager, + data->ui_id, + MENU_PATH, + "ConfigSpell", + "ConfigSpell", + GTK_UI_MANAGER_MENUITEM, + FALSE); + + update_ui_real (window, data); + + docs = gedit_window_get_documents (window); + for (l = docs; l != NULL; l = g_list_next (l)) + { + GeditDocument *doc = GEDIT_DOCUMENT (l->data); + + set_auto_spell_from_metadata (window, doc, + data->action_group); + + g_signal_handlers_disconnect_by_func (doc, + on_document_loaded, + window); + + g_signal_handlers_disconnect_by_func (doc, + on_document_saved, + window); + } + + data->tab_added_id = + g_signal_connect (window, "tab-added", + G_CALLBACK (tab_added_cb), NULL); + data->tab_removed_id = + g_signal_connect (window, "tab-removed", + G_CALLBACK (tab_removed_cb), NULL); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + manager = gedit_window_get_ui_manager (window); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + gtk_ui_manager_remove_ui (manager, data->ui_id); + gtk_ui_manager_remove_action_group (manager, data->action_group); + + g_signal_handler_disconnect (window, data->tab_added_id); + g_signal_handler_disconnect (window, data->tab_removed_id); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + update_ui_real (window, data); +} + +static void +gedit_spell_plugin_class_init (GeditSpellPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_spell_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; + + if (spell_checker_id == 0) + spell_checker_id = g_quark_from_string ("GeditSpellCheckerID"); + + if (check_range_id == 0) + check_range_id = g_quark_from_string ("CheckRangeID"); +} diff --git a/plugins/spell/gedit-spell-plugin.h b/plugins/spell/gedit-spell-plugin.h new file mode 100755 index 00000000..7de5807a --- /dev/null +++ b/plugins/spell/gedit-spell-plugin.h @@ -0,0 +1,75 @@ +/* + * gedit-spell-plugin.h + * + * 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, 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$ + */ + +#ifndef __GEDIT_SPELL_PLUGIN_H__ +#define __GEDIT_SPELL_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_SPELL_PLUGIN (gedit_spell_plugin_get_type ()) +#define GEDIT_SPELL_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPlugin)) +#define GEDIT_SPELL_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPluginClass)) +#define GEDIT_IS_SPELL_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_SPELL_PLUGIN)) +#define GEDIT_IS_SPELL_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_SPELL_PLUGIN)) +#define GEDIT_SPELL_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPluginClass)) + +/* Private structure type */ +typedef struct _GeditSpellPluginPrivate GeditSpellPluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditSpellPlugin GeditSpellPlugin; + +struct _GeditSpellPlugin +{ + GeditPlugin parent_instance; +}; + +/* + * Class definition + */ +typedef struct _GeditSpellPluginClass GeditSpellPluginClass; + +struct _GeditSpellPluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_spell_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_SPELL_PLUGIN_H__ */ diff --git a/plugins/spell/gedit-spell-utils.c b/plugins/spell/gedit-spell-utils.c new file mode 100755 index 00000000..0782d37a --- /dev/null +++ b/plugins/spell/gedit-spell-utils.c @@ -0,0 +1,94 @@ +/* + * gedit-spell-utils.c + * This file is part of gedit + * + * Copyright (C) 2010 - 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include <string.h> + +#include "gedit-spell-utils.h" +#include <gtksourceview/gtksourcebuffer.h> + +gboolean +gedit_spell_utils_is_digit (const char *text, gssize length) +{ + gunichar c; + const gchar *p; + const gchar *end; + + g_return_val_if_fail (text != NULL, FALSE); + + if (length < 0) + length = strlen (text); + + 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) && c != '.' && c != ',') + return FALSE; + + p = next; + } + + return TRUE; +} + +gboolean +gedit_spell_utils_skip_no_spell_check (GtkTextIter *start, + GtkTextIter *end) +{ + GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_iter_get_buffer (start)); + + while (gtk_source_buffer_iter_has_context_class (buffer, start, "no-spell-check")) + { + GtkTextIter last = *start; + + if (!gtk_source_buffer_iter_forward_to_context_class_toggle (buffer, start, "no-spell-check")) + { + return FALSE; + } + + if (gtk_text_iter_compare (start, &last) <= 0) + { + return FALSE; + } + + gtk_text_iter_forward_word_end (start); + gtk_text_iter_backward_word_start (start); + + if (gtk_text_iter_compare (start, &last) <= 0) + { + return FALSE; + } + + if (gtk_text_iter_compare (start, end) >= 0) + { + return FALSE; + } + } + + return TRUE; +} + diff --git a/plugins/spell/gedit-spell-utils.h b/plugins/spell/gedit-spell-utils.h new file mode 100755 index 00000000..fbfe4b1b --- /dev/null +++ b/plugins/spell/gedit-spell-utils.h @@ -0,0 +1,37 @@ +/* + * gedit-spell-utils.h + * This file is part of gedit + * + * Copyright (C) 2010 - 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef __GEDIT_SPELL_UTILS_H__ +#define __GEDIT_SPELL_UTILS_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +gboolean gedit_spell_utils_is_digit (const char *text, gssize length); + +gboolean gedit_spell_utils_skip_no_spell_check (GtkTextIter *start, GtkTextIter *end); + +G_END_DECLS + +#endif /* __GEDIT_SPELL_UTILS_H__ */ + diff --git a/plugins/spell/languages-dialog.ui b/plugins/spell/languages-dialog.ui new file mode 100755 index 00000000..8ceb7056 --- /dev/null +++ b/plugins/spell/languages-dialog.ui @@ -0,0 +1,145 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <object class="GtkDialog" id="dialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Set language</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="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">2</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <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> + <property name="focus_on_click">True</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> + <property name="focus_on_click">True</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> + <property name="focus_on_click">True</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="GtkVBox" id="content"> + <property name="border_width">5</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">11</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">Select the _language of the current document.</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">True</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">languages_treeview</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="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <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="languages_treeview"> + <property name="height_request">180</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">True</property> + <property name="fixed_height_mode">False</property> + <property name="hover_selection">False</property> + <property name="hover_expand">False</property> + </object> + </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/plugins/spell/spell-checker.ui b/plugins/spell/spell-checker.ui new file mode 100755 index 00000000..ea84290f --- /dev/null +++ b/plugins/spell/spell-checker.ui @@ -0,0 +1,482 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <object class="GtkImage" id="check_word_image"> + <property name="stock">gtk-spell-check</property> + <property name="icon_size">4</property> + </object> + <object class="GtkImage" id="add_word_image"> + <property name="stock">gtk-add</property> + <property name="icon_size">4</property> + </object> + <object class="GtkImage" id="ignore_image"> + <property name="stock">gtk-go-down</property> + <property name="icon_size">4</property> + </object> + <object class="GtkImage" id="change_image"> + <property name="stock">gtk-convert</property> + <property name="icon_size">4</property> + </object> + <object class="GtkImage" id="ignore_all_image"> + <property name="stock">gtk-goto-bottom</property> + <property name="icon_size">4</property> + </object> + <object class="GtkImage" id="change_all_image"> + <property name="stock">gtk-convert</property> + <property name="icon_size">4</property> + </object> + <object class="GtkWindow" id="check_spelling_window"> + <property name="visible">True</property> + <property name="title" translatable="yes">Check spelling</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> + <child> + <object class="GtkVBox" id="content"> + <property name="border_width">12</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkTable" id="table1"> + <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">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">Misspelled word:</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</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> + </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="misspelled_word_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">word</property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_CENTER</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> + <attributes> + <attribute name="weight" value="PANGO_WEIGHT_BOLD"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</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="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Change _to:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</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">word_entry</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> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkEntry" id="word_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"/> + <property name="has_frame">True</property> + <property name="activates_default">False</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="check_word_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="image">check_word_image</property> + <property name="label" translatable="yes">Check _Word</property> + <property name="use_underline">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="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">fill</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table2"> + <property name="visible">True</property> + <property name="n_rows">3</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Suggestions:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</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">suggestions_list</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="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">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="suggestions_list"> + <property name="width_request">200</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">True</property> + </object> + </child> + </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> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="homogeneous">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkTable" id="table3"> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="homogeneous">True</property> + <property name="row_spacing">12</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkButton" id="ignore_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="image">ignore_image</property> + <property name="label" translatable="yes">_Ignore</property> + <property name="use_underline">True</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="y_options">expand</property> + </packing> + </child> + <child> + <object class="GtkButton" id="change_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="image">change_image</property> + <property name="label" translatable="yes">Cha_nge</property> + <property name="use_underline">True</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="y_options">expand</property> + </packing> + </child> + <child> + <object class="GtkButton" id="ignore_all_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="image">ignore_all_image</property> + <property name="label" translatable="yes">Ignore _All</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="y_options">expand</property> + </packing> + </child> + <child> + <object class="GtkButton" id="change_all_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="image">change_all_image</property> + <property name="label" translatable="yes">Change A_ll</property> + <property name="use_underline">True</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="y_options">expand</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="vbox3"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">11</property> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="label" translatable="yes">User dictionary:</property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">7.45058e-09</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="homogeneous">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton" id="add_word_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="image">add_word_image</property> + <property name="label" translatable="yes">Add w_ord</property> + <property name="use_underline">True</property> + </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">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">False</property> + </packing> + </child> + </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> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox32"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label44"> + <property name="visible">True</property> + <property name="label" translatable="yes">Language:</property> + <property name="use_underline">False</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.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="language_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Language</property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <attributes> + <attribute name="weight" value="PANGO_WEIGHT_BOLD"/> + </attributes> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">fill</property> + <property name="y_options">fill</property> + </packing> + </child> + <child> + <object class="GtkHButtonBox" id="hbuttonbox1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <property name="spacing">0</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> + </object> + </child> + </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">fill</property> + <property name="y_options">fill</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/plugins/spell/spell.gedit-plugin.desktop.in b/plugins/spell/spell.gedit-plugin.desktop.in new file mode 100755 index 00000000..c75c9eee --- /dev/null +++ b/plugins/spell/spell.gedit-plugin.desktop.in @@ -0,0 +1,9 @@ +[Gedit Plugin] +Module=spell +IAge=2 +_Name=Spell Checker +_Description=Checks the spelling of the current document. +Icon=gtk-spell-check +Authors=Paolo Maggi <[email protected]> +Copyright=Copyright © 2002-2005 Paolo Maggi +Website=http://www.gedit.org diff --git a/plugins/taglist/HTML.tags.xml.in b/plugins/taglist/HTML.tags.xml.in new file mode 100755 index 00000000..116cc247 --- /dev/null +++ b/plugins/taglist/HTML.tags.xml.in @@ -0,0 +1,2672 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TagList xmlns="http://gedit.sourceforge.net/some-location"> +<TagGroup _name="XHTML 1.0 - Tags" sort="true"> + + <Tag _name="Abbreviated form"> + <Begin><abbr title="</Begin> + <End>"></abbr></End> + </Tag> + + <Tag _name="Abbreviation"> + <Begin>abbr="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Accessibility key character"> + <Begin>accesskey="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Acronym"> + <Begin><acronym title="</Begin> + <End>"></acronym></End> + </Tag> + + <Tag _name="Align"> + <Begin>align="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Alignment character"> + <Begin>char="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Alternative"> + <Begin>alt="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Anchor URI"> + <Begin><a href="</Begin> + <End>"></a></End> + </Tag> + + <Tag _name="Anchor"> + <Begin><a id="</Begin> + <End>"></a></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Applet class file code (deprecated)"> + <Begin>code="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Associated information"> + <Begin>content="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Author info"> + <Begin><address></Begin> + <End></address></End> + </Tag> + + <Tag _name="Axis related headers"> + <Begin>axis="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Background color (deprecated)"> + <Begin>bgcolor="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Background texture tile (deprecated)"> + <Begin>background="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Base font (deprecated)"> + <Begin><basefont></Begin> + </Tag> + + <Tag _name="Base URI"> + <Begin>codebase="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Bold"> + <Begin><b></Begin> + <End></b></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Border (deprecated)"> + <Begin>border="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Cell rowspan"> + <Begin>rowspan="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Center (deprecated)"> + <Begin><center></Begin> + <End></center></End> + </Tag> + + <Tag _name="Character encoding of linked resource"> + <Begin>charset="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Checked state"> + <Begin>checked="checked"</Begin> + </Tag> + + <Tag _name="Citation"> + <Begin><cite></Begin> + <End></cite></End> + </Tag> + + <Tag _name="Cite reason for change"> + <Begin>cite="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Class implementation ID"> + <Begin>classid="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Class list"> + <Begin>class="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Clear text flow control"> + <Begin>clear="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Code content type"> + <Begin>codetype="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Color of selected links (deprecated)"> + <Begin>alink="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Column span"> + <Begin>colspan="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Columns"> + <Begin>cols="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Comment"> + <Begin><!-- </Begin> + <End> --></End> + </Tag> + + <Tag _name="Computer code fragment"> + <Begin><code></Begin> + <End></code></End> + </Tag> + + <!-- The "type" attribute is deprecated for the "ol" tag only, + since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Content type (deprecated)"> + <Begin>type="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Coordinates"> + <Begin>coords="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Date and time of change"> + <Begin>datetime="YYYYMMDD"</Begin> + </Tag> + +<!-- NOTE: used in "object" tag --> + <Tag _name="Declare flag"> + <Begin>declare="declare"</Begin> + </Tag> + + <!-- Translators: DEFER is an optional attribute of the <script> tag. + It indicates that the script is not going to generate any document + content. The browser can continue parsing and drawing the page. --> + <Tag _name="Defer attribute"> + <Begin>defer="defer"</Begin> + </Tag> + + <Tag _name="Definition description"> + <Begin><dd></Begin> + <End></dd></End> + </Tag> + + <Tag _name="Definition list"> + <Begin><dl></Begin> + <End></dl></End> + </Tag> + + <Tag _name="Definition term"> + <Begin><dt></Begin> + <End></dt></End> + </Tag> + + <Tag _name="Deleted text"> + <Begin><del></Begin> + <End></del></End> + </Tag> + + <Tag _name="Directionality"> + <Begin>dir="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Directionality (deprecated)"> + <Begin><dir></Begin> + <End></dir></End> + </Tag> + + <Tag _name="Disabled"> + <Begin>disabled="disabled"</Begin> + </Tag> + + <Tag _name="DIV container"> + <Begin><div></Begin> + <End></div></End> + </Tag> + + <Tag _name="DIV Style container"> + <Begin><div style=></Begin> + <End></div></End> + </Tag> + + <Tag _name="Document base"> + <Begin><base href="</Begin> + <End>" /></End> + </Tag> + + <Tag _name="Document body"> + <Begin><body></Begin> + <End></body></End> + </Tag> + + <Tag _name="Document head"> + <Begin><head></Begin> + <End></head></End> + </Tag> + + <Tag _name="Element ID"> + <Begin>id="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Document title"> + <Begin><title></Begin> + <End></title></End> + </Tag> + + <Tag _name="Document type"> + <Begin><!DOCTYPE></Begin> + </Tag> + + <Tag _name="Emphasis"> + <Begin><em></Begin> + <End></em></End> + </Tag> + + <Tag _name="Encode type"> + <Begin>enctype="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Font face (deprecated)"> + <Begin>face="</Begin> + <End>"</End> + </Tag> + + <Tag _name="For label"> + <Begin>for="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Forced line break"> + <Begin><br /></Begin> + </Tag> + + <Tag _name="Form action handler"> + <Begin>action="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Form control group"> + <Begin><fieldset></Begin> + <End></fieldset></End> + </Tag> + + <Tag _name="Form field label text"> + <Begin><label></Begin> + <End></label></End> + </Tag> + + <Tag _name="Form input type"> + <Begin><input type="</Begin> + <End>" /></End> + </Tag> + + <Tag _name="Form input"> + <Begin><input /></Begin> + </Tag> + + <Tag _name="Form method"> + <Begin><form method="</Begin> + <End>"></form></End> + </Tag> + + <Tag _name="Form method"> + <Begin>method="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Form"> + <Begin><form></Begin> + <End></form></End> + </Tag> + + <Tag _name="Forward link"> + <Begin>rel="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Frame render parts"> + <Begin>frame="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Frame source"> + <Begin><frame src="</Begin> + <End>"></frame></End> + </Tag> + + <Tag _name="Frame target"> + <Begin>target="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Frame"> + <Begin><frame></Begin> + <End></frame></End> + </Tag> + + <Tag _name="Frame border"> + <Begin>frameborder="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Frameset columns"> + <Begin><frameset cols="</Begin> + <End>"></frameset></End> + </Tag> + + <Tag _name="Frameset rows"> + <Begin><frameset rows="</Begin> + <End>"></frameset></End> + </Tag> + + <Tag _name="Frameset"> + <Begin><frameset></Begin> + <End></frameset></End> + </Tag> + + <Tag _name="Frame spacing"> + <Begin>framespacing="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Generic embedded object"> + <Begin><object></Begin> + <End></object></End> + </Tag> + + <Tag _name="Generic metainformation"> + <Begin><meta content="</Begin> + <End>"></meta></End> + </Tag> + + <Tag _name="Generic span"> + <Begin><span></Begin> + <End></span></End> + </Tag> + + <Tag _name="Header cell IDs"> + <Begin>headers="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Heading 1"> + <Begin><h1></Begin> + <End></h1></End> + </Tag> + + <Tag _name="Heading 2"> + <Begin><h2></Begin> + <End></h2></End> + </Tag> + + <Tag _name="Heading 3"> + <Begin><h3></Begin> + <End></h3></End> + </Tag> + + <Tag _name="Heading 4"> + <Begin><h4></Begin> + <End></h4></End> + </Tag> + + <Tag _name="Heading 5"> + <Begin><h5></Begin> + <End></h5></End> + </Tag> + + <Tag _name="Heading 6"> + <Begin><h6></Begin> + <End></h6></End> + </Tag> + + <Tag _name="Height"> + <Begin>height="</Begin> + </Tag> + + <Tag _name="Horizontal rule"> + <Begin><hr /></Begin> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Horizontal space (deprecated)"> + <Begin>hspace="</Begin> + <End>"</End> + </Tag> + + <Tag _name="HREF URI"> + <Begin>href="</Begin> + <End>"</End> + </Tag> + + <Tag _name="HTML root element"> + <Begin><html></Begin> + <End></html></End> + </Tag> + + <Tag _name="HTTP header name"> + <Begin>http-equiv="</Begin> + <End>"</End> + </Tag> + + <Tag _name="I18N BiDi override"> + <Begin><bdo></Begin> + <End></bdo></End> + </Tag> + + <Tag _name="Image map area"> + <Begin><area alt="</Begin> + <End>"></area></End> + </Tag> + + <Tag _name="Image map name"> + <Begin><map name="</Begin> + <End>"></map></End> + </Tag> + + <Tag _name="Image map"> + <Begin><map id="</Begin> + <End>"></map></End> + </Tag> + + <Tag _name="Image"> + <Begin><img id="</Begin> + <End>" src="" alt="" /></End> + </Tag> + + <Tag _name="Inline frame"> + <Begin><iframe></Begin> + <End></iframe></End> + </Tag> + + <Tag _name="Inserted text"> + <Begin><ins></Begin> + <End></ins></End> + </Tag> + + <Tag _name="Instance definition"> + <Begin><dfn></Begin> + <End></dfn></End> + </Tag> + + <Tag _name="Italic text"> + <Begin><i></Begin> + <End></i></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Java applet (deprecated)"> + <Begin><applet height="</Begin> + <End>" width=""></applet></End> + </Tag> + + <Tag _name="Label"> + <Begin>label="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Language code"> + <Begin>hreflang="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Language code"> + <Begin>lang="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Large text style"> + <Begin><big></Begin> + <End></big></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Link color (deprecated)"> + <Begin>link="</Begin> + <End>"</End> + </Tag> + + <Tag _name="List item"> + <Begin><li></Begin> + <End></li></End> + </Tag> + + <Tag _name="List of MIME types for file upload"> + <Begin>accept="</Begin> + <End>"</End> + </Tag> + + <Tag _name="List of supported character sets"> + <Begin>accept-charset="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Local change to font"> + <Begin><font></Begin> + <End></font></End> + </Tag> + + <Tag _name="Long description link"> + <Begin>longdesc="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Long quotation"> + <Begin><blockquote></Begin> + <End></blockquote></End> + </Tag> + +<!-- Supported in XHTML 1.0 Frameset DTD only. --> + <Tag _name="Margin pixel height"> + <Begin>marginheight="</Begin> + <End>"</End> + </Tag> + +<!-- Supported in XHTML 1.0 Frameset DTD only. --> + <Tag _name="Margin pixel width"> + <Begin>marginwidth="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Maximum length of text field"> + <Begin>maxlength="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Output media"> + <Begin>media="</Begin> + <End>"</End> + </Tag> + + <!-- Here I take some liberties: There's no mandatory attributes, + but those are most common, and will likely be used. --> + <Tag _name="Media-independent link"> + <Begin><link rel="</Begin> + <End>" type="" href="" /></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Menu list (deprecated)"> + <Begin><menu></Begin> + <End></menu></End> + </Tag> + + <Tag _name="Multi-line text field"> + <Begin><textarea cols="</Begin> + <End>" rows=""></textarea></End> + </Tag> + + <Tag _name="Multiple"> + <Begin>multiple="multiple"</Begin> + </Tag> + + <Tag _name="Name"> + <Begin>name="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Named property value"> + <Begin><param name="</Begin> + <End>"></param></End> + </Tag> + + <Tag _name="No frames"> + <Begin><noframes></Begin> + <End></noframes></End> + </Tag> + + <Tag _name="No resize"> + <Begin>noresize="noresize"</Begin> + </Tag> + + <Tag _name="No script"> + <Begin><noscript></Begin> + <End></noscript></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="No shade (deprecated)"> + <Begin>noshade="noshade"</Begin> + </Tag> + + <Tag _name="No URI"> + <Begin>nohref="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="No word wrap (deprecated)"> + <Begin>nowrap="nowrap"</Begin> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Object applet file (deprecated)"> + <Begin>object="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Object data reference"> + <Begin>data="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Offset for alignment character"> + <Begin>charoff="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnBlur event"> + <Begin>onblur="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnChange event"> + <Begin>onchange="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnClick event"> + <Begin>onclick="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnDblClick event"> + <Begin>ondbclick="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnFocus event"> + <Begin>onfocus="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnKeyDown event"> + <Begin>onkeydown="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnKeyPress event"> + <Begin>onkeypress="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnKeyUp event"> + <Begin>onkeyup="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnLoad event"> + <Begin>onload="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnMouseDown event"> + <Begin>onmousedown="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnMouseMove event"> + <Begin>onmousemove="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnMouseOut event"> + <Begin>onmouseout="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnMouseOver event"> + <Begin>onmouseover="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnMouseUp event"> + <Begin>onmouseup="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnReset event"> + <Begin>onreset="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnSelect event"> + <Begin>onselect="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnSubmit event"> + <Begin>onsubmit="</Begin> + <End>"</End> + </Tag> + + <Tag _name="OnUnload event"> + <Begin>onunload="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Option group"> + <Begin><optgroup label="</Begin> + <End>"></optgroup></End> + </Tag> + + <Tag _name="Option selector"> + <Begin><select></Begin> + <End></select></End> + </Tag> + + <Tag _name="Ordered list"> + <Begin><ol></Begin> + <End></ol></End> + </Tag> + + <Tag _name="Paragraph class"> + <Begin><p class="</Begin> + <End>"></p></End> + </Tag> + + <Tag _name="Paragraph style"> + <Begin><p style="</Begin> + <End>"></p></End> + </Tag> + + <Tag _name="Paragraph"> + <Begin><p></Begin> + <End></p></End> + </Tag> + + <Tag _name="Preformatted text"> + <Begin><pre></Begin> + <End></pre></End> + </Tag> + + <Tag _name="Profile metainfo dictionary"> + <Begin>profile="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Push button"> + <Begin><button></Begin> + <End></button></End> + </Tag> + + <Tag _name="ReadOnly text and password"> + <Begin>readonly="readonly"</Begin> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Reduced spacing (deprecated)"> + <Begin>compact="compact_rendering"</Begin> + </Tag> + + <Tag _name="Reverse link"> + <Begin>rev="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Rows"> + <Begin>rows="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Rulings between rows and columns"> + <Begin>rules="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Sample program output, scripts"> + <Begin><samp></Begin> + <End></samp></End> + </Tag> + + <Tag _name="Scope covered by header cells"> + <Begin>scope="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Script language name"> + <Begin>language="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Script statements"> + <Begin><script type="</Begin> + <End>"></script></End> + </Tag> + + <Tag _name="Scrollbar"> + <Begin>scrolling="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Selectable option"> + <Begin><option></Begin> + <End></option></End> + </Tag> + + <Tag _name="Selected"> + <Begin>selected="selected"</Begin> + </Tag> + + <Tag _name="Server-side image map"> + <Begin>ismap="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Shape"> + <Begin>shape="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Short inline quotation"> + <Begin><q></Begin> + <End></q></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Size (deprecated)"> + <Begin>size="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Small text style"> + <Begin><small></Begin> + <End></small></End> + </Tag> + + <Tag _name="Source"> + <Begin>src="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Space-separated archive list"> + <Begin>archive="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Spacing between cells"> + <Begin>cellspacing="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Spacing within cells"> + <Begin>cellpadding="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Span"> + <Begin>span="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Standby load message"> + <Begin>standby="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Starting sequence number (deprecated)"> + <Begin>start="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Strike-through text style (deprecated)"> + <Begin><s></Begin> + <End></s></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Strike-through text (deprecated)"> + <Begin><strike></Begin> + <End></strike></End> + </Tag> + + <Tag _name="Strong emphasis"> + <Begin><strong></Begin> + <End></strong></End> + </Tag> + + <Tag _name="Style info"> + <Begin><style type="text/css"></Begin> + <End></style></End> + </Tag> + + <Tag _name="Style info"> + <Begin>style="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Subscript"> + <Begin><sub></Begin> + <End></sub></End> + </Tag> + + <Tag _name="Superscript"> + <Begin><sup></Begin> + <End></sup></End> + </Tag> + + <Tag _name="Table body"> + <Begin><tbody></Begin> + <End></tbody></End> + </Tag> + + <Tag _name="Table caption"> + <Begin><caption></Begin> + <End></caption></End> + </Tag> + + <Tag _name="Table column group properties"> + <Begin><colgroup></Begin> + <End></colgroup></End> + </Tag> + + <Tag _name="Table column properties"> + <Begin><col></Begin> + <End></col></End> + </Tag> + + <Tag _name="Table data cell"> + <Begin><td></Begin> + <End></td></End> + </Tag> + + <Tag _name="Table footer"> + <Begin><tfoot></Begin> + <End></tfoot></End> + </Tag> + + <Tag _name="Table header cell"> + <Begin><th></Begin> + <End></th></End> + </Tag> + + <Tag _name="Table header"> + <Begin><thead></Begin> + <End></thead></End> + </Tag> + + <Tag _name="Table row"> + <Begin><tr></Begin> + <End></tr></End> + </Tag> + + <Tag _name="Table summary"> + <Begin>summary="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Table"> + <Begin><table></Begin> + <End></table></End> + </Tag> + + <Tag _name="Target - Blank"> + <Begin><a target="_blank"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Target - Parent"> + <Begin><a target="_parent"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Target - Self"> + <Begin><a target="_self"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Target - Top"> + <Begin><a target="_top"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Teletype or monospace text style"> + <Begin><tt></Begin> + <End></tt></End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Text color (deprecated)"> + <Begin>color="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Text color (deprecated)"> + <Begin>text="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Text entered by user"> + <Begin><kbd></Begin> + <End></kbd></End> + </Tag> + + <Tag _name="Title"> + <Begin>title="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Underlined text style"> + <Begin><u></Begin> + <End></u></End> + </Tag> + + <Tag _name="Unordered list"> + <Begin><ul></Begin> + <End></ul></End> + </Tag> + + <Tag _name="Use image map"> + <Begin>usemap="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Value interpretation"> + <Begin>valuetype="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Value"> + <Begin>value="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Variable or program argument"> + <Begin><var></Begin> + <End></var></End> + </Tag> + + <Tag _name="Vertical cell alignment"> + <Begin>valign="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Vertical space (deprecated)"> + <Begin>vspace="</Begin> + <End>"</End> + </Tag> + +<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. --> + <Tag _name="Visited link color (deprecated)"> + <Begin>vlink="</Begin> + <End>"</End> + </Tag> + + <Tag _name="Width"> + <Begin>width="</Begin> + <End>"</End> + </Tag> + +</TagGroup> + +<TagGroup _name="HTML - Tags" sort="true"> + + <Tag _name="Abbreviated form"> + <Begin><abbr></Begin> + <End></abbr></End> + </Tag> + + <Tag _name="Abbreviation"> + <Begin>abbr=</Begin> + </Tag> + + <Tag _name="Above"> + <Begin><above></Begin> + </Tag> + + <Tag _name="Accessibility key character"> + <Begin>accesskey=</Begin> + </Tag> + + <Tag _name="Acronym"> + <Begin><acronym></Begin> + <End></acronym></End> + </Tag> + + <Tag _name="Align"> + <Begin>align=</Begin> + </Tag> + + <Tag _name="Alignment character"> + <Begin>char=</Begin> + </Tag> + + <Tag _name="Alternative"> + <Begin>alt=</Begin> + </Tag> + + <Tag _name="Anchor URI"> + <Begin><a href=></Begin> + <End></a></End> + </Tag> + + <Tag _name="Anchor"> + <Begin><a></Begin> + <End></a></End> + </Tag> + + <Tag _name="Applet class file code"> + <Begin>code=</Begin> + </Tag> + + <Tag _name="Array"> + <Begin><array></Begin> + </Tag> + + <Tag _name="Associated information"> + <Begin>content=</Begin> + </Tag> + + <Tag _name="Author info"> + <Begin><address></Begin> + <End></address></End> + </Tag> + + <Tag _name="Axis related headers"> + <Begin>axis=</Begin> + </Tag> + + <Tag _name="Background color"> + <Begin>bgcolor=</Begin> + </Tag> + + <Tag _name="Background texture tile"> + <Begin>background=</Begin> + </Tag> + + <Tag _name="Base font"> + <Begin><basefont></Begin> + </Tag> + + <Tag _name="Base URI"> + <Begin>codebase=</Begin> + </Tag> + + <Tag _name="Bold"> + <Begin><b></Begin> + <End></b></End> + </Tag> + + <Tag _name="Border color"> + <Begin>bordercolor=</Begin> + </Tag> + + <Tag _name="Border"> + <Begin>border=</Begin> + </Tag> + + <Tag _name="Cell rowspan"> + <Begin>rowspan=</Begin> + </Tag> + + <Tag _name="Center"> + <Begin><center></Begin> + <End></center></End> + </Tag> + + <Tag _name="Character encoding of linked resource"> + <Begin>charset=</Begin> + </Tag> + + <Tag _name="Checked (state)"> + <Begin>checked</Begin> + </Tag> + + <Tag _name="Citation"> + <Begin><cite></Begin> + <End></cite></End> + </Tag> + + <Tag _name="Cite reason for change"> + <Begin>cite=</Begin> + </Tag> + + <Tag _name="Class implementation ID"> + <Begin>classid=</Begin> + </Tag> + + <Tag _name="Class list"> + <Begin>class=</Begin> + </Tag> + + <Tag _name="Clear text flow control"> + <Begin>clear=</Begin> + </Tag> + + <Tag _name="Code content type"> + <Begin>codetype=</Begin> + </Tag> + + <Tag _name="Color of selected links"> + <Begin>alink=</Begin> + </Tag> + + <Tag _name="Column span"> + <Begin>colspan=</Begin> + </Tag> + + <Tag _name="Columns"> + <Begin>cols=</Begin> + </Tag> + + <Tag _name="Comment"> + <Begin><!-- </Begin> + <End> --></End> + </Tag> + + <Tag _name="Comment"> + <Begin><comment></Begin> + <End></comment></End> + </Tag> + + <Tag _name="Computer code fragment"> + <Begin><code></Begin> + <End></code></End> + </Tag> + + <Tag _name="Content scheme"> + <Begin>scheme=</Begin> + </Tag> + + <Tag _name="Content type"> + <Begin>type=</Begin> + </Tag> + + <Tag _name="Coordinates"> + <Begin>coords=</Begin> + </Tag> + + <Tag _name="Date and time of change"> + <Begin>datetime=</Begin> + </Tag> + + <Tag _name="Declare flag"> + <Begin>declare</Begin> + </Tag> + + <!-- Translators: DEFER is an optional attribute of the <script> tag. + It indicates that the script is not going to generate any document + content. The browser can continue parsing and drawing the page. --> + <Tag _name="Defer attribute"> + <Begin>defer</Begin> + </Tag> + + <Tag _name="Definition description"> + <Begin><dd></Begin> + <End></dd></End> + </Tag> + + <Tag _name="Definition list"> + <Begin><dl></Begin> + <End></dl></End> + </Tag> + + <Tag _name="Definition term"> + <Begin><dt></Begin> + <End></dt></End> + </Tag> + + <Tag _name="Deleted text"> + <Begin><del></Begin> + <End></del></End> + </Tag> + + <Tag _name="Direction"> + <Begin>direction=</Begin> + </Tag> + + <Tag _name="Directionality"> + <Begin>dir=</Begin> + </Tag> + + <Tag _name="Directory list"> + <Begin><dir></Begin> + <End></dir></End> + </Tag> + + <Tag _name="Disabled"> + <Begin>disabled</Begin> + </Tag> + + <Tag _name="DIV container"> + <Begin><div></Begin> + <End></div></End> + </Tag> + + <Tag _name="DIV Style container"> + <Begin><div style=></Begin> + <End></div></End> + </Tag> + + <Tag _name="Document base"> + <Begin><base></Begin> + </Tag> + + <Tag _name="Document body"> + <Begin><body></Begin> + <End></body></End> + </Tag> + + <Tag _name="Document head"> + <Begin><head></Begin> + <End></head></End> + </Tag> + + <Tag _name="Element ID"> + <Begin>id=</Begin> + </Tag> + + <Tag _name="Document title"> + <Begin><title></Begin> + <End></title></End> + </Tag> + + <Tag _name="Document type"> + <Begin><!DOCTYPE></Begin> + </Tag> + + <Tag _name="HTML version"> + <Begin>version=</Begin> + </Tag> + + <Tag _name="Embedded object"> + <Begin><embed></Begin> + <End></embed></End> + </Tag> + + <Tag _name="Emphasis"> + <Begin><em></Begin> + <End></em></End> + </Tag> + + <Tag _name="Encode type"> + <Begin>enctype=</Begin> + </Tag> + + <Tag _name="Figure"> + <Begin><fig></Begin> + <End></fig></End> + </Tag> + + <Tag _name="Font face"> + <Begin>face=</Begin> + </Tag> + + <Tag _name="For label"> + <Begin>for=</Begin> + </Tag> + + <Tag _name="Forced line break"> + <Begin><br /></Begin> + </Tag> + + <Tag _name="Form action handler"> + <Begin>action=</Begin> + </Tag> + + <Tag _name="Form control group"> + <Begin><fieldset></Begin> + <End></fieldset></End> + </Tag> + + <Tag _name="Form field label text"> + <Begin><label></Begin> + <End></label></End> + </Tag> + + <Tag _name="Form input type"> + <Begin><input type=></Begin> + </Tag> + + <Tag _name="Form input"> + <Begin><input></Begin> + </Tag> + + <Tag _name="Form method"> + <Begin><form method=></Begin> + </Tag> + + <Tag _name="Form method"> + <Begin>method=</Begin> + </Tag> + + <Tag _name="Form"> + <Begin><form></Begin> + <End></form></End> + </Tag> + + <Tag _name="Forward link"> + <Begin>rel=</Begin> + </Tag> + + <Tag _name="Frame render parts"> + <Begin>frame=</Begin> + </Tag> + + <Tag _name="Frame source"> + <Begin><frame src=></Begin> + </Tag> + + <Tag _name="Frame target"> + <Begin>target=</Begin> + </Tag> + + <Tag _name="Frame"> + <Begin><frame></Begin> + </Tag> + + <Tag _name="Frameborder"> + <Begin>frameborder=</Begin> + </Tag> + + <Tag _name="Frameset columns"> + <Begin><frameset cols=></Begin> + <End></frameset></End> + </Tag> + + <Tag _name="Frameset rows"> + <Begin><frameset rows=></Begin> + <End></frameset></End> + </Tag> + + <Tag _name="Frameset"> + <Begin><frameset></Begin> + <End></frameset></End> + </Tag> + + <Tag _name="Framespacing"> + <Begin>framespacing=</Begin> + </Tag> + + <Tag _name="Generic embedded object"> + <Begin><object></Begin> + </Tag> + + <Tag _name="Generic metainformation"> + <Begin><meta></Begin> + </Tag> + + <Tag _name="Generic span"> + <Begin><span></Begin> + <End></span></End> + </Tag> + + <Tag _name="Header cell IDs"> + <Begin>headers=</Begin> + </Tag> + + <Tag _name="Heading 1"> + <Begin><h1></Begin> + <End></h1></End> + </Tag> + + <Tag _name="Heading 2"> + <Begin><h2></Begin> + <End></h2></End> + </Tag> + + <Tag _name="Heading 3"> + <Begin><h3></Begin> + <End></h3></End> + </Tag> + + <Tag _name="Heading 4"> + <Begin><h4></Begin> + <End></h4></End> + </Tag> + + <Tag _name="Heading 5"> + <Begin><h5></Begin> + <End></h5></End> + </Tag> + + <Tag _name="Heading 6"> + <Begin><h6></Begin> + <End></h6></End> + </Tag> + + <Tag _name="Heading"> + <Begin><h></Begin> + <End></h></End> + </Tag> + + <Tag _name="Height"> + <Begin>height=</Begin> + </Tag> + + <Tag _name="Horizontal rule"> + <Begin><hr /></Begin> + </Tag> + + <Tag _name="Horizontal space"> + <Begin>hspace=</Begin> + </Tag> + + <Tag _name="HREF URI"> + <Begin>href=</Begin> + </Tag> + + <Tag _name="HTML root element"> + <Begin><html></Begin> + <End></html></End> + </Tag> + + <Tag _name="HTTP header name"> + <Begin>http-equiv=</Begin> + </Tag> + + <Tag _name="I18N BiDi override"> + <Begin><BDO></Begin> + <End></BDO></End> + </Tag> + + <Tag _name="Image map area"> + <Begin><area></Begin> + </Tag> + + <Tag _name="Image map name"> + <Begin><map name=></Begin> + <End></map></End> + </Tag> + + <Tag _name="Image map"> + <Begin><map></Begin> + <End></map></End> + </Tag> + + <Tag _name="Image source"> + <Begin><img src=></Begin> + </Tag> + + <Tag _name="Image"> + <Begin><img></Begin> + </Tag> + + <Tag _name="Inline frame"> + <Begin><iframe></Begin> + <End></iframe></End> + </Tag> + + <Tag _name="Inline layer"> + <Begin><ilayer></Begin> + <End></ilayer></End> + </Tag> + + <Tag _name="Inserted text"> + <Begin><ins></Begin> + <End></ins></End> + </Tag> + + <Tag _name="Instance definition"> + <Begin><dfn></Begin> + <End></dfn></End> + </Tag> + + <Tag _name="Italic text"> + <Begin><i></Begin> + <End></i></End> + </Tag> + + <Tag _name="Java applet"> + <Begin><applet></Begin> + <End></applet></End> + </Tag> + + <Tag _name="Label"> + <Begin>label=</Begin> + </Tag> + + <Tag _name="Language code"> + <Begin>hreflang=</Begin> + </Tag> + + <Tag _name="Language code"> + <Begin>lang=</Begin> + </Tag> + + <Tag _name="Large text style"> + <Begin><big></Begin> + <End></big></End> + </Tag> + + <Tag _name="Layer"> + <Begin><layer></Begin> + </Tag> + + <Tag _name="Link color"> + <Begin>link=</Begin> + </Tag> + + <Tag _name="List item"> + <Begin><li></Begin> + <End></li></End> + </Tag> + + <Tag _name="List of MIME types for file upload"> + <Begin>accept=</Begin> + </Tag> + + <Tag _name="List of supported character sets"> + <Begin>accept-charset=</Begin> + </Tag> + + <Tag _name="Listing"> + <Begin><listing></Begin> + <End></listing></End> + </Tag> + + <Tag _name="Local change to font"> + <Begin><font></Begin> + <End></font></End> + </Tag> + + <Tag _name="Long description link"> + <Begin>longdesc=</Begin> + </Tag> + + <Tag _name="Long quotation"> + <Begin><blockquote></Begin> + <End></blockquote></End> + </Tag> + + <Tag _name="Mail link"> + <Begin>mailto=</Begin> + </Tag> + + <Tag _name="Margin pixel height"> + <Begin>marginheight=</Begin> + </Tag> + + <Tag _name="Margin pixel width"> + <Begin>marginwidth=</Begin> + </Tag> + + <Tag _name="Marquee"> + <Begin><marquee></Begin> + <End></marquee></End> + </Tag> + + <Tag _name="Maximum length of text field"> + <Begin>maxlength=</Begin> + </Tag> + + <Tag _name="Output media"> + <Begin>media=</Begin> + </Tag> + + <Tag _name="Media-independent link"> + <Begin><link rel="></Begin> + </Tag> + + <Tag _name="Menu list"> + <Begin><menu></Begin> + <End></menu></End> + </Tag> + + <Tag _name="Multicolumn"> + <Begin><multicol></Begin> + <End></multicol></End> + </Tag> + + <Tag _name="Multi-line text field"> + <Begin><textarea></Begin> + <End></textarea></End> + </Tag> + + <Tag _name="Multiple"> + <Begin>multiple</Begin> + </Tag> + + <Tag _name="Name"> + <Begin>name=</Begin> + </Tag> + + <Tag _name="Named property value"> + <Begin><param></Begin> + </Tag> + + <Tag _name="Next ID"> + <Begin><nextid></Begin> + </Tag> + + <Tag _name="No embedded objects"> + <Begin><noembed></Begin> + </Tag> + + <Tag _name="No frames"> + <Begin><noframes></Begin> + <End></noframes></End> + </Tag> + + <Tag _name="No layers"> + <Begin><nolayer></Begin> + <End></nolayer></End> + </Tag> + + <Tag _name="No line break"> + <Begin><nobr></Begin> + <End></nobr></End> + </Tag> + + <Tag _name="No resize"> + <Begin>noresize</Begin> + </Tag> + + <Tag _name="No script"> + <Begin><noscript></Begin> + <End></noscript></End> + </Tag> + + <Tag _name="No shade"> + <Begin>noshade</Begin> + </Tag> + + <Tag _name="No URI"> + <Begin>nohref</Begin> + </Tag> + + <Tag _name="No word wrap"> + <Begin>nowrap</Begin> + </Tag> + + <Tag _name="Note"> + <Begin><note></Begin> + <End></note></End> + </Tag> + + <Tag _name="Object applet file"> + <Begin>object=</Begin> + </Tag> + + <Tag _name="Object data reference"> + <Begin>data=</Begin> + </Tag> + + <Tag _name="Offset for alignment character"> + <Begin>charoff=</Begin> + </Tag> + + <Tag _name="OnBlur event"> + <Begin>onblur=</Begin> + </Tag> + + <Tag _name="OnChange event"> + <Begin>onchange=</Begin> + </Tag> + + <Tag _name="OnClick event"> + <Begin>onclick=</Begin> + </Tag> + + <Tag _name="OnDblClick event"> + <Begin>ondbclick=</Begin> + </Tag> + + <Tag _name="OnFocus event"> + <Begin>onfocus=</Begin> + </Tag> + + <Tag _name="OnKeyDown event"> + <Begin>onkeydown=</Begin> + </Tag> + + <Tag _name="OnKeyPress event"> + <Begin>onkeypress=</Begin> + </Tag> + + <Tag _name="OnKeyUp event"> + <Begin>onkeyup=</Begin> + </Tag> + + <Tag _name="OnLoad event"> + <Begin>onload=</Begin> + </Tag> + + <Tag _name="OnMouseDown event"> + <Begin>onmousedown=</Begin> + </Tag> + + <Tag _name="OnMouseMove event"> + <Begin>onmousemove=</Begin> + </Tag> + + <Tag _name="OnMouseOut event"> + <Begin>onmouseout=</Begin> + </Tag> + + <Tag _name="OnMouseOver event"> + <Begin>onmouseover=</Begin> + </Tag> + + <Tag _name="OnMouseUp event"> + <Begin>onmouseup=</Begin> + </Tag> + + <Tag _name="OnReset event"> + <Begin>onreset=</Begin> + </Tag> + + <Tag _name="OnSelect event"> + <Begin>onselect=</Begin> + </Tag> + + <Tag _name="OnSubmit event"> + <Begin>onsubmit=</Begin> + </Tag> + + <Tag _name="OnUnload event"> + <Begin>onunload=</Begin> + </Tag> + + <Tag _name="Option group"> + <Begin><optgroup></Begin> + <End></optgroup></End> + </Tag> + + <Tag _name="Option selector"> + <Begin><select></Begin> + <End></select></End> + </Tag> + + <Tag _name="Ordered list"> + <Begin><ol></Begin> + <End></ol></End> + </Tag> + + <Tag _name="Paragraph class"> + <Begin><p class=></Begin> + <End></p></End> + </Tag> + + <Tag _name="Paragraph style"> + <Begin><p style=></Begin> + <End></p></End> + </Tag> + + <Tag _name="Paragraph"> + <Begin><p></Begin> + <End></p></End> + </Tag> + + <Tag _name="Preformatted listing"> + <Begin><xmp></Begin> + <End></xmp></End> + </Tag> + + <Tag _name="Preformatted text"> + <Begin><pre></Begin> + <End></pre></End> + </Tag> + + <Tag _name="Profile metainfo dictionary"> + <Begin>profile=</Begin> + </Tag> + + <Tag _name="Prompt message"> + <Begin>prompt=</Begin> + </Tag> + + <Tag _name="Push button"> + <Begin><button></Begin> + <End></button></End> + </Tag> + + <Tag _name="Quote"> + <Begin><quote></Begin> + </Tag> + + <Tag _name="Range"> + <Begin><range></Begin> + </Tag> + + <Tag _name="ReadOnly text and password"> + <Begin>readonly</Begin> + </Tag> + + <Tag _name="Reduced spacing"> + <Begin>compact</Begin> + </Tag> + + <Tag _name="Reverse link"> + <Begin>rev=</Begin> + </Tag> + + <Tag _name="Root"> + <Begin><root></Begin> + </Tag> + + <Tag _name="Rows"> + <Begin>rows=</Begin> + </Tag> + + <Tag _name="Rulings between rows and columns"> + <Begin>rules=</Begin> + </Tag> + + <Tag _name="Sample program output, scripts"> + <Begin><samp></Begin> + <End></samp></End> + </Tag> + + <Tag _name="Scope covered by header cells"> + <Begin>scope=</Begin> + </Tag> + + <Tag _name="Script language name"> + <Begin>language=</Begin> + </Tag> + + <Tag _name="Script statements"> + <Begin><script></Begin> + <End></script></End> + </Tag> + + <Tag _name="Scrollbar"> + <Begin>scrolling=</Begin> + </Tag> + + <Tag _name="Selectable option"> + <Begin><option></Begin> + </Tag> + + <Tag _name="Selected"> + <Begin>selected</Begin> + </Tag> + + <Tag _name="Server-side image map"> + <Begin>ismap</Begin> + </Tag> + + <Tag _name="Shape"> + <Begin>shape=</Begin> + </Tag> + + <Tag _name="Short inline quotation"> + <Begin><q></Begin> + <End></q></End> + </Tag> + + <Tag _name="Single line prompt"> + <Begin><isindex></Begin> + </Tag> + + <Tag _name="Size"> + <Begin>size=</Begin> + </Tag> + + <Tag _name="Small text style"> + <Begin><small></Begin> + <End></small></End> + </Tag> + + <Tag _name="Soft line break"> + <Begin><wbr></Begin> + </Tag> + + <Tag _name="Sound"> + <Begin><sound></Begin> + </Tag> + + <Tag _name="Source"> + <Begin>src=</Begin> + </Tag> + + <Tag _name="Space-separated archive list"> + <Begin>archive=</Begin> + </Tag> + + <Tag _name="Spacer"> + <Begin><spacer></Begin> + </Tag> + + <Tag _name="Spacing between cells"> + <Begin>cellspacing=</Begin> + </Tag> + + <Tag _name="Spacing within cells"> + <Begin>cellpadding=</Begin> + </Tag> + + <Tag _name="Span"> + <Begin>span=</Begin> + </Tag> + + <Tag _name="Square root"> + <Begin><sqrt></Begin> + </Tag> + + <Tag _name="Standby load message"> + <Begin>standby=</Begin> + </Tag> + + <Tag _name="Starting sequence number"> + <Begin>start</Begin> + </Tag> + + <Tag _name="Strike-through text style"> + <Begin><s></Begin> + <End></s></End> + </Tag> + + <Tag _name="Strike-through text"> + <Begin><strike></Begin> + <End></strike></End> + </Tag> + + <Tag _name="Strong emphasis"> + <Begin><strong></Begin> + <End></strong></End> + </Tag> + + <Tag _name="Style info"> + <Begin><style></Begin> + <End></style></End> + </Tag> + + <Tag _name="Style info"> + <Begin>style=</Begin> + </Tag> + + <Tag _name="Subscript"> + <Begin><sub></Begin> + <End></sub></End> + </Tag> + + <Tag _name="Superscript"> + <Begin><sup></Begin> + <End></sup></End> + </Tag> + + <Tag _name="Tab order position"> + <Begin>tabindex=</Begin> + </Tag> + + <Tag _name="Table body"> + <Begin><tbody></Begin> + <End></tbody></End> + </Tag> + + <Tag _name="Table caption"> + <Begin><caption></Begin> + <End></caption></End> + </Tag> + + <Tag _name="Table column group properties"> + <Begin><colgroup></Begin> + <End></colgroup></End> + </Tag> + + <Tag _name="Table column properties"> + <Begin><col></Begin> + <End></col></End> + </Tag> + + <Tag _name="Table data cell"> + <Begin><td></Begin> + <End></td></End> + </Tag> + + <Tag _name="Table footer"> + <Begin><tfoot></Begin> + <End></tfoot></End> + </Tag> + + <Tag _name="Table header cell"> + <Begin><th></Begin> + <End></th></End> + </Tag> + + <Tag _name="Table header"> + <Begin><thead></Begin> + <End></thead></End> + </Tag> + + <Tag _name="Table row"> + <Begin><tr></Begin> + <End></tr></End> + </Tag> + + <Tag _name="Table summary"> + <Begin>summary=</Begin> + </Tag> + + <Tag _name="Table"> + <Begin><table></Begin> + <End></table></End> + </Tag> + + <Tag _name="Target - Blank"> + <Begin><a target="_blank"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Target - Parent"> + <Begin><a target="_parent"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Target - Self"> + <Begin><a target="_self"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Target - Top"> + <Begin><a target="_top"></Begin> + <End></a></End> + </Tag> + + <Tag _name="Teletype or monospace text style"> + <Begin><tt></Begin> + <End></tt></End> + </Tag> + + <Tag _name="Text color"> + <Begin>color=</Begin> + </Tag> + + <Tag _name="Text color"> + <Begin>text=</Begin> + </Tag> + + <Tag _name="Text entered by user"> + <Begin><kbd></Begin> + <End></kbd></End> + </Tag> + + <Tag _name="Text"> + <Begin><text></Begin> + </Tag> + + <Tag _name="Title"> + <Begin>title=</Begin> + </Tag> + + <Tag _name="Top margin in pixels"> + <Begin>topmargin</Begin> + </Tag> + + <Tag _name="Underlined text style"> + <Begin><u></Begin> + <End></u></End> + </Tag> + + <Tag _name="Unordered list"> + <Begin><ul></Begin> + <End></ul></End> + </Tag> + + <Tag _name="URL"> + <Begin>url=</Begin> + </Tag> + + <Tag _name="Use image map"> + <Begin>usemap=</Begin> + </Tag> + + <Tag _name="Value interpretation"> + <Begin>valuetype=</Begin> + </Tag> + + <Tag _name="Value"> + <Begin>value=</Begin> + </Tag> + + <Tag _name="Variable or program argument"> + <Begin><var></Begin> + <End></var></End> + </Tag> + + <Tag _name="Vertical cell alignment"> + <Begin>valign=</Begin> + </Tag> + + <Tag _name="Vertical space"> + <Begin>vspace=</Begin> + </Tag> + + <Tag _name="Visited link color"> + <Begin>vlink=</Begin> + </Tag> + + <Tag _name="Width"> + <Begin>width=</Begin> + </Tag> + +</TagGroup> + +<TagGroup _name="HTML - Special Characters" sort="false"> + + <Tag _name="Non-breaking space"> + <Begin>&nbsp;</Begin> + </Tag> + + <Tag name="Soft hyphen"> + <Begin>&shy;</Begin> + </Tag> + + <Tag name="""> + <Begin>&quot;</Begin> + </Tag> + + <Tag name="&"> + <Begin>&amp;</Begin> + </Tag> + + <Tag name="¡"> + <Begin>&iexcl;</Begin> + </Tag> + + <Tag name="¦"> + <Begin>&brvbar;</Begin> + </Tag> + + <Tag name="¨"> + <Begin>&uml;</Begin> + </Tag> + + <Tag name="¯"> + <Begin>&macr;</Begin> + </Tag> + + <Tag name="´"> + <Begin>&acute;</Begin> + </Tag> + + <Tag name="¸"> + <Begin>&cedil;</Begin> + </Tag> + + <Tag name="<"> + <Begin>&lt;</Begin> + </Tag> + + <Tag name=">"> + <Begin>&gt;</Begin> + </Tag> + + <Tag name="±"> + <Begin>&plusmn;</Begin> + </Tag> + + <Tag name="«"> + <Begin>&laquo;</Begin> + </Tag> + + <Tag name="»"> + <Begin>&raquo;</Begin> + </Tag> + + <Tag name="×"> + <Begin>&times;</Begin> + </Tag> + + <Tag name="÷"> + <Begin>&divide;</Begin> + </Tag> + + <Tag name="¢"> + <Begin>&cent;</Begin> + </Tag> + + <Tag name="£"> + <Begin>&pound;</Begin> + </Tag> + + <Tag name="€"> + <Begin>&euro;</Begin> + </Tag> + + <Tag name="¤"> + <Begin>&curren;</Begin> + </Tag> + + <Tag name="¥"> + <Begin>&yen;</Begin> + </Tag> + + <Tag name="§"> + <Begin>&sect;</Begin> + </Tag> + + <Tag name="©"> + <Begin>&copy;</Begin> + </Tag> + + <Tag name="¬"> + <Begin>&not;</Begin> + </Tag> + + <Tag name="®"> + <Begin>&reg;</Begin> + </Tag> + + <Tag name="™"> + <Begin>&trade;</Begin> + </Tag> + + <Tag name="°"> + <Begin>&deg;</Begin> + </Tag> + + <Tag name="µ"> + <Begin>&micro;</Begin> + </Tag> + + <Tag name="¶"> + <Begin>&para;</Begin> + </Tag> + + <Tag name="·"> + <Begin>&middot;</Begin> + </Tag> + + <Tag name="¼"> + <Begin>&frac14;</Begin> + </Tag> + + <Tag name="½"> + <Begin>&frac12;</Begin> + </Tag> + + <Tag name="¾"> + <Begin>&frac34;</Begin> + </Tag> + + <Tag name="¹"> + <Begin>&sup1;</Begin> + </Tag> + + <Tag name="²"> + <Begin>&sup2;</Begin> + </Tag> + + <Tag name="³"> + <Begin>&sup3;</Begin> + </Tag> + + <Tag name="á"> + <Begin>&aacute;</Begin> + </Tag> + + <Tag name="Á"> + <Begin>&Aacute;</Begin> + </Tag> + + <Tag name="â"> + <Begin>&acirc;</Begin> + </Tag> + + <Tag name="Â"> + <Begin>&Acirc;</Begin> + </Tag> + + <Tag name="à"> + <Begin>&agrave;</Begin> + </Tag> + + <Tag name="À"> + <Begin>&Agrave;</Begin> + </Tag> + + <Tag name="å"> + <Begin>&aring;</Begin> + </Tag> + + <Tag name="Å"> + <Begin>&Aring;</Begin> + </Tag> + + <Tag name="ã"> + <Begin>&atilde;</Begin> + </Tag> + + <Tag name="Ã"> + <Begin>&Atilde;</Begin> + </Tag> + + <Tag name="ä"> + <Begin>&auml;</Begin> + </Tag> + + <Tag name="Ä"> + <Begin>&Auml;</Begin> + </Tag> + + <Tag name="ª"> + <Begin>&ordf;</Begin> + </Tag> + + <Tag name="æ"> + <Begin>&aelig;</Begin> + </Tag> + + <Tag name="Æ"> + <Begin>&AElig;</Begin> + </Tag> + + <Tag name="ç"> + <Begin>&ccedil;</Begin> + </Tag> + + <Tag name="Ç"> + <Begin>&Ccedil;</Begin> + </Tag> + + <Tag name="Ð"> + <Begin>&ETH;</Begin> + </Tag> + + <Tag name="ð"> + <Begin>&eth;</Begin> + </Tag> + + <Tag name="é"> + <Begin>&eacute;</Begin> + </Tag> + + <Tag name="É"> + <Begin>&Eacute;</Begin> + </Tag> + + <Tag name="ê"> + <Begin>&ecirc;</Begin> + </Tag> + + <Tag name="Ê"> + <Begin>&Ecirc;</Begin> + </Tag> + + <Tag name="è"> + <Begin>&egrave;</Begin> + </Tag> + + <Tag name="È"> + <Begin>&Egrave;</Begin> + </Tag> + + <Tag name="ë"> + <Begin>&euml;</Begin> + </Tag> + + <Tag name="Ë"> + <Begin>&Euml;</Begin> + </Tag> + + <Tag name="í"> + <Begin>&iacute;</Begin> + </Tag> + + <Tag name="Í"> + <Begin>&Iacute;</Begin> + </Tag> + + <Tag name="î"> + <Begin>&icirc;</Begin> + </Tag> + + <Tag name="Î"> + <Begin>&Icirc;</Begin> + </Tag> + + <Tag name="ì"> + <Begin>&igrave;</Begin> + </Tag> + + <Tag name="Ì"> + <Begin>&Igrave;</Begin> + </Tag> + + <Tag name="ï"> + <Begin>&iuml;</Begin> + </Tag> + + <Tag name="Ï"> + <Begin>&Iuml;</Begin> + </Tag> + + <Tag name="ñ"> + <Begin>&ntilde;</Begin> + </Tag> + + <Tag name="Ñ"> + <Begin>&Ntilde;</Begin> + </Tag> + + <Tag name="ó"> + <Begin>&oacute;</Begin> + </Tag> + + <Tag name="Ó"> + <Begin>&Oacute;</Begin> + </Tag> + + <Tag name="ô"> + <Begin>&ocirc;</Begin> + </Tag> + + <Tag name="Ô"> + <Begin>&Ocirc;</Begin> + </Tag> + + <Tag name="ò"> + <Begin>&ograve;</Begin> + </Tag> + + <Tag name="Ò"> + <Begin>&Ograve;</Begin> + </Tag> + + <Tag name="º"> + <Begin>&ordm;</Begin> + </Tag> + + <Tag name="ø"> + <Begin>&oslash;</Begin> + </Tag> + + <Tag name="Ø"> + <Begin>&Oslash;</Begin> + </Tag> + + <Tag name="õ"> + <Begin>&otilde;</Begin> + </Tag> + + <Tag name="Õ"> + <Begin>&Otilde;</Begin> + </Tag> + + <Tag name="ö"> + <Begin>&ouml;</Begin> + </Tag> + + <Tag name="Ö"> + <Begin>&Ouml;</Begin> + </Tag> + + <Tag name="ß"> + <Begin>&szlig;</Begin> + </Tag> + + <Tag name="þ"> + <Begin>&thorn;</Begin> + </Tag> + + <Tag name="Þ"> + <Begin>&THORN;</Begin> + </Tag> + + <Tag name="ú"> + <Begin>&uacute;</Begin> + </Tag> + + <Tag name="Ú"> + <Begin>&Uacute;</Begin> + </Tag> + + <Tag name="û"> + <Begin>&ucirc;</Begin> + </Tag> + + <Tag name="Û"> + <Begin>&Ucirc;</Begin> + </Tag> + + <Tag name="ù"> + <Begin>&ugrave;</Begin> + </Tag> + + <Tag name="Ù"> + <Begin>&Ugrave;</Begin> + </Tag> + + <Tag name="ü"> + <Begin>&uuml;</Begin> + </Tag> + + <Tag name="Ü"> + <Begin>&Uuml;</Begin> + </Tag> + + <Tag name="ý"> + <Begin>&yacute;</Begin> + </Tag> + + <Tag name="Ý"> + <Begin>&Yacute;</Begin> + </Tag> + + <Tag name="ÿ"> + <Begin>&yuml;</Begin> + </Tag> + +</TagGroup> +</TagList> diff --git a/plugins/taglist/Latex.tags.xml.in b/plugins/taglist/Latex.tags.xml.in new file mode 100755 index 00000000..5ac71c0d --- /dev/null +++ b/plugins/taglist/Latex.tags.xml.in @@ -0,0 +1,344 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TagList xmlns="http://gedit.sourceforge.net/some-location"> +<TagGroup _name="Latex - Tags" sort="true"> + <Tag _name="Bibliography (cite)"> + <Begin>\cite{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Bibliography (item)"> + <Begin>\bibitem{ </Begin> + <End> }{} </End> + </Tag> + + <Tag _name="Bibliography (shortcite)"> + <Begin>\shortcite{ </Begin> + <End> }</End> + </Tag> + + <Tag _name="Bibliography (thebibliography)"> + <Begin>\begin{thebibliography}{99} </Begin> + <End> \end{thebibliography}</End> + </Tag> + + <Tag _name="Brackets ()"> + <Begin>\left( </Begin> + <End> \right) </End> + </Tag> + + <Tag _name="Brackets []"> + <Begin>\left[ </Begin> + <End> \right] </End> + </Tag> + + <Tag _name="Brackets {}"> + <Begin>\left\{ </Begin> + <End> \right\} </End> + </Tag> + + <Tag _name="Brackets <>"> + <Begin>\left\langle </Begin> + <End> \right\rangle </End> + </Tag> + + <Tag _name="File input"> + <Begin>\input{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Function cosine"> + <Begin>\cos{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Function e^"> + <Begin>e^{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Function exp"> + <Begin>\exp{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Function log"> + <Begin>\log{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Function log10"> + <Begin>\log_{10}{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Function sine"> + <Begin>\sin{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Greek alpha"> + <Begin>\alpha </Begin> + </Tag> + + <Tag _name="Greek beta"> + <Begin>\beta </Begin> + </Tag> + + <Tag _name="Greek epsilon"> + <Begin>\varepsilon </Begin> + </Tag> + + <Tag _name="Greek gamma"> + <Begin>\gamma </Begin> + </Tag> + + <Tag _name="Greek lambda"> + <Begin>\lambda </Begin> + </Tag> + + <Tag _name="Greek rho"> + <Begin>\rho </Begin> + </Tag> + + <Tag _name="Greek tau"> + <Begin>\tau </Begin> + </Tag> + + <Tag _name="Header 0 (chapter)"> + <Begin>\chapter{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 0 (chapter*)"> + <Begin>\chapter*{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 1 (section)"> + <Begin>\section{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 1 (section*)"> + <Begin>\section*{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 2 (subsection)"> + <Begin>\subsection{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 2 (subsection*)"> + <Begin>\subsection*{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 3 (subsubsection)"> + <Begin>\subsubsection{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 3 (subsubsection*)"> + <Begin>\subsubsection*{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header 4 (paragraph)"> + <Begin>\paragraph{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Header appendix"> + <Begin>\appendix </Begin> + </Tag> + + <Tag _name="List description"> + <Begin>\begin{description} </Begin> + <End> \end{description} </End> + </Tag> + + <Tag _name="List enumerate"> + <Begin>\begin{enumerate} </Begin> + <End> \end{enumerate} </End> + </Tag> + + <Tag _name="List itemize"> + <Begin>\begin{itemize} </Begin> + <End> \end{itemize} </End> + </Tag> + + <Tag _name="Item with label"> + <Begin>\item[ </Begin> + <End> ]</End> + </Tag> + + <Tag _name="Item"> + <Begin>\item </Begin> + </Tag> + + <Tag _name="Maths (display)"> + <Begin> $$ </Begin> + <End> $$ </End> + </Tag> + + <Tag _name="Maths (inline)"> + <Begin>$ </Begin> + <End> $ </End> + </Tag> + + <Tag _name="Operator fraction"> + <Begin>\frac{ </Begin> + <End> }{} </End> + </Tag> + + <Tag _name="Operator integral (display)"> + <Begin>\int\limits_{ </Begin> + <End> }^{} </End> + </Tag> + + <Tag _name="Operator integral (inline)"> + <Begin>\int_{ </Begin> + <End> }^{} </End> + </Tag> + + <Tag _name="Operator sum (display)"> + <Begin>\sum\limits_{ </Begin> + <End> }^{} </End> + </Tag> + + <Tag _name="Operator sum (inline)"> + <Begin>\sum_{ </Begin> + <End> }^{} </End> + </Tag> + + <Tag _name="Reference label"> + <Begin>\label{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Reference ref"> + <Begin>{\protect\ref{ </Begin> + <End> }} </End> + </Tag> + + <Tag _name="Symbol <<"> + <Begin>\ll </Begin> + </Tag> + + <Tag _name="Symbol <="> + <Begin>\le </Begin> + </Tag> + + <Tag _name="Symbol >="> + <Begin>\ge </Begin> + </Tag> + + <Tag _name="Symbol >>"> + <Begin>\gg </Begin> + </Tag> + + <Tag _name="Symbol and"> + <Begin>\mathrm{and} </Begin> + </Tag> + + <Tag _name="Symbol const"> + <Begin>\mathrm{const} </Begin> + </Tag> + + <Tag _name="Symbol d2-by-dt2-partial"> + <Begin>\frac{\partial{ </Begin> + <End> }^2}{\partial t^2} </End> + </Tag> + + <Tag _name="Symbol dagger"> + <Begin>\dagger </Begin> + </Tag> + + <Tag _name="Symbol d-by-dt"> + <Begin>\frac{{\mathrm{d}} </Begin> + <End> }{{\mathrm{d}}t} </End> + </Tag> + + <Tag _name="Symbol d-by-dt-partial"> + <Begin>\frac{\partial </Begin> + <End> }{\partial t} </End> + </Tag> + + <Tag _name="Symbol equiv"> + <Begin>\equiv </Begin> + </Tag> + + <Tag _name="Symbol en-dash --"> + <Begin>-- </Begin> + </Tag> + + <Tag _name="Symbol em-dash ---"> + <Begin>--- </Begin> + </Tag> + + <Tag _name="Symbol infinity"> + <Begin>\infty </Begin> + </Tag> + + <Tag _name="Symbol mathspace ,"> + <Begin>\quad\mathrm{,} </Begin> + </Tag> + + <Tag _name="Symbol mathspace ."> + <Begin>\quad\mathrm{.} </Begin> + </Tag> + + <Tag _name="Symbol mathspace _"> + <Begin>\quad </Begin> + </Tag> + + <Tag _name="Symbol mathspace __"> + <Begin>\qquad </Begin> + </Tag> + + <Tag _name="Symbol simeq"> + <Begin>\simeq </Begin> + </Tag> + + <Tag _name="Symbol star"> + <Begin>\star </Begin> + </Tag> + + <Tag _name="Typeface bold"> + <Begin>\textbf{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Typeface type"> + <Begin>\texttt{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Typeface italic"> + <Begin>\textit{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Typeface slanted"> + <Begin>\textsl{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Emphasis"> + <Begin>\emph{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Unbreakable text"> + <Begin>\mbox{ </Begin> + <End> } </End> + </Tag> + + <Tag _name="Footnote"> + <Begin>\footnote{ </Begin> + <End> } </End> + </Tag> + +</TagGroup> +</TagList> diff --git a/plugins/taglist/Makefile.am b/plugins/taglist/Makefile.am new file mode 100755 index 00000000..b04f4584 --- /dev/null +++ b/plugins/taglist/Makefile.am @@ -0,0 +1,60 @@ +# Tag list plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +taglistdir = $(GEDIT_PLUGINS_DATA_DIR)/taglist + +taglist_in_files = \ + HTML.tags.xml.in \ + Latex.tags.xml.in \ + XSLT.tags.xml.in \ + XUL.tags.xml.in + +taglist_DATA = $(taglist_in_files:.tags.xml.in=.tags.gz) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libtaglist.la + +libtaglist_la_SOURCES = \ + gedit-taglist-plugin-parser.c \ + gedit-taglist-plugin-parser.h \ + gedit-taglist-plugin-panel.c \ + gedit-taglist-plugin-panel.h \ + gedit-taglist-plugin.c \ + gedit-taglist-plugin.h + +libtaglist_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libtaglist_la_LIBADD = $(GEDIT_LIBS) + +plugin_in_files = taglist.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) + $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +GZIP_ENV = -9 + +%.tags.gz: %.tags.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) + LC_ALL=C $(INTLTOOL_MERGE) $(top_srcdir)/po $< $(@:.gz=) -x -u -c $(top_builddir)/po/.intltool-merge-cache + GZIP=$(GZIP_ENV) gzip -f $(@:.gz=) + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = \ + $(taglist_in_files) $(taglist_DATA) \ + $(plugin_in_files) $(plugin_DATA) + +CLEANFILES = \ + $(taglist_DATA) \ + $(plugin_DATA) +DISTCLEANFILES = \ + $(taglist_DATA) \ + $(plugin_DATA) + + + + +-include $(top_srcdir)/git.mk diff --git a/plugins/taglist/XSLT.tags.xml.in b/plugins/taglist/XSLT.tags.xml.in new file mode 100755 index 00000000..f9591a25 --- /dev/null +++ b/plugins/taglist/XSLT.tags.xml.in @@ -0,0 +1,337 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + XSLT Tags for gedit + Jens Askengren <[email protected]> +--> +<TagList xmlns="http://gedit.sourceforge.net/some-location"> +<TagGroup _name="XSLT - Elements" sort="true"> + <Tag name="apply-imports"> + <Begin><xsl:apply-imports></Begin> + <End></xsl:apply-imports></End> + </Tag> + <Tag name="apply-templates"> + <Begin><xsl:apply-templates select="</Begin> + <End>"/></End> + </Tag> + <Tag name="attribute"> + <Begin><xsl:attribute name="</Begin> + <End>"></xsl:attribute></End> + </Tag> + <Tag name="attribute-set"> + <Begin><xsl:attribute-set name="</Begin> + <End>"></xsl:attribute-set></End> + </Tag> + <Tag name="call-template"> + <Begin><xsl:call-template name="</Begin> + <End>"/></End> + </Tag> + <Tag name="choose"> + <Begin><xsl:choose> </Begin> + <End> </xsl:choose></End> + </Tag> + <Tag name="comment"> + <Begin><xsl:comment></Begin> + <End></xsl:comment></End> + </Tag> + <Tag name="copy"> + <Begin><xsl:copy/></Begin> + </Tag> + <Tag name="copy-of"> + <Begin><xsl:copy-of select="</Begin> + <End>"/></End> + </Tag> + <Tag name="decimal-format"> + <Begin><xsl:decimal-format/></Begin> + </Tag> + <Tag name="element"> + <Begin><xsl:element name="</Begin> + <End>"></xsl:element></End> + </Tag> + <Tag name="fallback"> + <Begin><xsl:fallback></Begin> + <End></xsl:fallback></End> + </Tag> + <Tag name="for-each"> + <Begin><xsl:for-each select="</Begin> + <End>"> </xsl:for-each></End> + </Tag> + <Tag name="if"> + <Begin><xsl:if test="</Begin> + <End>"> </xsl:if></End> + </Tag> + <Tag name="import"> + <Begin><xsl:import href="</Begin> + <End>"/></End> + </Tag> + <Tag name="include"> + <Begin><xsl:include href="</Begin> + <End>"/></End> + </Tag> + <Tag name="key"> + <Begin><xsl:key name="</Begin> + <End>" match="" use=""/></End> + </Tag> + <Tag name="message"> + <Begin><xsl:message></Begin> + <End></xsl:message></End> + </Tag> + <Tag name="namespace-alias"> + <Begin><xsl:namespace-alias stylesheet-prefix="</Begin> + <End>" result-prefix=""/></End> + </Tag> + <Tag name="number"> + <Begin><xsl:number/></Begin> + </Tag> + <Tag name="otherwise"> + <Begin><xsl:otherwise></Begin> + <End></xsl:otherwise></End> + </Tag> + <Tag name="output"> + <Begin><xsl:output encoding="utf-8" method="</Begin> + <End>"/></End> + </Tag> + <Tag name="param"> + <Begin><xsl:param name="</Begin> + <End>"></xsl:param></End> + </Tag> + <Tag name="preserve-space"> + <Begin><xsl:preserve-space elements="</Begin> + <End>"></xsl:preserve-space></End> + </Tag> + <Tag name="processing-instruction"> + <Begin><xsl:processing-instruction name="</Begin> + <End>"></xsl:processing-instruction></End> + </Tag> + <Tag name="sort"> + <Begin><xsl:sort/></Begin> + </Tag> + <Tag name="strip-space"> + <Begin><xsl:strip-space elements="</Begin> + <End>"></xsl:strip-space></End> + </Tag> + <Tag name="stylesheet"> + <Begin><xsl:stylesheet version="</Begin> + <End>"/> </xsl:stylesheet></End> + </Tag> + <Tag name="template"> + <Begin><xsl:template match="</Begin> + <End>"> </xsl:template></End> + </Tag> + <Tag name="text"> + <Begin><xsl:text/></Begin> + </Tag> + <Tag name="value-of"> + <Begin><xsl:value-of select="</Begin> + <End>"/></End> + </Tag> + <Tag name="variable"> + <Begin><xsl:variable select="</Begin> + <End>"/></End> + </Tag> + <Tag name="when"> + <Begin><xsl:when test="</Begin> + <End>> </xsl:when></End> + </Tag> + <Tag name="with-param"> + <Begin><xsl:with-param name="</Begin> + <End>"></xsl:with-param></End> + </Tag> +</TagGroup> + +<TagGroup _name="XSLT - Functions" sort="true"> + <Tag name="boolean"> + <Begin>boolean(</Begin> + <End>)</End> + </Tag> + <Tag name="ceiling"> + <Begin>ceiling(</Begin> + <End>)</End> + </Tag> + <Tag name="comment"> + <Begin>comment()</Begin> + </Tag> + <Tag name="concat"> + <Begin>concat(</Begin> + <End>,,)</End> + </Tag> + <Tag name="contains"> + <Begin>contains(</Begin> + <End>,)</End> + </Tag> + <Tag name="count"> + <Begin>count(</Begin> + <End>)</End> + </Tag> + <Tag name="current"> + <Begin>current()</Begin> + </Tag> + <Tag name="document"> + <Begin>document(</Begin> + <End>,)</End> + </Tag> + <Tag name="element-available"> + <Begin>element-available(</Begin> + <End>)</End> + </Tag> + <Tag name="false"> + <Begin>false()</Begin> + </Tag> + <Tag name="floor"> + <Begin>floor(</Begin> + <End>)</End> + </Tag> + <Tag name="format-number"> + <Begin>format-number(</Begin> + <End>,,)</End> + </Tag> + <Tag name="function-available"> + <Begin>function-available(</Begin> + <End>)</End> + </Tag> + <Tag name="generate-id"> + <Begin>generate-id(</Begin> + <End>)</End> + </Tag> + <Tag name="id"> + <Begin>id(</Begin> + <End>)</End> + </Tag> + <Tag name="key"> + <Begin>key(</Begin> + <End>,)</End> + </Tag> + <Tag name="lang"> + <Begin>lang(</Begin> + <End>)</End> + </Tag> + <Tag name="last"> + <Begin>last()</Begin> + </Tag> + <Tag name="local-name"> + <Begin>local-name(</Begin> + <End>)</End> + </Tag> + <Tag name="name"> + <Begin>name(</Begin> + <End>)</End> + </Tag> + <Tag name="namespace-uri"> + <Begin>namespace-uri(</Begin> + <End>)</End> + </Tag> + <Tag name="node"> + <Begin>node()</Begin> + </Tag> + <Tag name="normalize-space"> + <Begin>normalize-space(</Begin> + <End>)</End> + </Tag> + <Tag name="not"> + <Begin>not(</Begin> + <End>)</End> + </Tag> + <Tag name="number"> + <Begin>number(</Begin> + <End>)</End> + </Tag> + <Tag name="position"> + <Begin>position()</Begin> + </Tag> + <Tag name="processing-instruction"> + <Begin>processing-instruction(</Begin> + <End>)</End> + </Tag> + <Tag name="round"> + <Begin>round(</Begin> + <End>)</End> + </Tag> + <Tag name="starts-with"> + <Begin>starts-with(</Begin> + <End>,)</End> + </Tag> + <Tag name="string"> + <Begin>string(</Begin> + <End>)</End> + </Tag> + <Tag name="string-length"> + <Begin>string-length(</Begin> + <End>)</End> + </Tag> + <Tag name="substring"> + <Begin>substring(</Begin> + <End>,,)</End> + </Tag> + <Tag name="substring-after"> + <Begin>substring-after(</Begin> + <End>,)</End> + </Tag> + <Tag name="substring-before"> + <Begin>substring-before(</Begin> + <End>,)</End> + </Tag> + <Tag name="sum"> + <Begin>sum(</Begin> + <End>)</End> + </Tag> + <Tag name="system-property"> + <Begin>system-property(</Begin> + <End>)</End> + </Tag> + <Tag name="text"> + <Begin>text()</Begin> + </Tag> + <Tag name="translate"> + <Begin>translate(</Begin> + <End>,,)</End> + </Tag> + <Tag name="true"> + <Begin>true()</Begin> + </Tag> + <Tag name="unparsed-entity-uri"> + <Begin>unparsed-entity-uri(</Begin> + <End>)</End> + </Tag> +</TagGroup> + +<TagGroup _name="XSLT - Axes" sort="true"> + <Tag _name="ancestor"> + <Begin>ancestor::</Begin> + </Tag> + <Tag _name="ancestor-or-self"> + <Begin>ancestor-or-self::</Begin> + </Tag> + <Tag _name="attribute"> + <Begin>@</Begin> + </Tag> + <Tag _name="child"> + <Begin>child::</Begin> + </Tag> + <Tag _name="descendant"> + <Begin>descendant::</Begin> + </Tag> + <Tag _name="descendant-or-self"> + <Begin>//</Begin> + </Tag> + <Tag _name="following"> + <Begin>following::</Begin> + </Tag> + <Tag _name="following-sibling"> + <Begin>following-sibling::</Begin> + </Tag> + <Tag _name="namespace"> + <Begin>namespace::</Begin> + </Tag> + <Tag _name="parent"> + <Begin>..</Begin> + </Tag> + <Tag _name="preceding"> + <Begin>preceding::</Begin> + </Tag> + <Tag _name="preceding-sibling"> + <Begin>preceding-sibling::</Begin> + </Tag> + <Tag _name="self"> + <Begin>.</Begin> + </Tag> +</TagGroup> +</TagList> diff --git a/plugins/taglist/XUL.tags.xml.in b/plugins/taglist/XUL.tags.xml.in new file mode 100755 index 00000000..fc4c26a7 --- /dev/null +++ b/plugins/taglist/XUL.tags.xml.in @@ -0,0 +1,536 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TagList xmlns="http://gedit.sourceforge.net/some-location"> +<TagGroup _name="XUL - Tags" sort="true"> + + <Tag name="action"> + <Begin><action></Begin> + <End></action></End> + </Tag> + + <Tag name="arrowscrollbox"> + <Begin><arrowscrollbox orient="vertical" flex="1"></Begin> + <End></arrowscrollbox></End> + </Tag> + + <Tag name="bbox"> + <Begin><bbox></Begin> + <End></bbox></End> + </Tag> + + <Tag name="binding"> + <Begin><binding id=""></Begin> + <End></binding></End> + </Tag> + + <Tag name="bindings"> + <Begin><bindings xmlns="http://www.mozilla.org/xbl"></Begin> + <End></bindings></End> + </Tag> + + <Tag name="box"> + <Begin><box orient=""></Begin> + <End></box></End> + </Tag> + + <Tag name="broadcaster"> + <Begin><broadcaster id="" label=""/></Begin> + </Tag> + + <Tag name="broadcasterset"> + <Begin><broadcasterset></Begin> + <End></broadcasterset></End> + </Tag> + + <Tag name="button"> + <Begin><button id="" label="" oncommand=""/></Begin> + </Tag> + + <Tag name="browser"> + <Begin><browser src="" type="content" flex="1"/></Begin> + </Tag> + + <Tag name="checkbox"> + <Begin><checkbox id="" checked="false" label=""/></Begin> + </Tag> + + <Tag name="caption"> + <Begin><caption label=""/></Begin> + </Tag> + + <Tag name="colorpicker"> + <Begin><colorpicker></Begin> + <End></colorpicker></End> + </Tag> + + <Tag name="column"> + <Begin><column flex="1"/></Begin> + </Tag> + + <Tag name="columns"> + <Begin><columns></Begin> + <End></columns></End> + </Tag> + + <Tag name="commandset"> + <Begin><commandset id="" commandupdater="true" events="focus" oncommandupdate=""/></Begin> + <End></commandset></End> + </Tag> + + <Tag name="command"> + <Begin><command id="" oncommand=""/></Begin> + </Tag> + + <Tag name="conditions"> + <Begin><conditions></Begin> + <End></conditions></End> + </Tag> + + <Tag name="content"> + <Begin><content uri=""/></Begin> + </Tag> + + <Tag name="deck"> + <Begin><deck selectedIndex="1"></Begin> + <End></deck></End> + </Tag> + + <Tag name="description"> + <Begin><description></Begin> + <End></description></End> + </Tag> + + <Tag name="dialog"> + <Begin><dialog id="" title="" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + ondialogaccept="" + ondialogcancel=""></Begin> + <End></dialog></End> + </Tag> + + <Tag name="dialogheader"> + <Begin><dialogheader title="" description=""/></Begin> + </Tag> + + <Tag name="editor"> + <Begin><editor src=""/></Begin> + </Tag> + + <Tag name="grid"> + <Begin><grid></Begin> + <End></grid></End> + </Tag> + + <Tag name="grippy"> + <Begin><grippy></Begin> + <End></grippy></End> + </Tag> + + <Tag name="groupbox"> + <Begin><groupbox flex="1"><caption label=""></Begin> + <End></groupbox></End> + </Tag> + + <Tag name="hbox"> + <Begin><hbox flex="1"></Begin> + <End></hbox></End> + </Tag> + + <Tag name="iframe"> + <Begin><iframe id="" src="" flex="1"/></Begin> + </Tag> + + <Tag name="image"> + <Begin><image id="" src=""/></Begin> + </Tag> + + <Tag name="key"> + <Begin><key id="" modifiers="control alt" key="" onkeypress=""/></Begin> + </Tag> + + <Tag name="keyset"> + <Begin><keyset></Begin> + <End></keyset></End> + </Tag> + + <Tag name="label"> + <Begin><label value="" control=""/></Begin> + </Tag> + + <Tag name="listbox"> + <Begin><listbox rows=""></Begin> + <End></listbox></End> + </Tag> + + <Tag name="listcell"> + <Begin><listcell label=""/></Begin> + </Tag> + + <Tag name="listcol"> + <Begin><listcol></Begin> + <End></listcol></End> + </Tag> + + <Tag name="listcols"> + <Begin><listcols></Begin> + <End></listcols></End> + </Tag> + + <Tag name="listhead"> + <Begin><listhead></Begin> + <End></listhead></End> + </Tag> + + <Tag name="listheader"> + <Begin><listheader label="" flex="1"/></Begin> + </Tag> + + <Tag name="listitem"> + <Begin><listitem id="" value="" label=""/></Begin> + </Tag> + + <Tag name="member"> + <Begin><member container="" child=""/></Begin> + </Tag> + + <Tag name="menu"> + <Begin><menu accesskey=""></Begin> + <End></menu></End> + </Tag> + + <Tag name="menubar"> + <Begin><menubar></Begin> + <End></menubar></End> + </Tag> + + <Tag name="menuitem"> + <Begin><menuitem value="" label="" accesskey="" acceltext="" oncommand=""/></Begin> + </Tag> + + <Tag name="menulist"> + <Begin><menulist label=""></Begin> + <End></menulist></End> + </Tag> + + <Tag name="menupopup"> + <Begin><menupopup id=""></Begin> + <End></menupopup></End> + </Tag> + + <Tag name="menuseparator"> + <Begin><menuseparator/></Begin> + </Tag> + + <Tag name="observes"> + <Begin><observes element="" attribute="" onbroadcast=""/></Begin> + </Tag> + + <Tag name="overlay"> + <Begin><overlay id="" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"></Begin> + <End></overlay></End> + </Tag> + + <Tag name="page"> + <Begin><page></Begin> + <End></page></End> + </Tag> + + <Tag name="popup"> + <Begin><popup id="" position="after_start"></Begin> + <End></popup></End> + </Tag> + + <Tag name="popupset"> + <Begin><popupset></Begin> + <End></popupset></End> + </Tag> + + <Tag name="preference"> + <Begin><preference></Begin> + <End></preference></End> + </Tag> + + <Tag name="preferences"> + <Begin><preferences></Begin> + <End></preferences></End> + </Tag> + + <Tag name="prefpane"> + <Begin><prefpane></Begin> + <End></prefpane></End> + </Tag> + + <Tag name="prefwindow"> + <Begin><prefwindow></Begin> + <End></prefwindow></End> + </Tag> + + <Tag name="progressmeter"> + <Begin><progressmeter id="" mode="determined" value="50%"/></Begin> + </Tag> + + <Tag name="radio"> + <Begin><radio id="" value="" name="" label=""/></Begin> + </Tag> + + <Tag name="radiogroup"> + <Begin><radiogroup></Begin> + <End></radiogroup></End> + </Tag> + + <Tag name="resizer"> + <Begin><resizer></Begin> + <End></resizer></End> + </Tag> + + <Tag name="richlistbox"> + <Begin><richlistbox></Begin> + <End></richlistbox></End> + </Tag> + + <Tag name="richlistitem"> + <Begin><richlistitem></Begin> + <End></richlistitem></End> + </Tag> + + <Tag name="row"> + <Begin><row flex="1"></Begin> + <End></row></End> + </Tag> + + <Tag name="rows"> + <Begin><rows></Begin> + <End></rows></End> + </Tag> + + <Tag name="rule"> + <Begin><rule></Begin> + <End></rule></End> + </Tag> + + <Tag name="script"> + <Begin><script src="" language="Javascript"></Begin> + <End></script></End> + </Tag> + + <Tag name="scrollbar"> + <Begin><scrollbar id="" orient="vertical" curpos="20" maxpos="100" increment="1" pageincrement="10"/></Begin> + </Tag> + + <Tag name="scrollbox"> + <Begin><scrollbox></Begin> + <End></scrollbox></End> + </Tag> + + <Tag name="scrollcorner"> + <Begin><scrollcorner></Begin> + <End></scrollcorner></End> + </Tag> + + <Tag name="separator"> + <Begin><separator></Begin> + <End></separator></End> + </Tag> + + <Tag name="spacer"> + <Begin><spacer flex="1"/></Begin> + </Tag> + + <Tag name="splitter"> + <Begin><splitter state="open" collapse="before" resizebefore="closest" resizeafter="grow"><grippy/></Begin> + <End></splitter></End> + </Tag> + + <Tag name="stack"> + <Begin><stack></Begin> + <End></stack></End> + </Tag> + + <Tag name="statusbar"> + <Begin><statusbar></Begin> + <End></statusbar></End> + </Tag> + + <Tag name="statusbarpanel"> + <Begin><statusbarpanel id="" label=""></Begin> + <End></statusbarpanel></End> + </Tag> + + <Tag name="stringbundle"> + <Begin><stringbundle id="" src=""/></Begin> + </Tag> + + <Tag name="stringbundleset"> + <Begin><stringbundleset></Begin> + <End></stringbundleset></End> + </Tag> + + <Tag name="tab"> + <Begin><tab id="" label=""/></Begin> + </Tag> + + <Tag name="tabbrowser"> + <Begin><tabbrowser></Begin> + <End></tabbrowser></End> + </Tag> + + <Tag name="tabbox"> + <Begin><tabbox id=""></Begin> + <End></tabbox></End> + </Tag> + + <Tag name="tabpanel"> + <Begin><tabpanel id=""></Begin> + <End></tabpanel></End> + </Tag> + + <Tag name="tabpanels"> + <Begin><tabpanels></Begin> + <End></tabpanels></End> + </Tag> + + <Tag name="tabs"> + <Begin><tabs></Begin> + <End></tabs></End> + </Tag> + + <Tag name="template"> + <Begin><template></Begin> + <End></template></End> + </Tag> + + <Tag name="textnode"> + <Begin><textnode></Begin> + <End></textnode></End> + </Tag> + + <Tag name="textbox"> + <Begin><textbox id="" value="" multiline="false"/></Begin> + </Tag> + + <Tag name="titlebar"> + <Begin><titlebar></Begin> + <End></titlebar></End> + </Tag> + + <Tag name="toolbar"> + <Begin><toolbar id=""></Begin> + <End></toolbar></End> + </Tag> + + <Tag name="toolbarbutton"> + <Begin><toolbarbutton id="" label=""/></Begin> + </Tag> + + <Tag name="toolbargrippy"> + <Begin><toolbargrippy></Begin> + <End></toolbargrippy></End> + </Tag> + + <Tag name="toolbaritem"> + <Begin><toolbaritem></Begin> + <End></toolbaritem></End> + </Tag> + + <Tag name="toolbarpalette"> + <Begin><toolbarpalette></Begin> + <End></toolbarpalette></End> + </Tag> + + <Tag name="toolbarseparator"> + <Begin><toolbarseparator></Begin> + <End></toolbarseparator></End> + </Tag> + + <Tag name="toolbarset"> + <Begin><toolbarset></Begin> + <End></toolbarset></End> + </Tag> + + <Tag name="toolbarspacer"> + <Begin><toolbarspacer></Begin> + <End></toolbarspacer></End> + </Tag> + + <Tag name="toolbarspring"> + <Begin><toolbarspring></Begin> + <End></toolbarspring></End> + </Tag> + + <Tag name="toolbox"> + <Begin><toolbox></Begin> + <End></toolbox></End> + </Tag> + + <Tag name="tooltip"> + <Begin><tooltip id="" orient="vertical"></Begin> + <End></tooltip></End> + </Tag> + + <Tag name="tree"> + <Begin><tree id="" flex="1"></Begin> + <End></tree></End> + </Tag> + + <Tag name="treecell"> + <Begin><treecell label=""/></Begin> + </Tag> + + <Tag name="treechildren"> + <Begin><treechildren></Begin> + <End></treechildren></End> + </Tag> + + <Tag name="treecol"> + <Begin><treecol label="" flex="1" primary="true"/></Begin> + </Tag> + + <Tag name="treecols"> + <Begin><treecols></Begin> + <End></treecols></End> + </Tag> + + <Tag name="treeitem"> + <Begin><treeitem container="true" open="true"></Begin> + <End></treeitem></End> + </Tag> + + <Tag name="treerow"> + <Begin><treerow></Begin> + <End></treerow></End> + </Tag> + + <Tag name="treeseparator"> + <Begin><treeseparator></Begin> + <End></treeseparator></End> + </Tag> + + <Tag name="triple"> + <Begin><triple></Begin> + <End></triple></End> + </Tag> + + <Tag name="vbox"> + <Begin><vbox flex="1"></Begin> + <End></vbox></End> + </Tag> + + <Tag name="window"> + <Begin><window id="" title="" orient="horizontal" pageid="" next="" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"></Begin> + <End></window></End> + </Tag> + + <Tag name="wizard"> + <Begin><wizard id="" title="" wizardnext="" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"></Begin> + <End></wizard></End> + </Tag> + + <Tag name="wizardpage"> + <Begin><wizardpage description="" onwizardfinish="" pageadvanced=""></Begin> + <End></wizardpage></End> + </Tag> + +</TagGroup> +</TagList> diff --git a/plugins/taglist/gedit-taglist-plugin-panel.c b/plugins/taglist/gedit-taglist-plugin-panel.c new file mode 100755 index 00000000..d66acc08 --- /dev/null +++ b/plugins/taglist/gedit-taglist-plugin-panel.c @@ -0,0 +1,776 @@ +/* + * gedit-taglist-plugin-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 <string.h> + +#include "gedit-taglist-plugin-panel.h" +#include "gedit-taglist-plugin-parser.h" + +#include <gedit/gedit-utils.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-plugin.h> + +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n.h> + +#define GEDIT_TAGLIST_PLUGIN_PANEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, \ + GeditTaglistPluginPanelPrivate)) + +enum +{ + COLUMN_TAG_NAME, + COLUMN_TAG_INDEX_IN_GROUP, + NUM_COLUMNS +}; + +struct _GeditTaglistPluginPanelPrivate +{ + GeditWindow *window; + + GtkWidget *tag_groups_combo; + GtkWidget *tags_list; + GtkWidget *preview; + + TagGroup *selected_tag_group; + + gchar *data_dir; +}; + +GEDIT_PLUGIN_DEFINE_TYPE (GeditTaglistPluginPanel, gedit_taglist_plugin_panel, GTK_TYPE_VBOX) + +enum +{ + PROP_0, + PROP_WINDOW, +}; + +static void +set_window (GeditTaglistPluginPanel *panel, + GeditWindow *window) +{ + g_return_if_fail (panel->priv->window == NULL); + g_return_if_fail (GEDIT_IS_WINDOW (window)); + + panel->priv->window = window; + + /* TODO */ +} + +static void +gedit_taglist_plugin_panel_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditTaglistPluginPanel *panel = GEDIT_TAGLIST_PLUGIN_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_taglist_plugin_panel_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditTaglistPluginPanel *panel = GEDIT_TAGLIST_PLUGIN_PANEL (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, + GEDIT_TAGLIST_PLUGIN_PANEL_GET_PRIVATE (panel)->window); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_taglist_plugin_panel_finalize (GObject *object) +{ + GeditTaglistPluginPanel *panel = GEDIT_TAGLIST_PLUGIN_PANEL (object); + + g_free (panel->priv->data_dir); + + G_OBJECT_CLASS (gedit_taglist_plugin_panel_parent_class)->finalize (object); +} + +static void +gedit_taglist_plugin_panel_class_init (GeditTaglistPluginPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_taglist_plugin_panel_finalize; + object_class->get_property = gedit_taglist_plugin_panel_get_property; + object_class->set_property = gedit_taglist_plugin_panel_set_property; + + g_object_class_install_property (object_class, + PROP_WINDOW, + g_param_spec_object ("window", + "Window", + "The GeditWindow this GeditTaglistPluginPanel is associated with", + GEDIT_TYPE_WINDOW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (object_class, sizeof(GeditTaglistPluginPanelPrivate)); +} + +static void +insert_tag (GeditTaglistPluginPanel *panel, + Tag *tag, + gboolean grab_focus) +{ + GeditView *view; + GtkTextBuffer *buffer; + GtkTextIter start, end; + GtkTextIter cursor; + gboolean sel = FALSE; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (panel->priv->window); + g_return_if_fail (view != NULL); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + gtk_text_buffer_begin_user_action (buffer); + + /* always insert the begin tag at the beginning of the selection + * and the end tag at the end, if there is no selection they will + * be automatically inserted at the cursor position. + */ + + if (tag->begin != NULL) + { + sel = gtk_text_buffer_get_selection_bounds (buffer, + &start, + &end); + + gtk_text_buffer_insert (buffer, + &start, + (gchar *)tag->begin, + -1); + + /* get iterators again since they have been invalidated and move + * the cursor after the selection */ + gtk_text_buffer_get_selection_bounds (buffer, + &start, + &cursor); + } + + if (tag->end != NULL) + { + sel = gtk_text_buffer_get_selection_bounds (buffer, + &start, + &end); + + gtk_text_buffer_insert (buffer, + &end, + (gchar *)tag->end, + -1); + + /* if there is no selection and we have a paired tag, move the + * cursor between the pair, otherwise move it at the end */ + if (!sel) + { + gint offset; + + offset = gtk_text_iter_get_offset (&end) - + g_utf8_strlen ((gchar *)tag->end, -1); + + gtk_text_buffer_get_iter_at_offset (buffer, + &end, + offset); + } + + cursor = end; + } + + gtk_text_buffer_place_cursor (buffer, &cursor); + + gtk_text_buffer_end_user_action (buffer); + + if (grab_focus) + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static void +tag_list_row_activated_cb (GtkTreeView *tag_list, + GtkTreePath *path, + GtkTreeViewColumn *column, + GeditTaglistPluginPanel *panel) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gint index; + + gedit_debug (DEBUG_PLUGINS); + + model = gtk_tree_view_get_model (tag_list); + + gtk_tree_model_get_iter (model, &iter, path); + g_return_if_fail (&iter != NULL); + + gtk_tree_model_get (model, &iter, COLUMN_TAG_INDEX_IN_GROUP, &index, -1); + + gedit_debug_message (DEBUG_PLUGINS, "Index: %d", index); + + insert_tag (panel, + (Tag*)g_list_nth_data (panel->priv->selected_tag_group->tags, index), + TRUE); +} + +static gboolean +tag_list_key_press_event_cb (GtkTreeView *tag_list, + GdkEventKey *event, + GeditTaglistPluginPanel *panel) +{ + gboolean grab_focus; + + grab_focus = (event->state & GDK_CONTROL_MASK) != 0; + + if (event->keyval == GDK_Return) + { + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + gint index; + + gedit_debug_message (DEBUG_PLUGINS, "RETURN Pressed"); + + model = gtk_tree_view_get_model (tag_list); + + selection = gtk_tree_view_get_selection (tag_list); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + gtk_tree_model_get (model, &iter, COLUMN_TAG_INDEX_IN_GROUP, &index, -1); + + gedit_debug_message (DEBUG_PLUGINS, "Index: %d", index); + + insert_tag (panel, + (Tag*)g_list_nth_data (panel->priv->selected_tag_group->tags, index), + grab_focus); + } + + return TRUE; + } + + return FALSE; +} + +static GtkTreeModel* +create_model (GeditTaglistPluginPanel *panel) +{ + gint i = 0; + GtkListStore *store; + GtkTreeIter iter; + GList *list; + + gedit_debug (DEBUG_PLUGINS); + + /* create list store */ + store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT); + + /* add data to the list store */ + list = panel->priv->selected_tag_group->tags; + + while (list != NULL) + { + const gchar* tag_name; + + tag_name = (gchar *)((Tag*)list->data)->name; + + gedit_debug_message (DEBUG_PLUGINS, "%d : %s", i, tag_name); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_TAG_NAME, tag_name, + COLUMN_TAG_INDEX_IN_GROUP, i, + -1); + ++i; + + list = g_list_next (list); + } + + gedit_debug_message (DEBUG_PLUGINS, "Rows: %d ", + gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL)); + + return GTK_TREE_MODEL (store); +} + +static void +populate_tags_list (GeditTaglistPluginPanel *panel) +{ + GtkTreeModel* model; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (taglist != NULL); + + model = create_model (panel); + gtk_tree_view_set_model (GTK_TREE_VIEW (panel->priv->tags_list), + model); + g_object_unref (model); +} + +static TagGroup * +find_tag_group (const gchar *name) +{ + GList *l; + + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (taglist != NULL, NULL); + + for (l = taglist->tag_groups; l != NULL; l = g_list_next (l)) + { + if (strcmp (name, (gchar *)((TagGroup*)l->data)->name) == 0) + return (TagGroup*)l->data; + } + + return NULL; +} + +static void +populate_tag_groups_combo (GeditTaglistPluginPanel *panel) +{ + GList *l; + GtkComboBox *combo; + + gedit_debug (DEBUG_PLUGINS); + + combo = GTK_COMBO_BOX (panel->priv->tag_groups_combo); + + if (taglist == NULL) + return; + + for (l = taglist->tag_groups; l != NULL; l = g_list_next (l)) + { + gtk_combo_box_append_text (combo, + (gchar *)((TagGroup*)l->data)->name); + } + + gtk_combo_box_set_active (combo, 0); + + return; +} + +static void +selected_group_changed (GtkComboBox *combo, + GeditTaglistPluginPanel *panel) +{ + gchar* group_name; + + gedit_debug (DEBUG_PLUGINS); + + group_name = gtk_combo_box_get_active_text (combo); + + if ((group_name == NULL) || (strlen (group_name) <= 0)) + { + g_free (group_name); + return; + } + + if ((panel->priv->selected_tag_group == NULL) || + (strcmp (group_name, (gchar *)panel->priv->selected_tag_group->name) != 0)) + { + panel->priv->selected_tag_group = find_tag_group (group_name); + g_return_if_fail (panel->priv->selected_tag_group != NULL); + + gedit_debug_message (DEBUG_PLUGINS, + "New selected group: %s", + panel->priv->selected_tag_group->name); + + populate_tags_list (panel); + } + + /* Clean up preview */ + gtk_label_set_text (GTK_LABEL (panel->priv->preview), + ""); + + g_free (group_name); +} + +static gchar * +create_preview_string (Tag *tag) +{ + GString *str; + + str = g_string_new ("<tt><small>"); + + if (tag->begin != NULL) + { + gchar *markup; + + markup = g_markup_escape_text ((gchar *)tag->begin, -1); + g_string_append (str, markup); + g_free (markup); + } + + if (tag->end != NULL) + { + gchar *markup; + + markup = g_markup_escape_text ((gchar *)tag->end, -1); + g_string_append (str, markup); + g_free (markup); + } + + g_string_append (str, "</small></tt>"); + + return g_string_free (str, FALSE); +} + +static void +update_preview (GeditTaglistPluginPanel *panel, + Tag *tag) +{ + gchar *str; + + str = create_preview_string (tag); + + gtk_label_set_markup (GTK_LABEL (panel->priv->preview), + str); + + g_free (str); +} + +static void +tag_list_cursor_changed_cb (GtkTreeView *tag_list, + gpointer data) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + gint index; + + GeditTaglistPluginPanel *panel = (GeditTaglistPluginPanel *)data; + + model = gtk_tree_view_get_model (tag_list); + + selection = gtk_tree_view_get_selection (tag_list); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + gtk_tree_model_get (model, &iter, COLUMN_TAG_INDEX_IN_GROUP, &index, -1); + + gedit_debug_message (DEBUG_PLUGINS, "Index: %d", index); + + update_preview (panel, + (Tag*)g_list_nth_data (panel->priv->selected_tag_group->tags, index)); + } +} + +static gboolean +tags_list_query_tooltip_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + GeditTaglistPluginPanel *panel) +{ + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreePath *path = NULL; + gint index; + Tag *tag; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); + + if (keyboard_tip) + { + gtk_tree_view_get_cursor (GTK_TREE_VIEW (widget), + &path, + NULL); + + if (path == NULL) + { + return FALSE; + } + } + else + { + gint bin_x, bin_y; + + gtk_tree_view_convert_widget_to_bin_window_coords (GTK_TREE_VIEW (widget), + x, y, + &bin_x, &bin_y); + + if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + bin_x, bin_y, + &path, + NULL, NULL, NULL)) + { + return FALSE; + } + } + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COLUMN_TAG_INDEX_IN_GROUP, &index, + -1); + + tag = g_list_nth_data (panel->priv->selected_tag_group->tags, index); + if (tag != NULL) + { + gchar *tip; + + tip = create_preview_string (tag); + gtk_tooltip_set_markup (tooltip, tip); + g_free (tip); + gtk_tree_path_free (path); + + return TRUE; + } + + gtk_tree_path_free (path); + + return FALSE; +} + +static gboolean +expose_event_cb (GtkWidget *panel, + GdkEventExpose *event, + gpointer user_data) +{ + GeditTaglistPluginPanel *ppanel = GEDIT_TAGLIST_PLUGIN_PANEL (panel); + + gedit_debug (DEBUG_PLUGINS); + + /* If needed load taglists from files at the first expose */ + if (taglist == NULL) + create_taglist (ppanel->priv->data_dir); + + /* And populate combo box */ + populate_tag_groups_combo (GEDIT_TAGLIST_PLUGIN_PANEL (panel)); + + /* We need to manage only the first expose event -> disconnect */ + g_signal_handlers_disconnect_by_func (panel, expose_event_cb, NULL); + + return FALSE; +} + +static void +set_combo_tooltip (GtkWidget *widget, + gpointer data) +{ + if (GTK_IS_BUTTON (widget)) + { + gtk_widget_set_tooltip_text (widget, + _("Select the group of tags you want to use")); + } +} + +static void +realize_tag_groups_combo (GtkWidget *combo, + gpointer data) +{ + gtk_container_forall (GTK_CONTAINER (combo), + set_combo_tooltip, + NULL); +} + +static void +add_preview_widget (GeditTaglistPluginPanel *panel) +{ + GtkWidget *expander; + GtkWidget *frame; + + expander = gtk_expander_new_with_mnemonic (_("_Preview")); + + panel->priv->preview = gtk_label_new (NULL); + gtk_widget_set_size_request (panel->priv->preview, -1, 80); + + gtk_label_set_line_wrap (GTK_LABEL (panel->priv->preview), TRUE); + gtk_label_set_use_markup (GTK_LABEL (panel->priv->preview), TRUE); + gtk_misc_set_alignment (GTK_MISC (panel->priv->preview), 0, 0); + gtk_misc_set_padding (GTK_MISC (panel->priv->preview), 6, 6); + gtk_label_set_selectable (GTK_LABEL (panel->priv->preview), TRUE); + gtk_label_set_selectable (GTK_LABEL (panel->priv->preview), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (panel->priv->preview), + PANGO_ELLIPSIZE_END); + + frame = gtk_frame_new (0); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + + gtk_container_add (GTK_CONTAINER (frame), + panel->priv->preview); + + gtk_container_add (GTK_CONTAINER (expander), + frame); + + gtk_box_pack_start (GTK_BOX (panel), expander, FALSE, FALSE, 0); + + gtk_widget_show_all (expander); +} + +static void +gedit_taglist_plugin_panel_init (GeditTaglistPluginPanel *panel) +{ + GtkWidget *sw; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GList *focus_chain = NULL; + + gedit_debug (DEBUG_PLUGINS); + + panel->priv = GEDIT_TAGLIST_PLUGIN_PANEL_GET_PRIVATE (panel); + panel->priv->data_dir = NULL; + + /* Build the window content */ + panel->priv->tag_groups_combo = gtk_combo_box_new_text (); + gtk_box_pack_start (GTK_BOX (panel), + panel->priv->tag_groups_combo, + FALSE, + TRUE, + 0); + + g_signal_connect (panel->priv->tag_groups_combo, + "realize", + G_CALLBACK (realize_tag_groups_combo), + panel); + + sw = gtk_scrolled_window_new (NULL, 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_box_pack_start (GTK_BOX (panel), sw, TRUE, TRUE, 0); + + /* Create tree view */ + panel->priv->tags_list = gtk_tree_view_new (); + + gedit_utils_set_atk_name_description (panel->priv->tag_groups_combo, + _("Available Tag Lists"), + NULL); + gedit_utils_set_atk_name_description (panel->priv->tags_list, + _("Tags"), + NULL); + gedit_utils_set_atk_relation (panel->priv->tag_groups_combo, + panel->priv->tags_list, + ATK_RELATION_CONTROLLER_FOR); + gedit_utils_set_atk_relation (panel->priv->tags_list, + panel->priv->tag_groups_combo, + ATK_RELATION_CONTROLLED_BY); + + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (panel->priv->tags_list), FALSE); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (panel->priv->tags_list), FALSE); + + g_object_set (panel->priv->tags_list, "has-tooltip", TRUE, NULL); + + /* Add the tags column */ + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Tags"), + cell, + "text", + COLUMN_TAG_NAME, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (panel->priv->tags_list), + column); + + gtk_tree_view_set_search_column (GTK_TREE_VIEW (panel->priv->tags_list), + COLUMN_TAG_NAME); + + gtk_container_add (GTK_CONTAINER (sw), panel->priv->tags_list); + + focus_chain = g_list_prepend (focus_chain, panel->priv->tags_list); + focus_chain = g_list_prepend (focus_chain, panel->priv->tag_groups_combo); + + gtk_container_set_focus_chain (GTK_CONTAINER (panel), + focus_chain); + g_list_free (focus_chain); + + add_preview_widget (panel); + + gtk_widget_show_all (GTK_WIDGET (sw)); + gtk_widget_show (GTK_WIDGET (panel->priv->tag_groups_combo)); + + g_signal_connect_after (panel->priv->tags_list, + "row_activated", + G_CALLBACK (tag_list_row_activated_cb), + panel); + g_signal_connect (panel->priv->tags_list, + "key_press_event", + G_CALLBACK (tag_list_key_press_event_cb), + panel); + g_signal_connect (panel->priv->tags_list, + "query-tooltip", + G_CALLBACK (tags_list_query_tooltip_cb), + panel); + g_signal_connect (panel->priv->tags_list, + "cursor_changed", + G_CALLBACK (tag_list_cursor_changed_cb), + panel); + g_signal_connect (panel->priv->tag_groups_combo, + "changed", + G_CALLBACK (selected_group_changed), + panel); + g_signal_connect (panel, + "expose-event", + G_CALLBACK (expose_event_cb), + NULL); +} + +GtkWidget * +gedit_taglist_plugin_panel_new (GeditWindow *window, + const gchar *data_dir) +{ + GeditTaglistPluginPanel *panel; + + g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL); + + panel = g_object_new (GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, + "window", window, + NULL); + + panel->priv->data_dir = g_strdup (data_dir); + + return GTK_WIDGET (panel); +} diff --git a/plugins/taglist/gedit-taglist-plugin-panel.h b/plugins/taglist/gedit-taglist-plugin-panel.h new file mode 100755 index 00000000..3f8e82d0 --- /dev/null +++ b/plugins/taglist/gedit-taglist-plugin-panel.h @@ -0,0 +1,89 @@ +/* + * gedit-taglist-plugin-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_TAGLIST_PLUGIN_PANEL_H__ +#define __GEDIT_TAGLIST_PLUGIN_PANEL_H__ + +#include <gtk/gtk.h> + +#include <gedit/gedit-window.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_TAGLIST_PLUGIN_PANEL (gedit_taglist_plugin_panel_get_type()) +#define GEDIT_TAGLIST_PLUGIN_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanel)) +#define GEDIT_TAGLIST_PLUGIN_PANEL_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanel const)) +#define GEDIT_TAGLIST_PLUGIN_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanelClass)) +#define GEDIT_IS_TAGLIST_PLUGIN_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL)) +#define GEDIT_IS_TAGLIST_PLUGIN_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL)) +#define GEDIT_TAGLIST_PLUGIN_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanelClass)) + +/* Private structure type */ +typedef struct _GeditTaglistPluginPanelPrivate GeditTaglistPluginPanelPrivate; + +/* + * Main object structure + */ +typedef struct _GeditTaglistPluginPanel GeditTaglistPluginPanel; + +struct _GeditTaglistPluginPanel +{ + GtkVBox vbox; + + /*< private > */ + GeditTaglistPluginPanelPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditTaglistPluginPanelClass GeditTaglistPluginPanelClass; + +struct _GeditTaglistPluginPanelClass +{ + GtkVBoxClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_taglist_plugin_panel_register_type (GTypeModule *module); + +GType gedit_taglist_plugin_panel_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_taglist_plugin_panel_new (GeditWindow *window, + const gchar *data_dir); + +G_END_DECLS + +#endif /* __GEDIT_TAGLIST_PLUGIN_PANEL_H__ */ diff --git a/plugins/taglist/gedit-taglist-plugin-parser.c b/plugins/taglist/gedit-taglist-plugin-parser.c new file mode 100755 index 00000000..e09c0e25 --- /dev/null +++ b/plugins/taglist/gedit-taglist-plugin-parser.c @@ -0,0 +1,655 @@ +/* + * gedit-taglist-plugin-parser.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$ + */ + +/* FIXME: we should rewrite the parser to avoid using DOM */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <libxml/parser.h> +#include <glib.h> +#include <glib/gi18n.h> + +#include <gedit/gedit-debug.h> + +#include "gedit-taglist-plugin-parser.h" + +/* we screwed up so we still look here for compatibility */ +#define USER_GEDIT_TAGLIST_PLUGIN_LOCATION_LEGACY ".gedit-2/plugins/taglist/" +#define USER_GEDIT_TAGLIST_PLUGIN_LOCATION "gedit/taglist/" + +TagList *taglist = NULL; +static gint taglist_ref_count = 0; + +static gboolean parse_tag (Tag *tag, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur); +static gboolean parse_tag_group (TagGroup *tg, const gchar *fn, + xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur, + gboolean sort); +static TagGroup* get_tag_group (const gchar* filename, xmlDocPtr doc, + xmlNsPtr ns, xmlNodePtr cur); +static TagList* lookup_best_lang (TagList *taglist, const gchar *filename, + xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur); +static TagList *parse_taglist_file (const gchar* filename); +static TagList *parse_taglist_dir (const gchar *dir); + +static void free_tag (Tag *tag); +static void free_tag_group (TagGroup *tag_group); + +static gboolean +parse_tag (Tag *tag, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur) +{ + /* + gedit_debug_message (DEBUG_PLUGINS, " Tag name: %s", tag->name); + */ + /* We don't care what the top level element name is */ + cur = cur->xmlChildrenNode; + + while (cur != NULL) + { + if ((!xmlStrcmp (cur->name, (const xmlChar *)"Begin")) && + (cur->ns == ns)) + { + tag->begin = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1); + /* + gedit_debug_message (DEBUG_PLUGINS, " - Begin: %s", tag->begin); + */ + } + + if ((!xmlStrcmp (cur->name, (const xmlChar *)"End")) && + (cur->ns == ns)) + { + tag->end = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1); + /* + gedit_debug_message (DEBUG_PLUGINS, " - End: %s", tag->end); + */ + } + + cur = cur->next; + } + + if ((tag->begin == NULL) && (tag->end == NULL)) + return FALSE; + + return TRUE; +} + +static gint +tags_cmp (gconstpointer a, gconstpointer b) +{ + gchar *tag_a = (gchar*)((Tag *)a)->name; + gchar *tag_b = (gchar*)((Tag *)b)->name; + + return g_utf8_collate (tag_a, tag_b); +} + +static gboolean +parse_tag_group (TagGroup *tg, const gchar* fn, xmlDocPtr doc, + xmlNsPtr ns, xmlNodePtr cur, gboolean sort) +{ + gedit_debug_message (DEBUG_PLUGINS, "Parse TagGroup: %s", tg->name); + + /* We don't care what the top level element name is */ + cur = cur->xmlChildrenNode; + + while (cur != NULL) + { + if ((xmlStrcmp (cur->name, (const xmlChar *) "Tag")) || (cur->ns != ns)) + { + g_warning ("The tag list file '%s' is of the wrong type, " + "was '%s', 'Tag' expected.", fn, cur->name); + + return FALSE; + } + else + { + Tag *tag; + + tag = g_new0 (Tag, 1); + + /* Get Tag name */ + tag->name = xmlGetProp (cur, (const xmlChar *) "name"); + + if (tag->name == NULL) + { + /* Error: No name */ + g_warning ("The tag list file '%s' is of the wrong type, " + "Tag without name.", fn); + + g_free (tag); + + return FALSE; + } + else + { + /* Parse Tag */ + if (parse_tag (tag, doc, ns, cur)) + { + /* Prepend Tag to TagGroup */ + tg->tags = g_list_prepend (tg->tags, tag); + } + else + { + /* Error parsing Tag */ + g_warning ("The tag list file '%s' is of the wrong type, " + "error parsing Tag '%s' in TagGroup '%s'.", + fn, tag->name, tg->name); + + free_tag (tag); + + return FALSE; + } + } + } + + cur = cur->next; + } + + if (sort) + tg->tags = g_list_sort (tg->tags, tags_cmp); + else + tg->tags = g_list_reverse (tg->tags); + + return TRUE; +} + +static TagGroup* +get_tag_group (const gchar* filename, xmlDocPtr doc, + xmlNsPtr ns, xmlNodePtr cur) +{ + TagGroup *tag_group; + xmlChar *sort_str; + gboolean sort = FALSE; + + tag_group = g_new0 (TagGroup, 1); + + /* Get TagGroup name */ + tag_group->name = xmlGetProp (cur, (const xmlChar *) "name"); + + sort_str = xmlGetProp (cur, (const xmlChar *) "sort"); + + if ((sort_str != NULL) && + ((xmlStrcasecmp (sort_str, (const xmlChar *) "yes") == 0) || + (xmlStrcasecmp (sort_str, (const xmlChar *) "true") == 0) || + (xmlStrcasecmp (sort_str, (const xmlChar *) "1") == 0))) + { + sort = TRUE; + } + + xmlFree(sort_str); + + if (tag_group->name == NULL) + { + /* Error: No name */ + g_warning ("The tag list file '%s' is of the wrong type, " + "TagGroup without name.", filename); + + g_free (tag_group); + } + else + { + /* Name found */ + gboolean exists = FALSE; + GList *t = taglist->tag_groups; + + /* Check if the tag group already exists */ + while (t && !exists) + { + gchar *tgn = (gchar*)((TagGroup*)(t->data))->name; + + if (strcmp (tgn, (gchar*)tag_group->name) == 0) + { + gedit_debug_message (DEBUG_PLUGINS, + "Tag group '%s' already exists.", tgn); + + exists = TRUE; + + free_tag_group (tag_group); + } + + t = g_list_next (t); + } + + if (!exists) + { + /* Parse tag group */ + if (parse_tag_group (tag_group, filename, doc, ns, cur, sort)) + { + return tag_group; + } + else + { + /* Error parsing TagGroup */ + g_warning ("The tag list file '%s' is of the wrong type, " + "error parsing TagGroup '%s'.", + filename, tag_group->name); + + free_tag_group (tag_group); + } + } + } + return NULL; +} + +static gint +groups_cmp (gconstpointer a, gconstpointer b) +{ + gchar *g_a = (gchar *)((TagGroup *)a)->name; + gchar *g_b = (gchar *)((TagGroup *)b)->name; + + return g_utf8_collate (g_a, g_b); +} + +/* + * tags file is localized by intltool-merge below. + * + * <gedit:TagGroup name="XSLT - Elements"> + * </gedit:TagGroup> + * <gedit:TagGroup xml:lang="am" name="LOCALIZED TEXT"> + * </gedit:TagGroup> + * <gedit:TagGroup xml:lang="ar" name="LOCALIZED TEXT"> + * </gedit:TagGroup> + * ..... + * <gedit:TagGroup name="XSLT - Functions"> + * </gedit:TagGroup> + * ..... + * Therefore need to pick up the best lang on the current locale. + */ +static TagList* +lookup_best_lang (TagList *taglist, const gchar *filename, + xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur) +{ + + TagGroup *best_tag_group = NULL; + TagGroup *tag_group; + gint best_lanking = -1; + + /* + * Walk the tree. + * + * First level we expect a list TagGroup + */ + cur = cur->xmlChildrenNode; + + while (cur != NULL) + { + if ((xmlStrcmp (cur->name, (const xmlChar *) "TagGroup")) || (cur->ns != ns)) + { + g_warning ("The tag list file '%s' is of the wrong type, " + "was '%s', 'TagGroup' expected.", filename, cur->name); + xmlFreeDoc (doc); + + return taglist; + } + else + { + const char * const *langs_pointer; + gchar *lang; + gint cur_lanking; + gint i; + + langs_pointer = g_get_language_names (); + + lang = (gchar*) xmlGetProp (cur, (const xmlChar*) "lang"); + cur_lanking = 1; + + /* + * When found a new TagGroup, prepend the best + * tag_group to taglist. In the current intltool-merge, + * the first section is the default lang NULL. + */ + if (lang == NULL) { + if (best_tag_group != NULL) { + taglist->tag_groups = + g_list_prepend (taglist->tag_groups, best_tag_group); + } + + best_tag_group = NULL; + best_lanking = -1; + } + + /* + * If already find the best TagGroup on the current + * locale, ignore the logic. + */ + if (best_lanking != -1 && best_lanking <= cur_lanking) { + cur = cur->next; + continue; + } + + /* try to find the best lang */ + for (i = 0; langs_pointer[i] != NULL; i++) + { + const gchar *best_lang = langs_pointer[i]; + + /* + * if launch on C, POSIX locale or does + * not find the best lang on the current locale, + * this is called. + * g_get_language_names returns lang + * lists with C locale. + */ + if (lang == NULL && + (!g_ascii_strcasecmp (best_lang, "C") || + !g_ascii_strcasecmp (best_lang, "POSIX"))) + { + tag_group = get_tag_group (filename, doc, ns, cur); + if (tag_group != NULL) + { + if (best_tag_group !=NULL) + free_tag_group (best_tag_group); + best_lanking = cur_lanking; + best_tag_group = tag_group; + } + } + + /* if it is possible the best lang is not C */ + else if (lang == NULL) + { + cur_lanking++; + continue; + } + + /* if the best lang is found */ + else if (!g_ascii_strcasecmp (best_lang, lang)) + { + tag_group = get_tag_group (filename, doc, ns, cur); + if (tag_group != NULL) + { + if (best_tag_group !=NULL) + free_tag_group (best_tag_group); + best_lanking = cur_lanking; + best_tag_group = tag_group; + } + } + + cur_lanking++; + } + + if (lang) g_free (lang); + } /* End of else */ + + cur = cur->next; + } /* End of while (cur != NULL) */ + + /* Prepend TagGroup to TagList */ + if (best_tag_group != NULL) { + taglist->tag_groups = + g_list_prepend (taglist->tag_groups, best_tag_group); + } + + taglist->tag_groups = g_list_sort (taglist->tag_groups, groups_cmp); + + return taglist; +} + +static TagList * +parse_taglist_file (const gchar* filename) +{ + xmlDocPtr doc; + + xmlNsPtr ns; + xmlNodePtr cur; + + gedit_debug_message (DEBUG_PLUGINS, "Parse file: %s", filename); + + xmlKeepBlanksDefault (0); + + /* + * build an XML tree from a the file; + */ + doc = xmlParseFile (filename); + if (doc == NULL) + { + g_warning ("The tag list file '%s' is empty.", filename); + + return taglist; + } + + /* + * Check the document is of the right kind + */ + + cur = xmlDocGetRootElement (doc); + + if (cur == NULL) + { + g_warning ("The tag list file '%s' is empty.", filename); + xmlFreeDoc(doc); + return taglist; + } + + ns = xmlSearchNsByHref (doc, cur, + (const xmlChar *) "http://gedit.sourceforge.net/some-location"); + + if (ns == NULL) + { + g_warning ("The tag list file '%s' is of the wrong type, " + "gedit namespace not found.", filename); + xmlFreeDoc (doc); + + return taglist; + } + + if (xmlStrcmp(cur->name, (const xmlChar *) "TagList")) + { + g_warning ("The tag list file '%s' is of the wrong type, " + "root node != TagList.", filename); + xmlFreeDoc (doc); + + return taglist; + } + + /* + * If needed, allocate taglist + */ + + if (taglist == NULL) + taglist = g_new0 (TagList, 1); + + taglist = lookup_best_lang (taglist, filename, doc, ns, cur); + + xmlFreeDoc (doc); + + gedit_debug_message (DEBUG_PLUGINS, "END"); + + return taglist; +} + +static void +free_tag (Tag *tag) +{ + /* + gedit_debug_message (DEBUG_PLUGINS, "Tag: %s", tag->name); + */ + g_return_if_fail (tag != NULL); + + free (tag->name); + + if (tag->begin != NULL) + free (tag->begin); + + if (tag->end != NULL) + free (tag->end); + + g_free (tag); +} + +static void +free_tag_group (TagGroup *tag_group) +{ + GList *l; + + gedit_debug_message (DEBUG_PLUGINS, "Tag group: %s", tag_group->name); + + g_return_if_fail (tag_group != NULL); + + free (tag_group->name); + + for (l = tag_group->tags; l != NULL; l = g_list_next (l)) + { + free_tag ((Tag *) l->data); + } + + g_list_free (tag_group->tags); + g_free (tag_group); + + gedit_debug_message (DEBUG_PLUGINS, "END"); +} + +void +free_taglist (void) +{ + GList *l; + + gedit_debug_message (DEBUG_PLUGINS, "ref_count: %d", taglist_ref_count); + + if (taglist == NULL) + return; + + g_return_if_fail (taglist_ref_count > 0); + + --taglist_ref_count; + if (taglist_ref_count > 0) + return; + + for (l = taglist->tag_groups; l != NULL; l = g_list_next (l)) + { + free_tag_group ((TagGroup *) l->data); + } + + g_list_free (taglist->tag_groups); + g_free (taglist); + taglist = NULL; + + gedit_debug_message (DEBUG_PLUGINS, "Really freed"); +} + +static TagList * +parse_taglist_dir (const gchar *dir) +{ + GError *error = NULL; + GDir *d; + const gchar *dirent; + + gedit_debug_message (DEBUG_PLUGINS, "DIR: %s", dir); + + d = g_dir_open (dir, 0, &error); + if (!d) + { + gedit_debug_message (DEBUG_PLUGINS, "%s", error->message); + g_error_free (error); + return taglist; + } + + while ((dirent = g_dir_read_name (d))) + { + if (g_str_has_suffix (dirent, ".tags") || + g_str_has_suffix (dirent, ".tags.gz")) + { + gchar *tags_file = g_build_filename (dir, dirent, NULL); + parse_taglist_file (tags_file); + g_free (tags_file); + } + } + + g_dir_close (d); + + return taglist; +} + +TagList* create_taglist (const gchar *data_dir) +{ + gchar *pdir; + + gedit_debug_message (DEBUG_PLUGINS, "ref_count: %d", taglist_ref_count); + + if (taglist_ref_count > 0) + { + ++taglist_ref_count; + + return taglist; + } + +#ifndef G_OS_WIN32 + const gchar *home; + const gchar *envvar; + + /* load user's taglists */ + + /* legacy dir */ + home = g_get_home_dir (); + if (home != NULL) + { + pdir = g_build_filename (home, + USER_GEDIT_TAGLIST_PLUGIN_LOCATION_LEGACY, + NULL); + parse_taglist_dir (pdir); + g_free (pdir); + } + + /* Support old libmate env var */ + envvar = g_getenv ("MATE22_USER_DIR"); + if (envvar != NULL) + { + pdir = g_build_filename (envvar, + USER_GEDIT_TAGLIST_PLUGIN_LOCATION, + NULL); + parse_taglist_dir (pdir); + g_free (pdir); + } + else if (home != NULL) + { + pdir = g_build_filename (home, + ".mate2", + USER_GEDIT_TAGLIST_PLUGIN_LOCATION, + NULL); + parse_taglist_dir (pdir); + g_free (pdir); + } + +#else + pdir = g_build_filename (g_get_user_config_dir (), + "gedit", + "taglist", + NULL); + parse_taglist_dir (pdir); + g_free (pdir); +#endif + + /* load system's taglists */ + parse_taglist_dir (data_dir); + + ++taglist_ref_count; + g_return_val_if_fail (taglist_ref_count == 1, taglist); + + return taglist; +} diff --git a/plugins/taglist/gedit-taglist-plugin-parser.h b/plugins/taglist/gedit-taglist-plugin-parser.h new file mode 100755 index 00000000..d008836e --- /dev/null +++ b/plugins/taglist/gedit-taglist-plugin-parser.h @@ -0,0 +1,68 @@ +/* + * gedit-taglist-plugin-parser.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_TAGLIST_PLUGIN_PARSER_H__ +#define __GEDIT_TAGLIST_PLUGIN_PARSER_H__ + +#include <libxml/tree.h> +#include <glib.h> + +typedef struct _TagList TagList; +typedef struct _TagGroup TagGroup; +typedef struct _Tag Tag; + +struct _TagList +{ + GList *tag_groups; +}; + +struct _TagGroup +{ + xmlChar *name; + + GList *tags; +}; + +struct _Tag +{ + xmlChar *name; + xmlChar *begin; + xmlChar *end; +}; + +/* Note that the taglist is ref counted */ +extern TagList *taglist; + +TagList* create_taglist (const gchar *data_dir); + +void free_taglist (void); + +#endif /* __GEDIT_TAGLIST_PLUGIN_PARSER_H__ */ + diff --git a/plugins/taglist/gedit-taglist-plugin.c b/plugins/taglist/gedit-taglist-plugin.c new file mode 100755 index 00000000..081fefcb --- /dev/null +++ b/plugins/taglist/gedit-taglist-plugin.c @@ -0,0 +1,160 @@ +/* + * gedit-taglist-plugin.h + * + * 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, 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-taglist-plugin.h" +#include "gedit-taglist-plugin-panel.h" +#include "gedit-taglist-plugin-parser.h" + +#include <glib/gi18n-lib.h> +#include <gmodule.h> + +#include <gedit/gedit-plugin.h> +#include <gedit/gedit-debug.h> + +#define WINDOW_DATA_KEY "GeditTaglistPluginWindowData" + +#define GEDIT_TAGLIST_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPluginPrivate)) + +struct _GeditTaglistPluginPrivate +{ + gpointer dummy; +}; + +GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE (GeditTaglistPlugin, gedit_taglist_plugin, + gedit_taglist_plugin_panel_register_type (module); +) + +static void +gedit_taglist_plugin_init (GeditTaglistPlugin *plugin) +{ + plugin->priv = GEDIT_TAGLIST_PLUGIN_GET_PRIVATE (plugin); + + gedit_debug_message (DEBUG_PLUGINS, "GeditTaglistPlugin initializing"); +} + +static void +gedit_taglist_plugin_finalize (GObject *object) +{ +/* + GeditTaglistPlugin *plugin = GEDIT_TAGLIST_PLUGIN (object); +*/ + gedit_debug_message (DEBUG_PLUGINS, "GeditTaglistPlugin finalizing"); + + free_taglist (); + + G_OBJECT_CLASS (gedit_taglist_plugin_parent_class)->finalize (object); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GeditPanel *side_panel; + GtkWidget *taglist_panel; + gchar *data_dir; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY) == NULL); + + side_panel = gedit_window_get_side_panel (window); + + data_dir = gedit_plugin_get_data_dir (plugin); + taglist_panel = gedit_taglist_plugin_panel_new (window, data_dir); + g_free (data_dir); + + gedit_panel_add_item_with_stock_icon (side_panel, + taglist_panel, + _("Tags"), + GTK_STOCK_ADD); + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + taglist_panel); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GeditPanel *side_panel; + gpointer data; + + gedit_debug (DEBUG_PLUGINS); + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + side_panel = gedit_window_get_side_panel (window); + + gedit_panel_remove_item (side_panel, + GTK_WIDGET (data)); + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + gpointer data; + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + view = gedit_window_get_active_view (window); + + gtk_widget_set_sensitive (GTK_WIDGET (data), + (view != NULL) && + gtk_text_view_get_editable (GTK_TEXT_VIEW (view))); +} + +static void +gedit_taglist_plugin_class_init (GeditTaglistPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_taglist_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; + + g_type_class_add_private (object_class, sizeof (GeditTaglistPluginPrivate)); +} diff --git a/plugins/taglist/gedit-taglist-plugin.h b/plugins/taglist/gedit-taglist-plugin.h new file mode 100755 index 00000000..d0444e76 --- /dev/null +++ b/plugins/taglist/gedit-taglist-plugin.h @@ -0,0 +1,85 @@ +/* + * gedit-taglist-plugin.h + * + * 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, 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_TAGLIST_PLUGIN_H__ +#define __GEDIT_TAGLIST_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_TAGLIST_PLUGIN (gedit_taglist_plugin_get_type ()) +#define GEDIT_TAGLIST_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPlugin)) +#define GEDIT_TAGLIST_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPluginClass)) +#define GEDIT_IS_TAGLIST_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_TAGLIST_PLUGIN)) +#define GEDIT_IS_TAGLIST_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_TAGLIST_PLUGIN)) +#define GEDIT_TAGLIST_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPluginClass)) + +/* Private structure type */ +typedef struct _GeditTaglistPluginPrivate GeditTaglistPluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditTaglistPlugin GeditTaglistPlugin; + +struct _GeditTaglistPlugin +{ + GeditPlugin parent_instance; + + /*< private >*/ + GeditTaglistPluginPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditTaglistPluginClass GeditTaglistPluginClass; + +struct _GeditTaglistPluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_taglist_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_TAGLIST_PLUGIN_H__ */ diff --git a/plugins/taglist/taglist.gedit-plugin.desktop.in b/plugins/taglist/taglist.gedit-plugin.desktop.in new file mode 100755 index 00000000..09d0f2d0 --- /dev/null +++ b/plugins/taglist/taglist.gedit-plugin.desktop.in @@ -0,0 +1,8 @@ +[Gedit Plugin] +Module=taglist +IAge=2 +_Name=Tag list +_Description=Provides a method to easily insert commonly used tags/strings into a document without having to type them. +Authors=Paolo Maggi <[email protected]> +Copyright=Copyright © 2002-2005 Paolo Maggi +Website=http://www.gedit.org diff --git a/plugins/time/Makefile.am b/plugins/time/Makefile.am new file mode 100755 index 00000000..c532f7d4 --- /dev/null +++ b/plugins/time/Makefile.am @@ -0,0 +1,36 @@ +# time plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libtime.la + +libtime_la_SOURCES = \ + gedit-time-plugin.h \ + gedit-time-plugin.c + +libtime_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libtime_la_LIBADD = $(GEDIT_LIBS) + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/time +ui_DATA = \ + gedit-time-dialog.ui \ + gedit-time-setup-dialog.ui + +plugin_in_files = time.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(ui_DATA) $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/time/gedit-time-dialog.ui b/plugins/time/gedit-time-dialog.ui new file mode 100755 index 00000000..398e9b98 --- /dev/null +++ b/plugins/time/gedit-time-dialog.ui @@ -0,0 +1,297 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <requires lib="gtk+" version="2.16"/> + <object class="GtkDialog" id="choose_format_dialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Insert Date and Time</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">True</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox2"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area2"> + <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="cancelbutton1"> + <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="okbutton1"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xscale">0</property> + <property name="yscale">0</property> + <child> + <object class="GtkHBox" id="hbox6"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">2</property> + <child> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="stock">mate-stock-timer</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Insert</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.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </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="GtkVBox" id="vbox7"> + <property name="border_width">5</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkVBox" id="vbox8"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkRadioButton" id="use_sel_format_radiobutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Use the _selected format</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</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">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox4"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="label" translatable="yes"> </property> + <property name="use_underline">False</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.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</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="scrolledwindow1"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + <child> + <object class="GtkTreeView" id="choice_list"> + <property name="visible">True</property> + <property name="headers_visible">False</property> + <property name="rules_hint">False</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> + </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> + <child> + <object class="GtkVBox" id="vbox9"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">2</property> + <child> + <object class="GtkHBox" id="hbox5"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkRadioButton" id="use_custom_radiobutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Use custom format</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + <property name="group">use_sel_format_radiobutton</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="custom_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes">%d/%m/%Y %H:%M:%S</property> + <property name="has_frame">True</property> + <property name="activates_default">False</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="padding">2</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="custom_format_example"> + <property name="visible">True</property> + <property name="label" translatable="yes">01/11/2009 17:52:00</property> + <property name="use_underline">False</property> + <property name="justify">GTK_JUSTIFY_RIGHT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <attributes> + <attribute name="scale" value="0.8"/><!-- PANGO_SCALE_SMALL --> + </attributes> + </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">False</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-11">helpbutton1</action-widget> + <action-widget response="-6">cancelbutton1</action-widget> + <action-widget response="-5">okbutton1</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/time/gedit-time-plugin.c b/plugins/time/gedit-time-plugin.c new file mode 100755 index 00000000..42bf6cb9 --- /dev/null +++ b/plugins/time/gedit-time-plugin.c @@ -0,0 +1,1272 @@ +/* + * gedit-time-plugin.c + * + * 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, 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$ + */ + +/* + * 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 <time.h> + +#include <mateconf/mateconf-client.h> + +#include "gedit-time-plugin.h" +#include <gedit/gedit-help.h> + +#include <glib/gi18n-lib.h> +#include <glib.h> +#include <gmodule.h> + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> + +#define GEDIT_TIME_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_TIME_PLUGIN, \ + GeditTimePluginPrivate)) + +#define WINDOW_DATA_KEY "GeditTimePluginWindowData" +#define MENU_PATH "/MenuBar/EditMenu/EditOps_4" + +/* mateconf keys */ +#define TIME_BASE_KEY "/apps/gedit-2/plugins/time" +#define PROMPT_TYPE_KEY TIME_BASE_KEY "/prompt_type" +#define SELECTED_FORMAT_KEY TIME_BASE_KEY "/selected_format" +#define CUSTOM_FORMAT_KEY TIME_BASE_KEY "/custom_format" + +#define DEFAULT_CUSTOM_FORMAT "%d/%m/%Y %H:%M:%S" + +static const gchar *formats[] = +{ + "%c", + "%x", + "%X", + "%x %X", + "%Y-%m-%d %H:%M:%S", + "%a %b %d %H:%M:%S %Z %Y", + "%a %b %d %H:%M:%S %Y", + "%a %d %b %Y %H:%M:%S %Z", + "%a %d %b %Y %H:%M:%S", + "%d/%m/%Y", + "%d/%m/%y", +#ifndef G_OS_WIN32 + "%D", /* This one is not supported on win32 */ +#endif + "%A %d %B %Y", + "%A %B %d %Y", + "%Y-%m-%d", + "%d %B %Y", + "%B %d, %Y", + "%A %b %d", + "%H:%M:%S", + "%H:%M", + "%I:%M:%S %p", + "%I:%M %p", + "%H.%M.%S", + "%H.%M", + "%I.%M.%S %p", + "%I.%M %p", + "%d/%m/%Y %H:%M:%S", + "%d/%m/%y %H:%M:%S", +#if __GLIBC__ >= 2 + "%a, %d %b %Y %H:%M:%S %z", +#endif + NULL +}; + +enum +{ + COLUMN_FORMATS = 0, + COLUMN_INDEX, + NUM_COLUMNS +}; + +typedef struct _TimeConfigureDialog TimeConfigureDialog; + +struct _TimeConfigureDialog +{ + GtkWidget *dialog; + + GtkWidget *list; + + /* Radio buttons to indicate what should be done */ + GtkWidget *prompt; + GtkWidget *use_list; + GtkWidget *custom; + + GtkWidget *custom_entry; + GtkWidget *custom_format_example; + + /* Info needed for the response handler */ + GeditTimePlugin *plugin; +}; + +typedef struct _ChooseFormatDialog ChooseFormatDialog; + +struct _ChooseFormatDialog +{ + GtkWidget *dialog; + + GtkWidget *list; + + /* Radio buttons to indicate what should be done */ + GtkWidget *use_list; + GtkWidget *custom; + + GtkWidget *custom_entry; + GtkWidget *custom_format_example; + + /* Info needed for the response handler */ + GtkTextBuffer *buffer; + GeditTimePlugin *plugin; +}; + +typedef enum +{ + PROMPT_SELECTED_FORMAT = 0, /* Popup dialog with list preselected */ + PROMPT_CUSTOM_FORMAT, /* Popup dialog with entry preselected */ + USE_SELECTED_FORMAT, /* Use selected format directly */ + USE_CUSTOM_FORMAT /* Use custom format directly */ +} GeditTimePluginPromptType; + +struct _GeditTimePluginPrivate +{ + MateConfClient *mateconf_client; +}; + +GEDIT_PLUGIN_REGISTER_TYPE(GeditTimePlugin, gedit_time_plugin) + +typedef struct +{ + GtkActionGroup *action_group; + guint ui_id; +} WindowData; + +typedef struct +{ + GeditWindow *window; + GeditTimePlugin *plugin; +} ActionData; + +static void time_cb (GtkAction *action, ActionData *data); + +static const GtkActionEntry action_entries[] = +{ + { + "InsertDateAndTime", + NULL, + N_("In_sert Date and Time..."), + NULL, + N_("Insert current date and time at the cursor position"), + G_CALLBACK (time_cb) + }, +}; + +static void +gedit_time_plugin_init (GeditTimePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin initializing"); + + plugin->priv = GEDIT_TIME_PLUGIN_GET_PRIVATE (plugin); + + plugin->priv->mateconf_client = mateconf_client_get_default (); + + mateconf_client_add_dir (plugin->priv->mateconf_client, + TIME_BASE_KEY, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); +} + +static void +gedit_time_plugin_finalize (GObject *object) +{ + GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin finalizing"); + + mateconf_client_suggest_sync (plugin->priv->mateconf_client, NULL); + + g_object_unref (G_OBJECT (plugin->priv->mateconf_client)); + + G_OBJECT_CLASS (gedit_time_plugin_parent_class)->finalize (object); +} + +static void +free_window_data (WindowData *data) +{ + g_return_if_fail (data != NULL); + + g_object_unref (data->action_group); + g_free (data); +} + +static void +update_ui_real (GeditWindow *window, + WindowData *data) +{ + GeditView *view; + GtkAction *action; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (window); + + gedit_debug_message (DEBUG_PLUGINS, "View: %p", view); + + action = gtk_action_group_get_action (data->action_group, + "InsertDateAndTime"); + gtk_action_set_sensitive (action, + (view != NULL) && + gtk_text_view_get_editable (GTK_TEXT_VIEW (view))); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + ActionData *action_data; + + gedit_debug (DEBUG_PLUGINS); + + data = g_new (WindowData, 1); + action_data = g_new (ActionData, 1); + + action_data->plugin = GEDIT_TIME_PLUGIN (plugin); + action_data->window = window; + + manager = gedit_window_get_ui_manager (window); + + data->action_group = gtk_action_group_new ("GeditTimePluginActions"); + gtk_action_group_set_translation_domain (data->action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions_full (data->action_group, + action_entries, + G_N_ELEMENTS (action_entries), + action_data, + (GDestroyNotify) g_free); + + gtk_ui_manager_insert_action_group (manager, data->action_group, -1); + + data->ui_id = gtk_ui_manager_new_merge_id (manager); + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + (GDestroyNotify) free_window_data); + + gtk_ui_manager_add_ui (manager, + data->ui_id, + MENU_PATH, + "InsertDateAndTime", + "InsertDateAndTime", + GTK_UI_MANAGER_MENUITEM, + FALSE); + + update_ui_real (window, data); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + manager = gedit_window_get_ui_manager (window); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + gtk_ui_manager_remove_ui (manager, data->ui_id); + gtk_ui_manager_remove_action_group (manager, data->action_group); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + update_ui_real (window, data); +} + +/* whether we should prompt the user or use the specified format */ +static GeditTimePluginPromptType +get_prompt_type (GeditTimePlugin *plugin) +{ + gchar *prompt_type; + GeditTimePluginPromptType res; + + prompt_type = mateconf_client_get_string (plugin->priv->mateconf_client, + PROMPT_TYPE_KEY, + NULL); + + if (prompt_type == NULL) + return PROMPT_SELECTED_FORMAT; + + if (strcmp (prompt_type, "USE_SELECTED_FORMAT") == 0) + res = USE_SELECTED_FORMAT; + else if (strcmp (prompt_type, "USE_CUSTOM_FORMAT") == 0) + res = USE_CUSTOM_FORMAT; + else if (strcmp (prompt_type, "PROMPT_CUSTOM_FORMAT") == 0) + res = PROMPT_CUSTOM_FORMAT; + else + res = PROMPT_SELECTED_FORMAT; + + g_free (prompt_type); + + return res; +} + +static void +set_prompt_type (GeditTimePlugin *plugin, + GeditTimePluginPromptType prompt_type) +{ + const gchar * str; + + if (!mateconf_client_key_is_writable (plugin->priv->mateconf_client, + PROMPT_TYPE_KEY, + NULL)) + { + return; + } + + switch (prompt_type) + { + case USE_SELECTED_FORMAT: + str = "USE_SELECTED_FORMAT"; + break; + case USE_CUSTOM_FORMAT: + str = "USE_CUSTOM_FORMAT"; + break; + case PROMPT_CUSTOM_FORMAT: + str = "PROMPT_CUSTOM_FORMAT"; + break; + default: + str = "PROMPT_SELECTED_FORMAT"; + } + + mateconf_client_set_string (plugin->priv->mateconf_client, + PROMPT_TYPE_KEY, + str, + NULL); +} + +/* The selected format in the list */ +static gchar * +get_selected_format (GeditTimePlugin *plugin) +{ + gchar *sel_format; + + sel_format = mateconf_client_get_string (plugin->priv->mateconf_client, + SELECTED_FORMAT_KEY, + NULL); + + return sel_format ? sel_format : g_strdup (formats [0]); +} + +static void +set_selected_format (GeditTimePlugin *plugin, + const gchar *format) +{ + g_return_if_fail (format != NULL); + + if (!mateconf_client_key_is_writable (plugin->priv->mateconf_client, + SELECTED_FORMAT_KEY, + NULL)) + { + return; + } + + mateconf_client_set_string (plugin->priv->mateconf_client, + SELECTED_FORMAT_KEY, + format, + NULL); +} + +/* the custom format in the entry */ +static gchar * +get_custom_format (GeditTimePlugin *plugin) +{ + gchar *format; + + format = mateconf_client_get_string (plugin->priv->mateconf_client, + CUSTOM_FORMAT_KEY, + NULL); + + return format ? format : g_strdup (DEFAULT_CUSTOM_FORMAT); +} + +static void +set_custom_format (GeditTimePlugin *plugin, + const gchar *format) +{ + g_return_if_fail (format != NULL); + + if (!mateconf_client_key_is_writable (plugin->priv->mateconf_client, + CUSTOM_FORMAT_KEY, + NULL)) + return; + + mateconf_client_set_string (plugin->priv->mateconf_client, + CUSTOM_FORMAT_KEY, + format, + NULL); +} + +static gchar * +get_time (const gchar* format) +{ + gchar *out = NULL; + gchar *out_utf8 = NULL; + time_t clock; + struct tm *now; + size_t out_length = 0; + gchar *locale_format; + + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (format != NULL, NULL); + + if (strlen (format) == 0) + return g_strdup (" "); + + locale_format = g_locale_from_utf8 (format, -1, NULL, NULL, NULL); + if (locale_format == NULL) + return g_strdup (" "); + + clock = time (NULL); + now = localtime (&clock); + + do + { + out_length += 255; + out = g_realloc (out, out_length); + } + while (strftime (out, out_length, locale_format, now) == 0); + + g_free (locale_format); + + if (g_utf8_validate (out, -1, NULL)) + { + out_utf8 = out; + } + else + { + out_utf8 = g_locale_to_utf8 (out, -1, NULL, NULL, NULL); + g_free (out); + + if (out_utf8 == NULL) + out_utf8 = g_strdup (" "); + } + + return out_utf8; +} + +static void +dialog_destroyed (GtkObject *obj, gpointer dialog_pointer) +{ + gedit_debug (DEBUG_PLUGINS); + + g_free (dialog_pointer); + + gedit_debug_message (DEBUG_PLUGINS, "END"); +} + +static GtkTreeModel * +create_model (GtkWidget *listview, + const gchar *sel_format, + GeditTimePlugin *plugin) +{ + gint i = 0; + GtkListStore *store; + GtkTreeSelection *selection; + GtkTreeIter iter; + + gedit_debug (DEBUG_PLUGINS); + + /* create list store */ + store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT); + + /* Set tree view model*/ + gtk_tree_view_set_model (GTK_TREE_VIEW (listview), + GTK_TREE_MODEL (store)); + g_object_unref (G_OBJECT (store)); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview)); + g_return_val_if_fail (selection != NULL, GTK_TREE_MODEL (store)); + + /* there should always be one line selected */ + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + + /* add data to the list store */ + while (formats[i] != NULL) + { + gchar *str; + + str = get_time (formats[i]); + + gedit_debug_message (DEBUG_PLUGINS, "%d : %s", i, str); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_FORMATS, str, + COLUMN_INDEX, i, + -1); + g_free (str); + + if (sel_format && strcmp (formats[i], sel_format) == 0) + gtk_tree_selection_select_iter (selection, &iter); + + ++i; + } + + /* fall back to select the first iter */ + if (!gtk_tree_selection_get_selected (selection, NULL, NULL)) + { + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + gtk_tree_selection_select_iter (selection, &iter); + } + + return GTK_TREE_MODEL (store); +} + +static void +scroll_to_selected (GtkTreeView *tree_view) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + gedit_debug (DEBUG_PLUGINS); + + model = gtk_tree_view_get_model (tree_view); + g_return_if_fail (model != NULL); + + /* Scroll to selected */ + selection = gtk_tree_view_get_selection (tree_view); + g_return_if_fail (selection != NULL); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + GtkTreePath* path; + + path = gtk_tree_model_get_path (model, &iter); + g_return_if_fail (path != NULL); + + gtk_tree_view_scroll_to_cell (tree_view, + path, NULL, TRUE, 1.0, 0.0); + gtk_tree_path_free (path); + } +} + +static void +create_formats_list (GtkWidget *listview, + const gchar *sel_format, + GeditTimePlugin *plugin) +{ + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (listview != NULL); + g_return_if_fail (sel_format != NULL); + + /* the Available formats column */ + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ( + _("Available formats"), + cell, + "text", COLUMN_FORMATS, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (listview), column); + + /* Create model, it also add model to the tree view */ + create_model (listview, sel_format, plugin); + + g_signal_connect (listview, + "realize", + G_CALLBACK (scroll_to_selected), + NULL); + + gtk_widget_show (listview); +} + +static void +updated_custom_format_example (GtkEntry *format_entry, + GtkLabel *format_example) +{ + const gchar *format; + gchar *time; + gchar *str; + gchar *escaped_time; + + gedit_debug (DEBUG_PLUGINS); + + g_return_if_fail (GTK_IS_ENTRY (format_entry)); + g_return_if_fail (GTK_IS_LABEL (format_example)); + + format = gtk_entry_get_text (format_entry); + + time = get_time (format); + escaped_time = g_markup_escape_text (time, -1); + + str = g_strdup_printf ("<span size=\"small\">%s</span>", escaped_time); + + gtk_label_set_markup (format_example, str); + + g_free (escaped_time); + g_free (time); + g_free (str); +} + +static void +choose_format_dialog_button_toggled (GtkToggleButton *button, + ChooseFormatDialog *dialog) +{ + gedit_debug (DEBUG_PLUGINS); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->custom))) + { + gtk_widget_set_sensitive (dialog->list, FALSE); + gtk_widget_set_sensitive (dialog->custom_entry, TRUE); + gtk_widget_set_sensitive (dialog->custom_format_example, TRUE); + + return; + } + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list))) + { + gtk_widget_set_sensitive (dialog->list, TRUE); + gtk_widget_set_sensitive (dialog->custom_entry, FALSE); + gtk_widget_set_sensitive (dialog->custom_format_example, FALSE); + + return; + } +} + +static void +configure_dialog_button_toggled (GtkToggleButton *button, TimeConfigureDialog *dialog) +{ + gedit_debug (DEBUG_PLUGINS); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->custom))) + { + gtk_widget_set_sensitive (dialog->list, FALSE); + gtk_widget_set_sensitive (dialog->custom_entry, TRUE); + gtk_widget_set_sensitive (dialog->custom_format_example, TRUE); + + return; + } + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list))) + { + gtk_widget_set_sensitive (dialog->list, TRUE); + gtk_widget_set_sensitive (dialog->custom_entry, FALSE); + gtk_widget_set_sensitive (dialog->custom_format_example, FALSE); + + return; + } + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->prompt))) + { + gtk_widget_set_sensitive (dialog->list, FALSE); + gtk_widget_set_sensitive (dialog->custom_entry, FALSE); + gtk_widget_set_sensitive (dialog->custom_format_example, FALSE); + + return; + } +} + +static gint +get_format_from_list (GtkWidget *listview) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + gedit_debug (DEBUG_PLUGINS); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (listview)); + g_return_val_if_fail (model != NULL, 0); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview)); + g_return_val_if_fail (selection != NULL, 0); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + gint selected_value; + + gtk_tree_model_get (model, &iter, COLUMN_INDEX, &selected_value, -1); + + gedit_debug_message (DEBUG_PLUGINS, "Sel value: %d", selected_value); + + return selected_value; + } + + g_return_val_if_reached (0); +} + +static TimeConfigureDialog * +get_configure_dialog (GeditTimePlugin *plugin) +{ + TimeConfigureDialog *dialog = NULL; + gchar *data_dir; + gchar *ui_file; + GtkWidget *content; + GtkWidget *viewport; + GeditTimePluginPromptType prompt_type; + gchar *sf, *cf; + GtkWidget *error_widget; + gboolean ret; + gchar *root_objects[] = { + "time_dialog_content", + NULL + }; + + gedit_debug (DEBUG_PLUGINS); + + dialog = g_new0 (TimeConfigureDialog, 1); + + dialog->dialog = gtk_dialog_new_with_buttons (_("Configure insert date/time plugin..."), + NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, + GTK_RESPONSE_OK, + GTK_STOCK_HELP, + GTK_RESPONSE_HELP, + NULL); + + /* HIG defaults */ + gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog->dialog)), 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog))), + 2); /* 2 * 5 + 2 = 12 */ + gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog->dialog))), + 5); + gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dialog->dialog))), 6); + + g_return_val_if_fail (dialog->dialog != NULL, NULL); + + data_dir = gedit_plugin_get_data_dir (GEDIT_PLUGIN (plugin)); + ui_file = g_build_filename (data_dir, "gedit-time-setup-dialog.ui", NULL); + ret = gedit_utils_get_ui_objects (ui_file, + root_objects, + &error_widget, + "time_dialog_content", &content, + "formats_viewport", &viewport, + "formats_tree", &dialog->list, + "always_prompt", &dialog->prompt, + "never_prompt", &dialog->use_list, + "use_custom", &dialog->custom, + "custom_entry", &dialog->custom_entry, + "custom_format_example", &dialog->custom_format_example, + NULL); + + g_free (data_dir); + g_free (ui_file); + + if (!ret) + { + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog))), + error_widget, + TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (error_widget), 5); + + gtk_widget_show (error_widget); + + return dialog; + } + + gtk_window_set_resizable (GTK_WINDOW (dialog->dialog), FALSE); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog->dialog), FALSE); + + sf = get_selected_format (plugin); + create_formats_list (dialog->list, sf, plugin); + g_free (sf); + + prompt_type = get_prompt_type (plugin); + + cf = get_custom_format (plugin); + gtk_entry_set_text (GTK_ENTRY(dialog->custom_entry), cf); + g_free (cf); + + if (prompt_type == USE_CUSTOM_FORMAT) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->custom), TRUE); + + gtk_widget_set_sensitive (dialog->list, FALSE); + gtk_widget_set_sensitive (dialog->custom_entry, TRUE); + gtk_widget_set_sensitive (dialog->custom_format_example, TRUE); + } + else if (prompt_type == USE_SELECTED_FORMAT) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->use_list), TRUE); + + gtk_widget_set_sensitive (dialog->list, TRUE); + gtk_widget_set_sensitive (dialog->custom_entry, FALSE); + gtk_widget_set_sensitive (dialog->custom_format_example, FALSE); + } + else + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->prompt), TRUE); + + gtk_widget_set_sensitive (dialog->list, FALSE); + gtk_widget_set_sensitive (dialog->custom_entry, FALSE); + gtk_widget_set_sensitive (dialog->custom_format_example, FALSE); + } + + updated_custom_format_example (GTK_ENTRY (dialog->custom_entry), + GTK_LABEL (dialog->custom_format_example)); + + /* setup a window of a sane size. */ + gtk_widget_set_size_request (GTK_WIDGET (viewport), 10, 200); + + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog))), + content, FALSE, FALSE, 0); + g_object_unref (content); + gtk_container_set_border_width (GTK_CONTAINER (content), 5); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog), + GTK_RESPONSE_OK); + + g_signal_connect (dialog->custom, + "toggled", + G_CALLBACK (configure_dialog_button_toggled), + dialog); + g_signal_connect (dialog->prompt, + "toggled", + G_CALLBACK (configure_dialog_button_toggled), + dialog); + g_signal_connect (dialog->use_list, + "toggled", + G_CALLBACK (configure_dialog_button_toggled), + dialog); + g_signal_connect (dialog->dialog, + "destroy", + G_CALLBACK (dialog_destroyed), + dialog); + g_signal_connect (dialog->custom_entry, + "changed", + G_CALLBACK (updated_custom_format_example), + dialog->custom_format_example); + + return dialog; +} + +static void +real_insert_time (GtkTextBuffer *buffer, + const gchar *the_time) +{ + gedit_debug_message (DEBUG_PLUGINS, "Insert: %s", the_time); + + gtk_text_buffer_begin_user_action (buffer); + + gtk_text_buffer_insert_at_cursor (buffer, the_time, -1); + gtk_text_buffer_insert_at_cursor (buffer, " ", -1); + + gtk_text_buffer_end_user_action (buffer); +} + +static void +choose_format_dialog_row_activated (GtkTreeView *list, + GtkTreePath *path, + GtkTreeViewColumn *column, + ChooseFormatDialog *dialog) +{ + gint sel_format; + gchar *the_time; + + sel_format = get_format_from_list (dialog->list); + the_time = get_time (formats[sel_format]); + + set_prompt_type (dialog->plugin, PROMPT_SELECTED_FORMAT); + set_selected_format (dialog->plugin, formats[sel_format]); + + g_return_if_fail (the_time != NULL); + + real_insert_time (dialog->buffer, the_time); + + g_free (the_time); +} + +static ChooseFormatDialog * +get_choose_format_dialog (GtkWindow *parent, + GeditTimePluginPromptType prompt_type, + GeditTimePlugin *plugin) +{ + ChooseFormatDialog *dialog; + gchar *data_dir; + gchar *ui_file; + GtkWidget *error_widget; + gboolean ret; + gchar *sf, *cf; + GtkWindowGroup *wg = NULL; + + if (parent != NULL) + wg = gtk_window_get_group (parent); + + dialog = g_new0 (ChooseFormatDialog, 1); + + data_dir = gedit_plugin_get_data_dir (GEDIT_PLUGIN (plugin)); + ui_file = g_build_filename (data_dir, "gedit-time-dialog.ui", NULL); + ret = gedit_utils_get_ui_objects (ui_file, + NULL, + &error_widget, + "choose_format_dialog", &dialog->dialog, + "choice_list", &dialog->list, + "use_sel_format_radiobutton", &dialog->use_list, + "use_custom_radiobutton", &dialog->custom, + "custom_entry", &dialog->custom_entry, + "custom_format_example", &dialog->custom_format_example, + NULL); + + g_free (data_dir); + g_free (ui_file); + + if (!ret) + { + GtkWidget *err_dialog; + + err_dialog = gtk_dialog_new_with_buttons (NULL, + parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + + if (wg != NULL) + gtk_window_group_add_window (wg, GTK_WINDOW (err_dialog)); + + gtk_window_set_resizable (GTK_WINDOW (err_dialog), FALSE); + gtk_dialog_set_has_separator (GTK_DIALOG (err_dialog), FALSE); + gtk_dialog_set_default_response (GTK_DIALOG (err_dialog), GTK_RESPONSE_OK); + + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (err_dialog))), + error_widget); + + g_signal_connect (G_OBJECT (err_dialog), + "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + + gtk_widget_show_all (err_dialog); + + return NULL; + } + + gtk_window_group_add_window (wg, + GTK_WINDOW (dialog->dialog)); + gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), parent); + gtk_window_set_modal (GTK_WINDOW (dialog->dialog), TRUE); + + sf = get_selected_format (plugin); + create_formats_list (dialog->list, sf, plugin); + g_free (sf); + + cf = get_custom_format (plugin); + gtk_entry_set_text (GTK_ENTRY(dialog->custom_entry), cf); + g_free (cf); + + updated_custom_format_example (GTK_ENTRY (dialog->custom_entry), + GTK_LABEL (dialog->custom_format_example)); + + if (prompt_type == PROMPT_CUSTOM_FORMAT) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->custom), TRUE); + + gtk_widget_set_sensitive (dialog->list, FALSE); + gtk_widget_set_sensitive (dialog->custom_entry, TRUE); + gtk_widget_set_sensitive (dialog->custom_format_example, TRUE); + } + else if (prompt_type == PROMPT_SELECTED_FORMAT) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->use_list), TRUE); + + gtk_widget_set_sensitive (dialog->list, TRUE); + gtk_widget_set_sensitive (dialog->custom_entry, FALSE); + gtk_widget_set_sensitive (dialog->custom_format_example, FALSE); + } + else + { + g_return_val_if_reached (NULL); + } + + /* setup a window of a sane size. */ + gtk_widget_set_size_request (dialog->list, 10, 200); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog), + GTK_RESPONSE_OK); + + g_signal_connect (dialog->custom, + "toggled", + G_CALLBACK (choose_format_dialog_button_toggled), + dialog); + g_signal_connect (dialog->use_list, + "toggled", + G_CALLBACK (choose_format_dialog_button_toggled), + dialog); + g_signal_connect (dialog->dialog, + "destroy", + G_CALLBACK (dialog_destroyed), + dialog); + g_signal_connect (dialog->custom_entry, + "changed", + G_CALLBACK (updated_custom_format_example), + dialog->custom_format_example); + g_signal_connect (dialog->list, + "row_activated", + G_CALLBACK (choose_format_dialog_row_activated), + dialog); + + gtk_window_set_resizable (GTK_WINDOW (dialog->dialog), FALSE); + + return dialog; +} + +static void +choose_format_dialog_response_cb (GtkWidget *widget, + gint response, + ChooseFormatDialog *dialog) +{ + switch (response) + { + case GTK_RESPONSE_HELP: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_HELP"); + gedit_help_display (GTK_WINDOW (widget), + NULL, + "gedit-insert-date-time-plugin"); + break; + } + case GTK_RESPONSE_OK: + { + gchar *the_time; + + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK"); + + /* Get the user's chosen format */ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list))) + { + gint sel_format; + + sel_format = get_format_from_list (dialog->list); + the_time = get_time (formats[sel_format]); + + set_prompt_type (dialog->plugin, PROMPT_SELECTED_FORMAT); + set_selected_format (dialog->plugin, formats[sel_format]); + } + else + { + const gchar *format; + + format = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry)); + the_time = get_time (format); + + set_prompt_type (dialog->plugin, PROMPT_CUSTOM_FORMAT); + set_custom_format (dialog->plugin, format); + } + + g_return_if_fail (the_time != NULL); + + real_insert_time (dialog->buffer, the_time); + g_free (the_time); + + gtk_widget_destroy (dialog->dialog); + break; + } + case GTK_RESPONSE_CANCEL: + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CANCEL"); + gtk_widget_destroy (dialog->dialog); + } +} + +static void +time_cb (GtkAction *action, + ActionData *data) +{ + GtkTextBuffer *buffer; + gchar *the_time = NULL; + GeditTimePluginPromptType prompt_type; + + gedit_debug (DEBUG_PLUGINS); + + buffer = GTK_TEXT_BUFFER (gedit_window_get_active_document (data->window)); + g_return_if_fail (buffer != NULL); + + prompt_type = get_prompt_type (data->plugin); + + if (prompt_type == USE_CUSTOM_FORMAT) + { + gchar *cf = get_custom_format (data->plugin); + the_time = get_time (cf); + g_free (cf); + } + else if (prompt_type == USE_SELECTED_FORMAT) + { + gchar *sf = get_selected_format (data->plugin); + the_time = get_time (sf); + g_free (sf); + } + else + { + ChooseFormatDialog *dialog; + + dialog = get_choose_format_dialog (GTK_WINDOW (data->window), + prompt_type, + data->plugin); + if (dialog != NULL) + { + dialog->buffer = buffer; + dialog->plugin = data->plugin; + + g_signal_connect (dialog->dialog, + "response", + G_CALLBACK (choose_format_dialog_response_cb), + dialog); + + gtk_widget_show (GTK_WIDGET (dialog->dialog)); + } + + return; + } + + g_return_if_fail (the_time != NULL); + + real_insert_time (buffer, the_time); + + g_free (the_time); +} + +static void +ok_button_pressed (TimeConfigureDialog *dialog) +{ + gint sel_format; + const gchar *custom_format; + + gedit_debug (DEBUG_PLUGINS); + + sel_format = get_format_from_list (dialog->list); + + custom_format = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry)); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->custom))) + { + set_prompt_type (dialog->plugin, USE_CUSTOM_FORMAT); + set_custom_format (dialog->plugin, custom_format); + } + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list))) + { + set_prompt_type (dialog->plugin, USE_SELECTED_FORMAT); + set_selected_format (dialog->plugin, formats [sel_format]); + } + else + { + /* Default to prompt the user with the list selected */ + set_prompt_type (dialog->plugin, PROMPT_SELECTED_FORMAT); + } + + gedit_debug_message (DEBUG_PLUGINS, "Sel: %d", sel_format); +} + +static void +configure_dialog_response_cb (GtkWidget *widget, + gint response, + TimeConfigureDialog *dialog) +{ + switch (response) + { + case GTK_RESPONSE_HELP: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_HELP"); + + gedit_help_display (GTK_WINDOW (dialog), + NULL, + "gedit-date-time-configure"); + break; + } + case GTK_RESPONSE_OK: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK"); + + ok_button_pressed (dialog); + + gtk_widget_destroy (dialog->dialog); + break; + } + case GTK_RESPONSE_CANCEL: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CANCEL"); + gtk_widget_destroy (dialog->dialog); + } + } +} + +static GtkWidget * +impl_create_configure_dialog (GeditPlugin *plugin) +{ + TimeConfigureDialog *dialog; + + dialog = get_configure_dialog (GEDIT_TIME_PLUGIN (plugin)); + + dialog->plugin = GEDIT_TIME_PLUGIN (plugin); + + g_signal_connect (dialog->dialog, + "response", + G_CALLBACK (configure_dialog_response_cb), + dialog); + + return GTK_WIDGET (dialog->dialog); +} + +static void +gedit_time_plugin_class_init (GeditTimePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_time_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; + + plugin_class->create_configure_dialog = impl_create_configure_dialog; + + g_type_class_add_private (object_class, sizeof (GeditTimePluginPrivate)); +} diff --git a/plugins/time/gedit-time-plugin.h b/plugins/time/gedit-time-plugin.h new file mode 100755 index 00000000..6d598264 --- /dev/null +++ b/plugins/time/gedit-time-plugin.h @@ -0,0 +1,78 @@ +/* + * gedit-time-plugin.h + * + * 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, 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$ + */ + +#ifndef __GEDIT_TIME_PLUGIN_H__ +#define __GEDIT_TIME_PLUGIN_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gedit/gedit-plugin.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_TIME_PLUGIN (gedit_time_plugin_get_type ()) +#define GEDIT_TIME_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_TIME_PLUGIN, GeditTimePlugin)) +#define GEDIT_TIME_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_TIME_PLUGIN, GeditTimePluginClass)) +#define GEDIT_IS_TIME_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_TIME_PLUGIN)) +#define GEDIT_IS_TIME_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_TIME_PLUGIN)) +#define GEDIT_TIME_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_TIME_PLUGIN, GeditTimePluginClass)) + +/* Private structure type */ +typedef struct _GeditTimePluginPrivate GeditTimePluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditTimePlugin GeditTimePlugin; + +struct _GeditTimePlugin +{ + GeditPlugin parent_instance; + + /*< private >*/ + GeditTimePluginPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditTimePluginClass GeditTimePluginClass; + +struct _GeditTimePluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_time_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_TIME_PLUGIN_H__ */ diff --git a/plugins/time/gedit-time-setup-dialog.ui b/plugins/time/gedit-time-setup-dialog.ui new file mode 100755 index 00000000..46fb5b9d --- /dev/null +++ b/plugins/time/gedit-time-setup-dialog.ui @@ -0,0 +1,330 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <requires lib="gtk+" version="2.16"/> + <object class="GtkDialog" id="time_dialog"> + <property name="title" translatable="yes">Configure date/time plugin</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="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_END</property> + <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-help</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + </object> + </child> + <child> + <object class="GtkButton" id="button3"> + <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="button4"> + <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="GtkVBox" id="time_dialog_content"> + <property name="border_width">10</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">When inserting date/time...</property> + <property name="use_underline">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> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes"> </property> + <property name="use_underline">False</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.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox4"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkRadioButton" id="always_prompt"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Prompt for a format</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</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">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox5"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkRadioButton" id="never_prompt"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Use the _selected format</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + <property name="group">always_prompt</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox3"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes"> </property> + <property name="use_underline">False</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.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</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="formats_viewport"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + <child> + <object class="GtkTreeView" id="formats_tree"> + <property name="visible">True</property> + <property name="headers_visible">False</property> + <property name="rules_hint">False</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> + </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> + <child> + <object class="GtkVBox" id="vbox6"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">2</property> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkRadioButton" id="use_custom"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Use custom format</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + <property name="group">always_prompt</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="custom_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes" comments="Translators: Use the more common date format in your locale">%d/%m/%Y %H:%M:%S</property> + <property name="has_frame">True</property> + <property name="activates_default">False</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="padding">2</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="custom_format_example"> + <property name="visible">True</property> + <property name="label" translatable="yes" comments="Translators: This example should follow the date format defined in the entry above">01/11/2009 17:52:00</property> + <property name="use_underline">False</property> + <property name="justify">GTK_JUSTIFY_RIGHT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <attributes> + <attribute name="scale" value="0.8"/><!-- PANGO_SCALE_SMALL --> + </attributes> + </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> + <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">False</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">button1</action-widget> + <action-widget response="0">button3</action-widget> + <action-widget response="0">button4</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/time/time.gedit-plugin.desktop.in b/plugins/time/time.gedit-plugin.desktop.in new file mode 100755 index 00000000..ba06854a --- /dev/null +++ b/plugins/time/time.gedit-plugin.desktop.in @@ -0,0 +1,8 @@ +[Gedit Plugin] +Module=time +IAge=2 +_Name=Insert Date/Time +_Description=Inserts current date and time at the cursor position. +Authors=Paolo Maggi <[email protected]>;Lee Mallabone <[email protected]> +Copyright=Copyright © 2002-2005 Paolo Maggi +Website=http://www.gedit.org |