diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:32:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:32:59 +0000 |
commit | adb934701975f6b0214475d1a8d0d1ce727b9d4d (patch) | |
tree | 5688c745d10b64c8856586864ec416a6bdae881d /plugins | |
parent | Initial commit. (diff) | |
download | gedit-adb934701975f6b0214475d1a8d0d1ce727b9d4d.tar.xz gedit-adb934701975f6b0214475d1a8d0d1ce727b9d4d.zip |
Adding upstream version 3.38.1.upstream/3.38.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins')
189 files changed, 40021 insertions, 0 deletions
diff --git a/plugins/docinfo/docinfo.plugin.desktop.in b/plugins/docinfo/docinfo.plugin.desktop.in new file mode 100644 index 0000000..b493bb4 --- /dev/null +++ b/plugins/docinfo/docinfo.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=docinfo +IAge=3 +Name=Document Statistics +Description=Report the number of words, lines and characters in a document. +Authors=Paolo Maggi <paolo.maggi@polito.it>;Jorge Alberto Torres <jorge@deadoak.com> +Copyright=Copyright © 2002-2005 Paolo Maggi +Website=http://www.gedit.org diff --git a/plugins/docinfo/gedit-docinfo-plugin.c b/plugins/docinfo/gedit-docinfo-plugin.c new file mode 100644 index 0000000..5073254 --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.c @@ -0,0 +1,638 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + * + */ + +#include "config.h" + +#include "gedit-docinfo-plugin.h" + +#include <string.h> /* For strlen (...) */ + +#include <glib/gi18n.h> +#include <pango/pango-break.h> +#include <gmodule.h> + +#include <gedit/gedit-app.h> +#include <gedit/gedit-window.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-menu-extension.h> +#include <gedit/gedit-app-activatable.h> +#include <gedit/gedit-window-activatable.h> + +struct _GeditDocinfoPluginPrivate +{ + GeditWindow *window; + + GSimpleAction *action; + + GtkWidget *dialog; + GtkWidget *header_bar; + GtkWidget *lines_label; + GtkWidget *words_label; + GtkWidget *chars_label; + GtkWidget *chars_ns_label; + GtkWidget *bytes_label; + GtkWidget *document_label; + GtkWidget *document_lines_label; + GtkWidget *document_words_label; + GtkWidget *document_chars_label; + GtkWidget *document_chars_ns_label; + GtkWidget *document_bytes_label; + GtkWidget *selection_label; + GtkWidget *selected_lines_label; + GtkWidget *selected_words_label; + GtkWidget *selected_chars_label; + GtkWidget *selected_chars_ns_label; + GtkWidget *selected_bytes_label; + + GeditApp *app; + GeditMenuExtension *menu_ext; +}; + +enum +{ + PROP_0, + PROP_WINDOW, + PROP_APP +}; + +static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface); +static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditDocinfoPlugin, + gedit_docinfo_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE, + gedit_app_activatable_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE, + gedit_window_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditDocinfoPlugin)) + +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 +update_document_info (GeditDocinfoPlugin *plugin, + GeditDocument *doc) +{ + GeditDocinfoPluginPrivate *priv; + GtkTextIter start, end; + gint words = 0; + gint chars = 0; + gint white_chars = 0; + gint lines = 0; + gint bytes = 0; + gchar *doc_name; + gchar *tmp_str; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + 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); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (priv->header_bar), doc_name); + g_free (doc_name); + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (priv->document_lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (priv->document_words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (priv->document_chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (priv->document_chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (priv->document_bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +update_selection_info (GeditDocinfoPlugin *plugin, + GeditDocument *doc) +{ + GeditDocinfoPluginPrivate *priv; + 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); + + priv = plugin->priv; + + 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 (priv->selection_label, TRUE); + gtk_widget_set_sensitive (priv->selected_words_label, TRUE); + gtk_widget_set_sensitive (priv->selected_bytes_label, TRUE); + gtk_widget_set_sensitive (priv->selected_lines_label, TRUE); + gtk_widget_set_sensitive (priv->selected_chars_label, TRUE); + gtk_widget_set_sensitive (priv->selected_chars_ns_label, TRUE); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, "Selection empty"); + + gtk_widget_set_sensitive (priv->selection_label, FALSE); + gtk_widget_set_sensitive (priv->selected_words_label, FALSE); + gtk_widget_set_sensitive (priv->selected_bytes_label, FALSE); + gtk_widget_set_sensitive (priv->selected_lines_label, FALSE); + gtk_widget_set_sensitive (priv->selected_chars_label, FALSE); + gtk_widget_set_sensitive (priv->selected_chars_ns_label, FALSE); + } + + if (chars == 0) + lines = 0; + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (priv->selected_lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (priv->selected_words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (priv->selected_chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (priv->selected_chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (priv->selected_bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +docinfo_dialog_response_cb (GtkDialog *widget, + gint res_id, + GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + switch (res_id) + { + case GTK_RESPONSE_CLOSE: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CLOSE"); + + gtk_widget_destroy (priv->dialog); + + break; + } + + case GTK_RESPONSE_OK: + { + GeditDocument *doc; + + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK"); + + doc = gedit_window_get_active_document (priv->window); + + update_document_info (plugin, doc); + update_selection_info (plugin, doc); + + break; + } + } +} + +static void +create_docinfo_dialog (GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + GtkBuilder *builder; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + builder = gtk_builder_new (); + gtk_builder_add_from_resource (builder, "/org/gnome/gedit/plugins/docinfo/ui/gedit-docinfo-plugin.ui", NULL); + priv->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "dialog")); + priv->header_bar = GTK_WIDGET (gtk_builder_get_object (builder, "header_bar")); + priv->words_label = GTK_WIDGET (gtk_builder_get_object (builder, "words_label")); + priv->bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "bytes_label")); + priv->lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "lines_label")); + priv->chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "chars_label")); + priv->chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "chars_ns_label")); + priv->document_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_label")); + priv->document_words_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_words_label")); + priv->document_bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_bytes_label")); + priv->document_lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_lines_label")); + priv->document_chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_chars_label")); + priv->document_chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_chars_ns_label")); + priv->selection_label = GTK_WIDGET (gtk_builder_get_object (builder, "selection_label")); + priv->selected_words_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_words_label")); + priv->selected_bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_bytes_label")); + priv->selected_lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_lines_label")); + priv->selected_chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_chars_label")); + priv->selected_chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_chars_ns_label")); + g_object_unref (builder); + + gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog), + GTK_RESPONSE_OK); + gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), + GTK_WINDOW (priv->window)); + + g_signal_connect (priv->dialog, + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &priv->dialog); + g_signal_connect (priv->dialog, + "response", + G_CALLBACK (docinfo_dialog_response_cb), + plugin); + + /* We set this explictely with code since glade does not + * save the can_focus property when set to false :( + * Making sure the labels are not focusable is needed to + * prevent loosing the selection in the document when + * creating the dialog. + */ + gtk_widget_set_can_focus (priv->words_label, FALSE); + gtk_widget_set_can_focus (priv->bytes_label, FALSE); + gtk_widget_set_can_focus (priv->lines_label, FALSE); + gtk_widget_set_can_focus (priv->chars_label, FALSE); + gtk_widget_set_can_focus (priv->chars_ns_label, FALSE); + gtk_widget_set_can_focus (priv->document_label, FALSE); + gtk_widget_set_can_focus (priv->document_words_label, FALSE); + gtk_widget_set_can_focus (priv->document_bytes_label, FALSE); + gtk_widget_set_can_focus (priv->document_lines_label, FALSE); + gtk_widget_set_can_focus (priv->document_chars_label, FALSE); + gtk_widget_set_can_focus (priv->document_chars_ns_label, FALSE); + gtk_widget_set_can_focus (priv->selection_label, FALSE); + gtk_widget_set_can_focus (priv->selected_words_label, FALSE); + gtk_widget_set_can_focus (priv->selected_bytes_label, FALSE); + gtk_widget_set_can_focus (priv->selected_lines_label, FALSE); + gtk_widget_set_can_focus (priv->selected_chars_label, FALSE); + gtk_widget_set_can_focus (priv->selected_chars_ns_label, FALSE); +} + +static void +docinfo_cb (GAction *action, + GVariant *parameter, + GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + GeditDocument *doc; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + doc = gedit_window_get_active_document (priv->window); + + if (priv->dialog != NULL) + { + gtk_window_present (GTK_WINDOW (priv->dialog)); + gtk_widget_grab_focus (GTK_WIDGET (priv->dialog)); + } + else + { + create_docinfo_dialog (plugin); + gtk_widget_show (GTK_WIDGET (priv->dialog)); + } + + update_document_info (plugin, doc); + update_selection_info (plugin, doc); +} + +static void +gedit_docinfo_plugin_init (GeditDocinfoPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditDocinfoPlugin initializing"); + + plugin->priv = gedit_docinfo_plugin_get_instance_private (plugin); +} + +static void +gedit_docinfo_plugin_dispose (GObject *object) +{ + GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditDocinfoPlugin dispose"); + + g_clear_object (&plugin->priv->action); + g_clear_object (&plugin->priv->window); + g_clear_object (&plugin->priv->menu_ext); + g_clear_object (&plugin->priv->app); + + G_OBJECT_CLASS (gedit_docinfo_plugin_parent_class)->dispose (object); +} + + +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 +gedit_docinfo_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value)); + break; + case PROP_APP: + plugin->priv->app = GEDIT_APP (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_docinfo_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, plugin->priv->window); + break; + case PROP_APP: + g_value_set_object (value, plugin->priv->app); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +update_ui (GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + view = gedit_window_get_active_view (priv->window); + + g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->action), view != NULL); + + if (priv->dialog != NULL) + { + gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog), + GTK_RESPONSE_OK, + (view != NULL)); + } +} + +static void +gedit_docinfo_plugin_app_activate (GeditAppActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + GMenuItem *item; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "tools-section"); + item = g_menu_item_new (_("_Document Statistics"), "win.docinfo"); + gedit_menu_extension_append_menu_item (priv->menu_ext, item); + g_object_unref (item); +} + +static void +gedit_docinfo_plugin_app_deactivate (GeditAppActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + g_clear_object (&priv->menu_ext); +} + +static void +gedit_docinfo_plugin_window_activate (GeditWindowActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + priv->action = g_simple_action_new ("docinfo", NULL); + g_signal_connect (priv->action, "activate", + G_CALLBACK (docinfo_cb), activatable); + g_action_map_add_action (G_ACTION_MAP (priv->window), + G_ACTION (priv->action)); + + update_ui (GEDIT_DOCINFO_PLUGIN (activatable)); +} + +static void +gedit_docinfo_plugin_window_deactivate (GeditWindowActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + g_action_map_remove_action (G_ACTION_MAP (priv->window), "docinfo"); +} + +static void +gedit_docinfo_plugin_window_update_state (GeditWindowActivatable *activatable) +{ + gedit_debug (DEBUG_PLUGINS); + + update_ui (GEDIT_DOCINFO_PLUGIN (activatable)); +} + +static void +gedit_docinfo_plugin_class_init (GeditDocinfoPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_docinfo_plugin_dispose; + object_class->finalize = gedit_docinfo_plugin_finalize; + object_class->set_property = gedit_docinfo_plugin_set_property; + object_class->get_property = gedit_docinfo_plugin_get_property; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); + g_object_class_override_property (object_class, PROP_APP, "app"); +} + +static void +gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface) +{ + iface->activate = gedit_docinfo_plugin_app_activate; + iface->deactivate = gedit_docinfo_plugin_app_deactivate; +} + +static void +gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface) +{ + iface->activate = gedit_docinfo_plugin_window_activate; + iface->deactivate = gedit_docinfo_plugin_window_deactivate; + iface->update_state = gedit_docinfo_plugin_window_update_state; +} + +static void +gedit_docinfo_plugin_class_finalize (GeditDocinfoPluginClass *klass) +{ +} + + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_docinfo_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_APP_ACTIVATABLE, + GEDIT_TYPE_DOCINFO_PLUGIN); + peas_object_module_register_extension_type (module, + GEDIT_TYPE_WINDOW_ACTIVATABLE, + GEDIT_TYPE_DOCINFO_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/docinfo/gedit-docinfo-plugin.h b/plugins/docinfo/gedit-docinfo-plugin.h new file mode 100644 index 0000000..e657c9f --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.h @@ -0,0 +1,63 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef GEDIT_DOCINFO_PLUGIN_H +#define GEDIT_DOCINFO_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> + +G_BEGIN_DECLS + +#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)) + +typedef struct _GeditDocinfoPlugin GeditDocinfoPlugin; +typedef struct _GeditDocinfoPluginPrivate GeditDocinfoPluginPrivate; +typedef struct _GeditDocinfoPluginClass GeditDocinfoPluginClass; + +struct _GeditDocinfoPlugin +{ + PeasExtensionBase parent; + + /*< private >*/ + GeditDocinfoPluginPrivate *priv; +}; + +struct _GeditDocinfoPluginClass +{ + PeasExtensionBaseClass parent_class; +}; + +GType gedit_docinfo_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_DOCINFO_PLUGIN_H */ + +/* ex:set ts=8 noet: */ diff --git a/plugins/docinfo/meson.build b/plugins/docinfo/meson.build new file mode 100644 index 0000000..200ace5 --- /dev/null +++ b/plugins/docinfo/meson.build @@ -0,0 +1,34 @@ +libdocinfo_sources = files( + 'gedit-docinfo-plugin.c', +) + +libdocinfo_deps = [ + libgedit_dep, +] + +subdir('resources') + +libdocinfo_sha = shared_module( + 'docinfo', + sources: libdocinfo_sources, + include_directories: root_include_dir, + dependencies: libdocinfo_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'docinfo.plugin', + input: 'docinfo.plugin.desktop.in', + output: 'docinfo.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/docinfo/resources/gedit-docinfo.gresource.xml b/plugins/docinfo/resources/gedit-docinfo.gresource.xml new file mode 100644 index 0000000..9d61659 --- /dev/null +++ b/plugins/docinfo/resources/gedit-docinfo.gresource.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/gedit/plugins/docinfo"> + <file preprocess="xml-stripblanks">ui/gedit-docinfo-plugin.ui</file> + </gresource> +</gresources> diff --git a/plugins/docinfo/resources/meson.build b/plugins/docinfo/resources/meson.build new file mode 100644 index 0000000..b1f4d4d --- /dev/null +++ b/plugins/docinfo/resources/meson.build @@ -0,0 +1,8 @@ +libdocinfo_res = gnome.compile_resources( + 'gedit-docinfo-resources', + 'gedit-docinfo.gresource.xml', +) + +libdocinfo_sources += [ + libdocinfo_res.get(0), +] diff --git a/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui b/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui new file mode 100644 index 0000000..84cf918 --- /dev/null +++ b/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui @@ -0,0 +1,337 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="2.16"/> + <object class="GtkDialog" id="dialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Document Statistics</property> + <property name="resizable">False</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">dialog</property> + <child type="titlebar"> + <object class="GtkHeaderBar" id="header_bar"> + <property name="title" translatable="yes">Document Statistics</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show-close-button">True</property> + <child> + <object class="GtkButton" id="update_button"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="can_focus">True</property> + <property name="use_underline">True</property> + <style> + <class name="image-button"/> + </style> + <child> + <object class="GtkImage" id="reload_button_image"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">view-refresh-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + </object> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">8</property> + <child> + <object class="GtkBox" id="docinfo_dialog_content"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkGrid" id="grid_table"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">18</property> + <child> + <object class="GtkLabel" id="document_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Document</property> + <property name="use_markup">True</property> + <property name="use_underline">True</property> + <property name="justify">center</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selection_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Selection</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lines_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Lines</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="words_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Words</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="chars_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Characters (with spaces)</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="chars_ns_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Characters (no spaces)</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="bytes_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Bytes</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">5</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="document_lines_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="document_words_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="document_chars_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="document_chars_ns_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="document_bytes_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">5</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_lines_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_words_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_chars_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_chars_ns_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="selected_bytes_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label">0</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">5</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-5">update_button</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/externaltools/data/build.desktop.in b/plugins/externaltools/data/build.desktop.in new file mode 100644 index 0000000..14dfebd --- /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=all +Languages= diff --git a/plugins/externaltools/data/build.tool.in b/plugins/externaltools/data/build.tool.in new file mode 100755 index 0000000..0b81d5b --- /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/meson.build b/plugins/externaltools/data/meson.build new file mode 100644 index 0000000..02d5d6c --- /dev/null +++ b/plugins/externaltools/data/meson.build @@ -0,0 +1,46 @@ +externaltools_tools = [ + 'build', + 'remove-trailing-spaces', + 'send-to-fpaste', +] + +if host_machine.system() == 'darwin' + externaltools_tools += [ + 'open-terminal-here-osx', + ] +elif host_machine.system() != 'windows' + externaltools_tools += [ + 'open-terminal-here', + 'run-command', + ] +endif + +foreach tool_name: externaltools_tools + dektop_file = custom_target( + '@0@.desktop'.format(tool_name), + input: '@0@.desktop.in'.format(tool_name), + output: '@0@.desktop'.format(tool_name), + command: msgfmt_externaltools_cmd, + install: false, + ) + + custom_target( + '@0@.tool'.format(tool_name), + input: '@0@.tool.in'.format(tool_name), + output: '@0@'.format(tool_name), + depends: dektop_file, + command: [ + merge_tool_prg, + '@INPUT@', + dektop_file.full_path(), + ], + capture: true, + install: true, + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'externaltools', + 'tools', + ) + ) +endforeach 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 100644 index 0000000..846ff9a --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here-osx.desktop.in @@ -0,0 +1,9 @@ +[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= +Shortcut=<Primary><Alt>t 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 0000000..c336006 --- /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 100644 index 0000000..846ff9a --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.desktop.in @@ -0,0 +1,9 @@ +[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= +Shortcut=<Primary><Alt>t 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 0000000..9365648 --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "gconftool-2 -g /desktop/gnome/applications/terminal/exec" +gnome-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 100644 index 0000000..9a34de7 --- /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 0000000..83e4c19 --- /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 100644 index 0000000..ca7b7da --- /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 0000000..77ad4e6 --- /dev/null +++ b/plugins/externaltools/data/run-command.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "gconftool-2 -g /desktop/gnome/applications/terminal/exec" +eval $(zenity --entry --title="Run Command - gedit" --text="Command to run:") diff --git a/plugins/externaltools/data/send-to-fpaste.desktop.in b/plugins/externaltools/data/send-to-fpaste.desktop.in new file mode 100644 index 0000000..40282c8 --- /dev/null +++ b/plugins/externaltools/data/send-to-fpaste.desktop.in @@ -0,0 +1,11 @@ +[Gedit Tool] +Name=Send to fpaste +Comment=Paste selected text or current document to fpaste +Input=selection-document +Output=output-panel +Shortcut=<Shift><Super>p +Applicability=always +Save-files=nothing +Languages= + + diff --git a/plugins/externaltools/data/send-to-fpaste.tool.in b/plugins/externaltools/data/send-to-fpaste.tool.in new file mode 100755 index 0000000..d392173 --- /dev/null +++ b/plugins/externaltools/data/send-to-fpaste.tool.in @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import os, urllib, json, sys, urllib.request +from gi.repository import Gtk, Gdk + +text = sys.stdin.read() + +lang = os.getenv('GEDIT_CURRENT_DOCUMENT_LANGUAGE') +if lang is None: + lang = "text" + +url_params = urllib.parse.urlencode({'paste_data': text, 'paste_lang': lang, 'mode':'json', 'api_submit':'true'}) +openfpaste = urllib.request.urlopen("http://fpaste.org", bytes(url_params, 'utf-8')).read().decode("utf-8") +if openfpaste is None: + print("Failed to send fpaste request.") + +final_data = json.loads(openfpaste) + +paste_url = "http://fpaste.org/" + final_data['result']['id'] + +disp = Gdk.Display.get_default() +clipboard = Gtk.Clipboard.get_for_display(disp, Gdk.SELECTION_CLIPBOARD) +clipboard.set_text(paste_url, len(paste_url)) +clipboard.store() + +print(paste_url + " has been copied to the clipboard.") diff --git a/plugins/externaltools/externaltools.plugin.desktop.in b/plugins/externaltools/externaltools.plugin.desktop.in new file mode 100644 index 0000000..f575818 --- /dev/null +++ b/plugins/externaltools/externaltools.plugin.desktop.in @@ -0,0 +1,9 @@ +[Plugin] +Loader=python3 +Module=externaltools +IAge=3 +Name=External Tools +Description=Execute external commands and shell scripts. +Authors=Steve Frécinaux <steve@istique.net> +Copyright=Copyright © 2005 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/externaltools/meson.build b/plugins/externaltools/meson.build new file mode 100644 index 0000000..d46d0dd --- /dev/null +++ b/plugins/externaltools/meson.build @@ -0,0 +1,35 @@ +subdir('scripts') +subdir('tools') +subdir('data') + +externaltools_gschema_file = files('org.gnome.gedit.plugins.externaltools.gschema.xml') +install_data( + externaltools_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-externaltools-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + externaltools_gschema_file, + ] + ) +endif + +custom_target( + 'externaltools.plugin', + input: 'externaltools.plugin.desktop.in', + output: 'externaltools.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) + +subdir('tests') diff --git a/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml b/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml new file mode 100644 index 0000000..d760de2 --- /dev/null +++ b/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml @@ -0,0 +1,20 @@ +<schemalist gettext-domain="gedit"> + <schema id="org.gnome.gedit.plugins.externaltools" path="/org/gnome/gedit/plugins/externaltools/"> + <key name="use-system-font" type="b"> + <default>true</default> + <summary>Whether to use the system font</summary> + <description> + If true, the external tools will use the desktop-global standard + font if it’s monospace (and the most similar font it can + come up with otherwise). + </description> + </key> + <key name="font" type="s"> + <default>'Monospace 10'</default> + <summary>Font</summary> + <description> + A Pango font name. Examples are “Sans 12” or “Monospace Bold 14”. + </description> + </key> + </schema> +</schemalist> diff --git a/plugins/externaltools/scripts/gedit-tool-merge.pl b/plugins/externaltools/scripts/gedit-tool-merge.pl new file mode 100755 index 0000000..b7cbd77 --- /dev/null +++ b/plugins/externaltools/scripts/gedit-tool-merge.pl @@ -0,0 +1,78 @@ +#!/usr/bin/env perl + +# gedit-tool-merge.pl +# This file is part of gedit +# +# Copyright (C) 2006 - Steve Frécinaux <code@istique.net> +# +# 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/scripts/meson.build b/plugins/externaltools/scripts/meson.build new file mode 100644 index 0000000..c5f50e9 --- /dev/null +++ b/plugins/externaltools/scripts/meson.build @@ -0,0 +1,13 @@ +msgfmt_externaltools_cmd = [ + find_program('msgfmt'), + '--desktop', + '--keyword=Name', + '--keyword=Comment', + '--template=@INPUT@', + '-d', join_paths(srcdir, 'po'), + '--output=@OUTPUT@' +] + +merge_tool_prg = find_program( + files('gedit-tool-merge.pl'), +) diff --git a/plugins/externaltools/tests/meson.build b/plugins/externaltools/tests/meson.build new file mode 100644 index 0000000..6cee9fe --- /dev/null +++ b/plugins/externaltools/tests/meson.build @@ -0,0 +1,21 @@ +externaltools_tests = { + 'LinkParser': files('testlinkparsing.py'), +} + +externaltools_srcdir = join_paths( + srcdir, + 'plugins', + 'externaltools', + 'tools', +) + +foreach test_name, test_script : externaltools_tests + test( + 'test-externaltools-@0@'.format(test_name), + python3, + args: [test_script], + env: [ + 'PYTHONPATH=@0@'.format(externaltools_srcdir), + ] + ) +endforeach diff --git a/plugins/externaltools/tests/testlinkparsing.py b/plugins/externaltools/tests/testlinkparsing.py new file mode 100644 index 0000000..3b8a78e --- /dev/null +++ b/plugins/externaltools/tests/testlinkparsing.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2010 Per Arneng <per.arneng@anyplanet.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 unittest +from linkparsing import LinkParser + + +class TestLinkParser(unittest.TestCase): + + def setUp(self): + self.p = LinkParser() + + def assert_link_count(self, links, expected_count): + self.assertEqual(len(links), expected_count, 'incorrect nr of links') + + def assert_link(self, actual, path, line_nr, col_nr=0): + self.assertEqual(actual.path, path, "incorrect path") + self.assertEqual(actual.line_nr, line_nr, "incorrect line nr") + self.assertEqual(actual.col_nr, col_nr, "incorrect col nr") + + def assert_link_text(self, text, link, link_text): + self.assertEqual(text[link.start:link.end], link_text, + "the expected link text does not match the text within the string") + + def test_parse_gcc_simple_test_with_real_output(self): + gcc_output = """ +test.c: In function 'f': +test.c:5:6: warning: passing argument 1 of 'f' makes integer from pointer without a cast +test.c:3:7: note: expected 'int' but argument is of type 'char *' +test.c: In function 'main': +test.c:11:10: warning: initialization makes pointer from integer without a cast +test.c:12:11: warning: initialization makes integer from pointer without a cast +test.c:13:12: error: too few arguments to function 'f' +test.c:14:13: error: expected ';' before 'return' +""" + links = self.p.parse(gcc_output) + self.assert_link_count(links, 6) + lnk = links[2] + self.assert_link(lnk, "test.c", 11, 10) + self.assert_link_text(gcc_output, lnk, "test.c:11:10") + + def test_parse_gcc_one_line(self): + line = "/tmp/myfile.c:1212:12: error: ..." + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "/tmp/myfile.c", 1212, 12) + self.assert_link_text(line, lnk, "/tmp/myfile.c:1212:12") + + def test_parse_gcc_empty_string(self): + links = self.p.parse("") + self.assert_link_count(links, 0) + + def test_parse_gcc_no_files_in_text(self): + links = self.p.parse("no file links in this string") + self.assert_link_count(links, 0) + + def test_parse_gcc_none_as_argument(self): + self.assertRaises(ValueError, self.p.parse, None) + + def test_parse_grep_one_line(self): + line = "libnautilus-private/nautilus-canvas-container.h:45:#define NAUTILUS_CANVAS_ICON_DATA(pointer)" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "libnautilus-private/nautilus-canvas-container.h", 45) + self.assert_link_text(line, lnk, "libnautilus-private/nautilus-canvas-container.h:45") + + def test_parse_python_simple_test_with_real_output(self): + output = """ +Traceback (most recent call last): + File "test.py", line 10, in <module> + err() + File "test.py", line 7, in err + real_err() + File "test.py", line 4, in real_err + int('xxx') +ValueError: invalid literal for int() with base 10: 'xxx' +""" + links = self.p.parse(output) + self.assert_link_count(links, 3) + lnk = links[2] + self.assert_link(lnk, "test.py", 4) + self.assert_link_text(output, lnk, '"test.py", line 4') + + def test_parse_python_one_line(self): + line = " File \"test.py\", line 1\n def a()" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.py", 1) + self.assert_link_text(line, lnk, '"test.py", line 1') + + def test_parse_bash_one_line(self): + line = "test.sh: line 5: gerp: command not found" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.sh", 5) + self.assert_link_text(line, lnk, 'test.sh: line 5') + + def test_parse_javac_one_line(self): + line = "/tmp/Test.java:10: incompatible types" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "/tmp/Test.java", 10) + self.assert_link_text(line, lnk, '/tmp/Test.java:10') + + def test_parse_valac_simple_test_with_real_output(self): + output = """ +Test.vala:14.13-14.21: error: Assignment: Cannot convert from `string' to `int' + int a = "xxx"; + ^^^^^^^^^ +""" + links = self.p.parse(output) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "Test.vala", 14) + self.assert_link_text(output, lnk, 'Test.vala:14.13-14.21') + + def test_parse_ruby_simple_test_with_real_output(self): + output = """ +test.rb:5: undefined method `fake_method' for main:Object (NoMethodError) + from test.rb:3:in `each' + from test.rb:3 +""" + links = self.p.parse(output) + self.assert_link_count(links, 3) + lnk = links[0] + self.assert_link(lnk, "test.rb", 5) + self.assert_link_text(output, lnk, 'test.rb:5') + lnk = links[1] + self.assert_link(lnk, "test.rb", 3) + self.assert_link_text(output, lnk, 'test.rb:3') + + def test_parse_scalac_one_line(self): + line = "Test.scala:7: error: not found: value fakeMethod" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "Test.scala", 7) + self.assert_link_text(line, lnk, 'Test.scala:7') + + def test_parse_sbt_one_line(self): + line = "[error] /home/hank/foo/Test.scala:7: not found: value fakeMethod" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "/home/hank/foo/Test.scala", 7) + self.assert_link_text(line, lnk, '/home/hank/foo/Test.scala:7') + + def test_parse_go_6g_one_line(self): + line = "test.go:9: undefined: FakeMethod" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.go", 9) + self.assert_link_text(line, lnk, 'test.go:9') + + def test_parse_perl_one_line(self): + line = 'syntax error at test.pl line 889, near "$fake_var' + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.pl", 889) + self.assert_link_text(line, lnk, 'test.pl line 889') + + def test_parse_mcs_one_line(self): + line = 'Test.cs(12,7): error CS0103: The name `fakeMethod' + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "Test.cs", 12) + self.assert_link_text(line, lnk, 'Test.cs(12,7)') + + def test_parse_pas_one_line(self): + line = 'hello.pas(11,1) Fatal: Syntax error, ":" expected but "BEGIN"' + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "hello.pas", 11) + self.assert_link_text(line, lnk, 'hello.pas(11,1)') + +if __name__ == '__main__': + unittest.main() + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py new file mode 100644 index 0000000..0cc0b4f --- /dev/null +++ b/plugins/externaltools/tools/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: UTF-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2010 Ignacio Casal Quinteiro <icq@gnome.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 gi +gi.require_version('Gedit', '3.0') +gi.require_version('Gtk', '3.0') + +from .appactivatable import AppActivatable +from .windowactivatable import WindowActivatable + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/appactivatable.py b/plugins/externaltools/tools/appactivatable.py new file mode 100644 index 0000000..87e1226 --- /dev/null +++ b/plugins/externaltools/tools/appactivatable.py @@ -0,0 +1,178 @@ +# -*- coding: UTF-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from gi.repository import GLib, Gio, GObject, Gtk, Gdk, Gedit +from .library import ToolLibrary +from .manager import Manager +import os + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class ToolMenu(object): + def __init__(self, library, menu): + super(ToolMenu, self).__init__() + self._library = library + self._menu = menu + self._action_tools = {} + + self.update() + + def deactivate(self): + self.remove() + + def remove(self): + self._menu.remove_all() + + for name, tool in self._action_tools.items(): + if tool.shortcut: + app = Gio.Application.get_default() + app.remove_accelerator(tool.shortcut) + + def _insert_directory(self, directory, menu): + for d in sorted(directory.subdirs, key=lambda x: x.name.lower()): + submenu = Gio.Menu() + menu.append_submenu(d.name.replace('_', '__'), submenu) + section = Gio.Menu() + submenu.append_section(None, section) + + self._insert_directory(d, section) + + for tool in sorted(directory.tools, key=lambda x: x.name.lower()): + # FIXME: find a better way to share the action name + action_name = 'external-tool-%X-%X' % (id(tool), id(tool.name)) + item = Gio.MenuItem.new(tool.name.replace('_', '__'), "win.%s" % action_name) + item.set_attribute_value("hidden-when", GLib.Variant.new_string("action-disabled")) + menu.append_item(item) + + if tool.shortcut: + app = Gio.Application.get_default() + app.add_accelerator(tool.shortcut, "win.%s" % action_name, None) + + def update(self): + self.remove() + self._insert_directory(self._library.tree, self._menu) + + +# FIXME: restore the launch of the manager on configure using PeasGtk.Configurable +class AppActivatable(GObject.Object, Gedit.AppActivatable): + __gtype_name__ = "ExternalToolsAppActivatable" + + app = GObject.Property(type=Gedit.App) + + def __init__(self): + GObject.Object.__init__(self) + self.menu = None + self._manager = None + self._manager_default_size = None + + def do_activate(self): + self._library = ToolLibrary() + self._library.set_locations(os.path.join(self.plugin_info.get_data_dir(), 'tools')) + + action = Gio.SimpleAction(name="manage-tools") + action.connect("activate", lambda action, parameter: self._open_dialog()) + self.app.add_action(action) + + self.css = Gtk.CssProvider() + self.css.load_from_data(""" +.gedit-tool-manager-paned { + border-style: solid; + border-color: @borders; +} + +.gedit-tool-manager-paned:dir(ltr) { + border-width: 0 1px 0 0; +} + +.gedit-tool-manager-paned:dir(rtl) { + border-width: 0 0 0 1px; +} + +.gedit-tool-manager-view { + border-width: 0 0 1px 0; +} + +.gedit-tool-manager-treeview { + border-top-width: 0; +} + +.gedit-tool-manager-treeview:dir(ltr) { + border-left-width: 0; +} + +.gedit-tool-manager-treeview:dir(rtl) { + border-right-width: 0; +} +""".encode('utf-8')) + + Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), + self.css, 600) + + self.menu_ext = self.extend_menu("preferences-section") + item = Gio.MenuItem.new(_("Manage _External Tools…"), "app.manage-tools") + self.menu_ext.append_menu_item(item) + + self.submenu_ext = self.extend_menu("tools-section-1") + external_tools_submenu = Gio.Menu() + item = Gio.MenuItem.new_submenu(_("External _Tools"), external_tools_submenu) + self.submenu_ext.append_menu_item(item) + external_tools_submenu_section = Gio.Menu() + external_tools_submenu.append_section(None, external_tools_submenu_section) + + self.menu = ToolMenu(self._library, external_tools_submenu_section) + + def do_deactivate(self): + self.menu.deactivate() + self.menu_ext = None + self.submenu_ext = None + + self.app.remove_action("manage-tools") + + Gtk.StyleContext.remove_provider_for_screen(Gdk.Screen.get_default(), + self.css) + + def _open_dialog(self): + if not self._manager: + self._manager = Manager(self.plugin_info.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) + self._manager.connect('tools-updated', self._on_manager_tools_updated) + + self._manager.run(self.app.get_active_window()) + + return self._manager.dialog + + def _on_manager_destroy(self, dialog): + self._manager_default_size = self._manager.get_final_size() + self._manager = None + + def _on_manager_tools_updated(self, manager): + for window in self.app.get_main_windows(): + window.external_tools_window_activatable.update_actions() + self.menu.update() + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py new file mode 100644 index 0000000..d7560c5 --- /dev/null +++ b/plugins/externaltools/tools/capture.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +import sys +import signal +import locale +import subprocess +import fcntl +from gi.repository import GLib, GObject + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class Capture(GObject.Object): + CAPTURE_STDOUT = 0x01 + CAPTURE_STDERR = 0x02 + CAPTURE_BOTH = 0x03 + CAPTURE_NEEDS_SHELL = 0x04 + + WRITE_BUFFER_SIZE = 0x4000 + + __gsignals__ = { + 'stdout-line': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'stderr-line': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'begin-execute': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, tuple()), + 'end-execute': (GObject.SignalFlags.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.encode("UTF-8") if text else None + + 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.in_channel = None + self.out_channel = None + self.err_channel = None + self.in_channel_id = 0 + self.out_channel_id = 0 + self.err_channel_id = 0 + + try: + self.pipe = subprocess.Popen(self.command, **popen_args) + except OSError as e: + self.pipe = None + self.emit('stderr-line', _('Could not execute command: %s') % (e, )) + return + + self.emit('begin-execute') + + if self.input_text is not None: + self.in_channel, self.in_channel_id = self.add_in_watch(self.pipe.stdin.fileno(), + self.on_in_writable) + + if self.flags & self.CAPTURE_STDOUT: + self.out_channel, self.out_channel_id = self.add_out_watch(self.pipe.stdout.fileno(), + self.on_output) + + if self.flags & self.CAPTURE_STDERR: + self.err_channel, self.err_channel_id = self.add_out_watch(self.pipe.stderr.fileno(), + self.on_err_output) + + # Wait for the process to complete + GLib.child_watch_add(GLib.PRIORITY_DEFAULT, + self.pipe.pid, + self.on_child_end) + + def add_in_watch(self, fd, io_func): + channel = GLib.IOChannel.unix_new(fd) + channel.set_flags(channel.get_flags() | GLib.IOFlags.NONBLOCK) + channel.set_encoding(None) + channel_id = GLib.io_add_watch(channel, + GLib.PRIORITY_DEFAULT, + GLib.IOCondition.OUT | GLib.IOCondition.HUP | GLib.IOCondition.ERR, + io_func) + return (channel, channel_id) + + def add_out_watch(self, fd, io_func): + channel = GLib.IOChannel.unix_new(fd) + channel.set_flags(channel.get_flags() | GLib.IOFlags.NONBLOCK) + channel_id = GLib.io_add_watch(channel, + GLib.PRIORITY_DEFAULT, + GLib.IOCondition.IN | GLib.IOCondition.HUP | GLib.IOCondition.ERR, + io_func) + return (channel, channel_id) + + def write_chunk(self, dest, condition): + if condition & (GObject.IO_OUT): + status = GLib.IOStatus.NORMAL + l = len(self.input_text) + while status == GLib.IOStatus.NORMAL: + if l == 0: + return False + m = min(l, self.WRITE_BUFFER_SIZE) + try: + (status, length) = dest.write_chars(self.input_text, m) + self.input_text = self.input_text[length:] + l -= length + except Exception as e: + return False + if status != GLib.IOStatus.AGAIN: + return False + + if condition & ~(GObject.IO_OUT): + return False + + return True + + def on_in_writable(self, dest, condition): + ret = self.write_chunk(dest, condition) + if ret is False: + self.input_text = None + try: + self.in_channel.shutdown(True) + except: + pass + self.in_channel = None + self.in_channel_id = 0 + self.cleanup_pipe() + + return ret + + def handle_source(self, source, condition, signalname): + if condition & (GObject.IO_IN | GObject.IO_PRI): + status = GLib.IOStatus.NORMAL + while status == GLib.IOStatus.NORMAL: + try: + (status, buf, length, terminator_pos) = source.read_line() + except Exception as e: + return False + if buf: + self.emit(signalname, buf) + if status != GLib.IOStatus.AGAIN: + return False + + if condition & ~(GObject.IO_IN | GObject.IO_PRI): + return False + + return True + + def on_output(self, source, condition): + ret = self.handle_source(source, condition, 'stdout-line') + if ret is False and self.out_channel: + try: + self.out_channel.shutdown(True) + except: + pass + self.out_channel = None + self.out_channel_id = 0 + self.cleanup_pipe() + + return ret + + def on_err_output(self, source, condition): + ret = self.handle_source(source, condition, 'stderr-line') + if ret is False and self.err_channel: + try: + self.err_channel.shutdown(True) + except: + pass + self.err_channel = None + self.err_channel_id = 0 + self.cleanup_pipe() + + return ret + + def cleanup_pipe(self): + if self.in_channel is None and self.out_channel is None and self.err_channel is None: + self.pipe = None + + def stop(self, error_code=-1): + if self.in_channel_id: + GLib.source_remove(self.in_channel_id) + self.in_channel.shutdown(True) + self.in_channel = None + self.in_channel_id = 0 + + if self.out_channel_id: + GLib.source_remove(self.out_channel_id) + self.out_channel.shutdown(True) + self.out_channel = None + self.out_channel_id = 0 + + if self.err_channel_id: + GLib.source_remove(self.err_channel_id) + self.err_channel.shutdown(True) + self.err_channel = None + self.err_channel = 0 + + if self.pipe is not None: + if not self.tried_killing: + os.kill(self.pipe.pid, signal.SIGTERM) + self.tried_killing = True + else: + os.kill(self.pipe.pid, signal.SIGKILL) + + self.pipe = None + + def emit_end_execute(self, error_code): + self.emit('end-execute', error_code) + return False + + def on_child_end(self, pid, error_code): + # In an idle, so it is emitted after all the std*-line signals + # have been intercepted + GLib.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 100644 index 0000000..f256eea --- /dev/null +++ b/plugins/externaltools/tools/filelookup.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2010 Per Arneng <per.arneng@anyplanet.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +from gi.repository import Gio, Gedit +from .functions import * + + +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, window): + self.providers = [] + self.providers.append(AbsoluteFileLookupProvider()) + self.providers.append(BrowserRootFileLookupProvider(window)) + 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_new_for_path(path) + else: + return None + + +class BrowserRootFileLookupProvider(FileLookupProvider): + """ + This lookup provider tries to find a file specified by the path relative to + the file browser root. + """ + def __init__(self, window): + self.window = window + + def lookup(self, path): + root = file_browser_root(self.window) + if root: + real_path = os.path.join(root, path) + if os.path.isfile(real_path): + return Gio.file_new_for_path(real_path) + + 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_new_for_path(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 Gio.Application.get_default().get_documents(): + if doc.get_file().is_local(): + location = doc.get_file().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_new_for_path(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 Gio.Application.get_default().get_documents(): + if doc.get_file().is_local(): + location = doc.get_file().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 100644 index 0000000..bc755be --- /dev/null +++ b/plugins/externaltools/tools/functions.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +from gi.repository import Gio, Gtk, Gdk, GtkSource, Gedit +from .capture import * + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +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) + + +def file_browser_root(window): + bus = window.get_message_bus() + + if bus.is_registered('/plugins/filebrowser', 'get_root'): + msg = bus.send_sync('/plugins/filebrowser', 'get_root') + + if msg: + browser_root = msg.props.location + + if browser_root and browser_root.is_native(): + return browser_root.get_path() + + return None + + +# ==== Capture related functions ==== +def run_external_tool(window, panel, 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() + document = None + + if view is not None: + # Environment vars relative to current document + document = view.get_buffer() + location = document.get_file().get_location() + + # 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)) + + if document.get_language() is not None: + capture.set_env(GEDIT_CURRENT_DOCUMENT_LANGUAGE=document.get_language().get_id()) + + # 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 location is not None: + scheme = location.get_uri_scheme() + name = location.get_basename() + capture.set_env(GEDIT_CURRENT_DOCUMENT_URI=location.get_uri(), + GEDIT_CURRENT_DOCUMENT_NAME=name, + GEDIT_CURRENT_DOCUMENT_SCHEME=scheme) + if location.has_uri_scheme('file'): + path = location.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_location = [doc.get_file().get_location() + for doc in window.get_documents() + if doc.get_file().get_location() is not None] + documents_uri = [location.get_uri() + for location in documents_location + if location.get_uri() is not None] + documents_path = [location.get_path() + for location in documents_location + if location.has_uri_scheme('file')] + capture.set_env(GEDIT_DOCUMENTS_URI=' '.join(documents_uri), + GEDIT_DOCUMENTS_PATH=' '.join(documents_path)) + + # set file browser root env var if possible + browser_root = file_browser_root(window) + if browser_root: + capture.set_env(GEDIT_FILE_BROWSER_ROOT=browser_root) + + 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 + + # Clear the panel + 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() + 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.error_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, False) + 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.startswith('replace-'): + if output_type == 'replace-selection': + try: + start_iter, end_iter = document.get_selection_bounds() + except ValueError: + start_iter = document.get_iter_at_mark(document.get_insert()) + end_iter = start_iter.copy() + elif output_type == 'replace-document': + start_iter, end_iter = document.get_bounds() + capture.connect('stdout-line', capture_delayed_replace, + document, start_iter, end_iter) + else: + if output_type == 'insert': + pos = document.get_iter_at_mark(document.get_insert()) + 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) + + if not document is None: + 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': + if not document is None: + document.end_user_action() + +class MultipleDocumentsSaver: + def __init__(self, window, panel, all_docs, node): + self._window = window + self._panel = panel + self._node = node + + if all_docs: + docs = window.get_documents() + else: + docs = [window.get_active_document()] + + self._docs_to_save = [doc for doc in docs if doc.get_modified()] + self.save_next_document() + + def save_next_document(self): + if len(self._docs_to_save) == 0: + # The documents are saved, we can run the tool. + run_external_tool(self._window, self._panel, self._node) + else: + next_doc = self._docs_to_save[0] + self._docs_to_save.remove(next_doc) + + Gedit.commands_save_document_async(next_doc, + self._window, + None, + self.on_document_saved, + None) + + def on_document_saved(self, doc, result, user_data): + saved = Gedit.commands_save_document_finish(doc, result) + if saved: + self.save_next_document() + + +def capture_menu_action(action, parameter, window, panel, node): + if node.save_files == 'document' and window.get_active_document(): + MultipleDocumentsSaver(window, panel, False, node) + return + elif node.save_files == 'all': + MultipleDocumentsSaver(window, panel, True, node) + return + + run_external_tool(window, panel, 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): + if view: + view.get_window(Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor.new(Gdk.CursorType.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 view: + if output_type in ('new-document', 'replace-document'): + doc = view.get_buffer() + start = doc.get_start_iter() + end = start.copy() + end.forward_chars(300) + uri = '' + + mtype, uncertain = Gio.content_type_guess(None, doc.get_text(start, end, False).encode('utf-8')) + lmanager = GtkSource.LanguageManager.get_default() + + location = doc.get_file().get_location() + if location: + uri = location.get_uri() + language = lmanager.guess_language(uri, mtype) + + if language is not None: + doc.set_language(language) + + view.get_window(Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor.new(Gdk.CursorType.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) + + +def capture_delayed_replace(capture, line, document, start_iter, end_iter): + document.delete(start_iter, end_iter) + + # Must be done after deleting the text + pos = document.get_iter_at_mark(document.get_insert()) + + capture_stdout_line_document(capture, line, document, pos) + + capture.disconnect_by_func(capture_delayed_replace) + capture.connect('stdout-line', capture_stdout_line_document, document, pos) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py new file mode 100644 index 0000000..adfd943 --- /dev/null +++ b/plugins/externaltools/tools/library.py @@ -0,0 +1,520 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2006 Steve Frécinaux <code@istique.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +from gi.repository import GLib + + +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', '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('GNOME22_USER_DIR') + if userdir: + toolsdir = os.path.join(userdir, 'gedit/tools') + else: + toolsdir = os.path.join(GLib.get_user_config_dir(), '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('GNOME22_USER_DIR') + if userdir: + filename = os.path.join(userdir, 'gedit/gedit-tools.xml') + else: + filename = os.path.join(GLib.get_user_config_dir(), '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 = sorted(elements.keys()) + 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 [x.strip() for x in 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 = open(filename, 'r', 1, encoding='utf-8') + 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, encoding='utf-8') + 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, encoding='utf-8') + 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.items(): + 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, encoding='utf-8') + + # 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, 0o750) + 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() + library.set_locations(os.path.expanduser("~/.config/gedit/tools")) + + 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 100644 index 0000000..d9c09a5 --- /dev/null +++ b/plugins/externaltools/tools/linkparsing.py @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2010 Per Arneng <per.arneng@anyplanet.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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, col_nr, start, end): + """ + path -- the path of the file (that could be extracted) + line_nr -- the line nr of the specified file + col_nr -- the col nr of the specific 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.col_nr = int(col_nr) + self.start = start + self.end = end + + def __repr__(self): + return "%s[%s][%s](%s:%s)" % (self.path, self.line_nr, self.col_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): + groups = m.groups() + + path = m.group("pth") + line_nr = m.group("ln") + start = m.start("lnk") + end = m.end("lnk") + + # some regexes may have a col group + if len(groups) > 3 and groups[3] != None: + col_nr = m.group("col") + else: + col_nr = 0 + + link = Link(path, line_nr, col_nr, start, end) + links.append(link) + + return links + +# gcc 'test.c:13: warning: ...' +# grep 'test.c:5:int main(...' +# javac 'Test.java:13: ...' +# ruby 'test.rb:5: ...' +# scalac 'Test.scala:5: ...' +# sbt (scala) '[error] test.scala:4: ...' +# 6g (go) 'test.go:9: ...' +REGEXP_STANDARD = r""" +^ +(?:\[(?:error|warn)\]\ )? +(?P<lnk> + (?P<pth> [^ \:\n]* ) + \: + (?P<ln> \d+) + \:? + (?P<col> \d+)? +) +\:""" + +# 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' +# fpc (Pascal) 'hello.pas(11,1) Fatal: Syntax error, ":" expected but "BEGIN"' +REGEXP_MCS = r""" +^ +(?P<lnk> + (?P<pth> \S+ ) + \( + (?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 100644 index 0000000..072286b --- /dev/null +++ b/plugins/externaltools/tools/manager.py @@ -0,0 +1,878 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 os.path +from .library import * +from .functions import * +import hashlib +from xml.sax import saxutils +from gi.repository import Gio, GObject, Gtk, GtkSource, Gedit + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class LanguagesPopup(Gtk.Popover): + __gtype_name__ = "LanguagePopup" + + COLUMN_NAME = 0 + COLUMN_ID = 1 + COLUMN_ENABLED = 2 + + def __init__(self, widget, languages): + Gtk.Popover.__init__(self, relative_to=widget) + + self.props.can_focus = True + + self.build() + self.init_languages(languages) + + self.view.get_selection().select_path((0,)) + + def build(self): + self.model = Gtk.ListStore(str, str, bool) + + self.sw = Gtk.ScrolledWindow() + self.sw.set_size_request(-1, 200) + self.sw.show() + + self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + + self.view = Gtk.TreeView(model=self.model) + self.view.show() + + self.view.set_headers_visible(False) + + column = Gtk.TreeViewColumn() + + renderer = Gtk.CellRendererToggle() + column.pack_start(renderer, False) + column.add_attribute(renderer, 'active', self.COLUMN_ENABLED) + + renderer.connect('toggled', self.on_language_toggled) + + renderer = Gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, 'text', self.COLUMN_NAME) + + self.view.append_column(column) + self.view.set_row_separator_func(self.on_separator, None) + + 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.get_indices()[0] == 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, user_data=None): + val = model.get_value(piter, self.COLUMN_NAME) + return val == '-' + + def init_languages(self, languages): + manager = GtkSource.LanguageManager() + langs = [manager.get_language(x) for x in manager.get_language_ids()] + langs.sort(key=lambda x: x.get_name()) + + 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.get_indices()[0] == 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) + + +class Manager(GObject.Object): + TOOL_COLUMN = 0 # For Tree + NAME_COLUMN = 1 # For Combo + + __gsignals__ = { + 'tools-updated': (GObject.SignalFlags.RUN_LAST, None, ()) + } + + def __init__(self, datadir): + GObject.Object.__init__(self) + self.datadir = datadir + self.dialog = None + self._size = (0, 0) + self._languages = {} + self._tool_rows = {} + + self.build() + + def get_final_size(self): + return self._size + + def build(self): + callbacks = { + 'on_add_tool_button_clicked': self.on_add_tool_button_clicked, + 'on_remove_tool_button_clicked': self.on_remove_tool_button_clicked, + 'on_tool_manager_dialog_delete_event': self.on_tool_manager_dialog_delete_event, + 'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out, + 'on_tool_manager_dialog_configure_event': self.on_tool_manager_dialog_configure_event, + '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_accelerator_backspace': self.on_accelerator_backspace, + 'on_applicability_changed': self.on_applicability_changed, + '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['view'] + + self.__init_tools_model() + self.__init_tools_view() + + # join treeview and toolbar + context = self['scrolled_window1'].get_style_context() + context.set_junction_sides(Gtk.JunctionSides.BOTTOM) + context = self['toolbar1'].get_style_context() + context.set_junction_sides(Gtk.JunctionSides.TOP) + context.set_junction_sides(Gtk.JunctionSides.BOTTOM) + + 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 is 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, GtkSource.Language): + lid = language.get_id() + else: + lid = language + + if not lid in self._languages: + piter = self.model.append(None, [language]) + + parent = Gtk.TreeRowReference.new(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.new(self.model, self.model.get_path(child))) + return child + + def add_tool(self, tool): + manager = GtkSource.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.SortType.ASCENDING) + + def sort_tools(self, model, iter1, iter2, user_data=None): + # 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) is None: + if t1 is None: + return -1 + + if t2 is None: + return 1 + + def lang_name(lang): + if isinstance(lang, GtkSource.Language): + return lang.get_name() + else: + return _('Plain Text') + + n1 = lang_name(t1) + n2 = lang_name(t2) + else: + n1 = t1.name + n2 = t2.name + + if n1.lower() < n2.lower(): + return -1 + elif n1.lower() > n2.lower(): + return 1 + else: + return 0 + + 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, None) + + 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.encode('utf-8')).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() + (start, end) = buf.get_bounds() + script = buf.get_text(start, end, False) + 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 = GtkSource.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): + self.update_accelerator_label() + + buf = self['commands'].get_buffer() + script = default(''.join(self.current_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, uncertain = Gio.content_type_guess(None, script.encode('utf-8')) + lmanager = GtkSource.LanguageManager.get_default() + language = lmanager.guess_language(None, 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(self.current_node.__getattribute__(nm.replace('-', '_')), + model.get_value(piter, self.NAME_COLUMN))) + + self.fill_languages_button() + + def update_accelerator_label(self): + if self.current_node.shortcut: + key, mods = Gtk.accelerator_parse(self.current_node.shortcut) + label = Gtk.accelerator_get_label(key, mods) + self['accelerator'].set_text(label) + else: + self['accelerator'].set_text('') + + 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-grid'].set_sensitive(True) + else: + self.clear_fields() + self['tool-grid'].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, GtkSource.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_add_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-grid'].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.set_value(self.model.get_iter(row.get_path()), + self.TOOL_COLUMN, + tool) + + 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] = [x for x in self._tool_rows[node] if x.valid()] + + 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.DialogFlags.MODAL, + Gtk.MessageType.ERROR, + Gtk.ButtonsType.CLOSE, + _('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 == Gdk.KEY_Escape: + self.update_accelerator_label() + self['commands'].grab_focus() + return True + elif event.keyval == Gdk.KEY_BackSpace: + return False + elif event.keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1): + # New accelerator + if self.set_accelerator(event.keyval, mask): + self.update_accelerator_label() + self['commands'].grab_focus() + + # Capture all `normal characters` + return True + elif Gdk.keyval_to_unicode(event.keyval): + if mask: + # New accelerator + if self.set_accelerator(event.keyval, mask): + self.update_accelerator_label() + 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: + self.update_accelerator_label() + self.tool_changed(self.current_node) + + def on_accelerator_backspace(self, entry): + entry.set_text('') + self.remove_accelerator(self.current_node) + self.current_node.shortcut = None + self['commands'].grab_focus() + + def on_tool_manager_dialog_delete_event(self, dialog, event): + self.save_current_tool() + return False + + def on_tool_manager_dialog_focus_out(self, dialog, event): + self.save_current_tool() + self.emit('tools-updated') + + def on_tool_manager_dialog_configure_event(self, dialog, event): + if dialog.get_realized(): + alloc = dialog.get_allocation() + self._size = (alloc.width, alloc.height) + + def on_applicability_changed(self, combo): + applicability = combo.get_model().get_value(combo.get_active_iter(), + self.NAME_COLUMN) + + if applicability == 'always': + if self.current_node is not None: + self.current_node.languages = [] + + self.fill_languages_button() + + self['languages_button'].set_sensitive(applicability != 'always') + + def get_cell_data_cb(self, column, cell, model, piter, user_data=None): + tool = model.get_value(piter, self.TOOL_COLUMN) + + if tool is None or not isinstance(tool, Tool): + if tool is None: + label = _('All Languages') + elif not isinstance(tool, GtkSource.Language): + label = _('Plain Text') + else: + label = tool.get_name() + + markup = saxutils.escape(label) + editable = False + else: + escaped = saxutils.escape(tool.name) + + if tool.shortcut: + key, mods = Gtk.accelerator_parse(tool.shortcut) + label = Gtk.accelerator_get_label(key, mods) + markup = '%s (<b>%s</b>)' % (escaped, label) + 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.get_indices()[0] == parent.get_indices()[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.new(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 = GtkSource.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(button, self.current_node.languages) + popup.show() + popup.connect('closed', self.update_languages) + +# ex:et:ts=4: diff --git a/plugins/externaltools/tools/meson.build b/plugins/externaltools/tools/meson.build new file mode 100644 index 0000000..bd623cf --- /dev/null +++ b/plugins/externaltools/tools/meson.build @@ -0,0 +1,36 @@ +externaltools_sources = files( + '__init__.py', + 'appactivatable.py', + 'capture.py', + 'filelookup.py', + 'functions.py', + 'library.py', + 'linkparsing.py', + 'manager.py', + 'outputpanel.py', + 'windowactivatable.py', +) + +install_data( + externaltools_sources, + install_dir: join_paths( + pkglibdir, + 'plugins', + 'externaltools', + ) +) + +externaltools_data = files( + 'outputpanel.ui', + 'tools.ui', +) + +install_data( + externaltools_data, + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'externaltools', + 'ui', + ) +) diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py new file mode 100644 index 0000000..e9fc241 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net> +# Copyright (C) 2010 Per Arneng <per.arneng@anyplanet.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 os +from weakref import WeakKeyDictionary +from .capture import * +import re +from . import linkparsing +from . import filelookup +from gi.repository import GLib, Gio, Gdk, Gtk, Pango, Gedit + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +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 + } + + self.profile_settings = self.get_profile_settings() + self.profile_settings.connect("changed", self.font_changed) + self.system_settings = Gio.Settings.new("org.gnome.desktop.interface") + self.system_settings.connect("changed::monospace-font-name", self.font_changed) + + 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['view'].connect('button-press-event', self.on_view_button_press_event) + + self.panel = self["output-panel"] + self.font_changed() + + 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.new(Gdk.CursorType.HAND2) + self.normal_cursor = Gdk.Cursor.new(Gdk.CursorType.XTERM) + + self.process = None + + self.links = [] + + self.link_parser = linkparsing.LinkParser() + self.file_lookup = filelookup.FileLookup(window) + + def get_profile_settings(self): + #FIXME return either the gnome-terminal settings or the gedit one + return Gio.Settings.new("org.gnome.gedit.plugins.externaltools") + + def font_changed(self, settings=None, key=None): + if self.profile_settings.get_boolean("use-system-font"): + font = self.system_settings.get_string("monospace-font-name") + else: + font = self.profile_settings.get_string("font") + + font_desc = Pango.font_description_from_string(font) + + self["view"].override_font(font_desc) + + 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, False, 0.5, 0.5) + 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.props.visible_child == 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) + GLib.idle_add(self.scroll_to_end) + + def show(self): + panel = self.window.get_bottom_panel() + panel.props.visible_child = self.panel + panel.show() + + 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.TextWindowType.TEXT).set_cursor(cursor) + + def on_view_motion_notify_event(self, view, event): + if event.window == view.get_window(Gtk.TextWindowType.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.TextWindowType.TEXT): + win, x, y, flags = 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.TextWindowType.TEXT, x, y) + (over_text, iter_at_xy) = view.get_iter_at_location(buff_x, buff_y) + if not over_text: + return None + 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.EventType.BUTTON_PRESS or \ + event.window != view.get_window(Gtk.TextWindowType.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_location(self.window, gfile, None, link.line_nr, link.col_nr) + GLib.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 100644 index 0000000..4c163c2 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.ui @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.6 --> + <object class="GtkOverlay" id="output-panel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GtkTextView" id="view"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="wrap_mode">word</property> + <property name="cursor_visible">False</property> + <property name="accepts_tab">False</property> + <signal name="visibility-notify-event" handler="on_view_visibility_notify_event" swapped="no"/> + <signal name="motion-notify-event" handler="on_view_motion_notify_event" swapped="no"/> + </object> + </child> + </object> + </child> + <child type="overlay"> + <object class="GtkButton" id="stop"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">end</property> + <property name="halign">end</property> + <property name="margin_bottom">2</property> + <property name="margin_end">2</property> + <property name="tooltip_text" translatable="yes">Stop Tool</property> + <signal name="clicked" handler="on_stop_clicked" swapped="no"/> + <child> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">process-stop-symbolic</property> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui new file mode 100644 index 0000000..a3f0ab1 --- /dev/null +++ b/plugins/externaltools/tools/tools.ui @@ -0,0 +1,548 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.16.0 --> +<interface> + <requires lib="gtk+" version="3.0"/> + <object class="GeditDocument" id="commands_buffer"/> + <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">Always available</col> + <col id="1">always</col> + </row> + <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="GtkListStore" id="model_input"> + <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">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_save_files"> + <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">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="GtkWindow" id="tool-manager-dialog"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Manage External Tools</property> + <property name="default_width">800</property> + <property name="default_height">600</property> + <property name="type_hint">dialog</property> + <signal name="configure-event" handler="on_tool_manager_dialog_configure_event" swapped="no"/> + <signal name="delete-event" handler="on_tool_manager_dialog_delete_event" swapped="no"/> + <signal name="focus-out-event" handler="on_tool_manager_dialog_focus_out" swapped="no"/> + <child type="titlebar"> + <object class="GtkHeaderBar" id="headerbar"> + <property name="visible">True</property> + <property name="title" translatable="yes">Manage External Tools</property> + <property name="show_close_button">True</property> + </object> + </child> + <child> + <object class="GtkPaned" id="paned"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="vexpand">True</property> + <property name="position">275</property> + <property name="position_set">True</property> + <style> + <class name="gedit-tool-manager-paned"/> + </style> + <child> + <object class="GtkBox" id="vbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow" id="scrolled_window1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <style> + <class name="gedit-tool-manager-treeview"/> + </style> + <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> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection"/> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToolbar" id="toolbar1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="toolbar_style">icons</property> + <style> + <class name="inline-toolbar"/> + </style> + <property name="icon_size">1</property> + <child> + <object class="GtkToolButton" id="add-tool-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Add a new tool</property> + <property name="label" translatable="yes">Add Tool</property> + <property name="use_underline">True</property> + <property name="icon_name">list-add-symbolic</property> + <signal name="clicked" handler="on_add_tool_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="remove-tool-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Remove selected tool</property> + <property name="label" translatable="yes">Remove Tool</property> + <property name="use_underline">True</property> + <property name="icon_name">list-remove-symbolic</property> + <signal name="clicked" handler="on_remove_tool_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="revert-tool-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Revert tool</property> + <property name="label" translatable="yes">Revert Tool</property> + <property name="use_underline">True</property> + <property name="icon_name">edit-undo-symbolic</property> + <signal name="clicked" handler="on_remove_tool_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="resize">False</property> + <property name="shrink">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="vbox5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox" id="hbox7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkGrid" id="tool-grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">6</property> + <property name="margin_end">6</property> + <property name="margin_top">6</property> + <property name="margin_bottom">6</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can_focus">False</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> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="accelerator"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <signal name="backspace" handler="on_accelerator_backspace" swapped="no"/> + <signal name="focus-in-event" handler="on_accelerator_focus_in" swapped="no"/> + <signal name="focus-out-event" handler="on_accelerator_focus_out" swapped="no"/> + <signal name="key-press-event" handler="on_accelerator_key_press" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <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> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="save-files"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="model">model_save_files</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="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="can_focus">False</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="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="input"> + <property name="visible">True</property> + <property name="can_focus">False</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="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="can_focus">False</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="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="output"> + <property name="visible">True</property> + <property name="can_focus">False</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="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label23"> + <property name="visible">True</property> + <property name="can_focus">False</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="left_attach">0</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="hbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkComboBox" id="applicability"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="model">model_applicability</property> + <signal name="changed" handler="on_applicability_changed" swapped="no"/> + <child> + <object class="GtkCellRendererText" id="applicability_renderer"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="languages_button"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <signal name="clicked" handler="on_languages_button_clicked" swapped="no"/> + <child> + <object class="GtkLabel" id="languages_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">All Languages</property> + <property name="ellipsize">end</property> + <property name="width_chars">13</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <style> + <class name="gedit-tool-manager-view"/> + </style> + <child> + <object class="GeditView" id="commands"> + <property name="buffer">commands_buffer</property> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="resize">True</property> + <property name="shrink">False</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/plugins/externaltools/tools/windowactivatable.py b/plugins/externaltools/tools/windowactivatable.py new file mode 100644 index 0000000..5949598 --- /dev/null +++ b/plugins/externaltools/tools/windowactivatable.py @@ -0,0 +1,141 @@ +# -*- coding: UTF-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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', 'OutputPanel', 'Capture', 'UniqueById') + +from gi.repository import GLib, Gio, GObject, Gtk, Gedit +from .library import ToolLibrary +from .outputpanel import OutputPanel +from .capture import Capture +from .functions import * + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class ToolActions(object): + def __init__(self, library, window, panel): + super(ToolActions, self).__init__() + self._library = library + self._window = window + self._panel = panel + self._action_tools = {} + + self.update() + + def deactivate(self): + self.remove() + + def remove(self): + for name, tool in self._action_tools.items(): + self._window.remove_action(name) + self._action_tools = {} + + def _insert_directory(self, directory): + for tool in sorted(directory.tools, key=lambda x: x.name.lower()): + # FIXME: find a better way to share the action name + action_name = 'external-tool-%X-%X' % (id(tool), id(tool.name)) + self._action_tools[action_name] = tool + + action = Gio.SimpleAction(name=action_name) + action.connect('activate', capture_menu_action, self._window, self._panel, tool) + self._window.add_action(action) + + def update(self): + self.remove() + self._insert_directory(self._library.tree) + 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: + titled = False + remote = False + language = None + else: + titled = document.get_file().get_location() is not None + remote = not document.get_file().is_local() + language = document.get_language() + + states = { + 'always': True, + 'all': document is not None, + 'local': titled and not remote, + 'remote': titled and remote, + 'titled': titled, + 'untitled': not titled, + } + + for name, tool in self._action_tools.items(): + action = self._window.lookup_action(name) + if action: + action.set_enabled(states[tool.applicability] and + self.filter_language(language, tool)) + + +class WindowActivatable(GObject.Object, Gedit.WindowActivatable): + __gtype_name__ = "ExternalToolsWindowActivatable" + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + self.actions = None + + def do_activate(self): + self.window.external_tools_window_activatable = self + + self._library = ToolLibrary() + + # Create output console + self._output_buffer = OutputPanel(self.plugin_info.get_data_dir(), self.window) + + self.actions = ToolActions(self._library, self.window, self._output_buffer) + + bottom = self.window.get_bottom_panel() + bottom.add_titled(self._output_buffer.panel, "GeditExternalToolsShellOutput", _("Tool Output")) + + def do_update_state(self): + if self.actions is not None: + self.actions.filter(self.window.get_active_document()) + + def do_deactivate(self): + self.actions.deactivate() + bottom = self.window.get_bottom_panel() + bottom.remove(self._output_buffer.panel) + self.window.external_tools_window_activatable = None + + def update_actions(self): + self.actions.update() + +# ex:ts=4:et: diff --git a/plugins/filebrowser/filebrowser.plugin.desktop.in b/plugins/filebrowser/filebrowser.plugin.desktop.in new file mode 100644 index 0000000..706168d --- /dev/null +++ b/plugins/filebrowser/filebrowser.plugin.desktop.in @@ -0,0 +1,12 @@ +[Plugin] +Loader=C +Module=filebrowser +IAge=3 +Name=File Browser Panel +Description=Easy file access from the side panel. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +Icon=system-file-manager +Authors=Jesse van den Kieboom <jesse@icecrew.nl> +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 100644 index 0000000..df0968d --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.c @@ -0,0 +1,920 @@ +/* + * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gedit/gedit-utils.h> + +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-utils.h" + +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); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBookmarksStore, + gedit_file_bookmarks_store, + GTK_TYPE_TREE_STORE, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBookmarksStore)) + +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; + } + + g_clear_object (&obj->priv->bookmarks_monitor); + + G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->dispose (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; +} + +static void +gedit_file_bookmarks_store_class_finalize (GeditFileBookmarksStoreClass *klass) +{ +} + +static void +gedit_file_bookmarks_store_init (GeditFileBookmarksStore *obj) +{ + obj->priv = gedit_file_bookmarks_store_get_instance_private (obj); +} + +/* Private */ +static void +add_node (GeditFileBookmarksStore *model, + GdkPixbuf *pixbuf, + const gchar *icon_name, + 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_ICON_NAME, icon_name, + 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) +{ + gboolean native = g_file_is_native (file); + gchar *icon_name = NULL; + gchar *newname; + + if (native && !g_file_query_exists (file, NULL)) + return FALSE; + + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_HOME) + icon_name = g_strdup ("user-home-symbolic"); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP) + icon_name = g_strdup ("user-desktop-symbolic"); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT) + icon_name = g_strdup ("drive-harddisk-symbolic"); + else + { + /* getting the icon is a sync get_info call, so we just do it for local files */ + if (native) + icon_name = gedit_file_browser_utils_symbolic_icon_name_from_file (file); + else + icon_name = g_strdup ("folder-symbolic"); + } + + if (name == NULL) + newname = gedit_file_browser_utils_file_basename (file); + else + newname = g_strdup (name); + + add_node (model, NULL, icon_name, newname, G_OBJECT (file), flags, iter); + + g_free (icon_name); + g_free (newname); + + return TRUE; +} + +static void +check_mount_separator (GeditFileBookmarksStore *model, + guint flags, + gboolean added) +{ + GtkTreeIter iter; + gboolean 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, 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 = g_get_home_dir (); + GFile *file; + + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, + file, + _("Home"), + GEDIT_FILE_BOOKMARKS_STORE_IS_HOME | GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, + NULL); + g_object_unref (file); + } + +#if defined(G_OS_WIN32) || defined(OS_OSX) + 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); + } +#endif + + 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, + gchar **icon_name, + guint *flags) +{ + GIcon *icon = NULL; + + *flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS; + *name = NULL; + *icon_name = NULL; + + if (G_IS_DRIVE (fs)) + { + icon = g_drive_get_symbolic_icon (G_DRIVE (fs)); + *name = g_drive_get_name (G_DRIVE (fs)); + *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE; + } + else if (G_IS_VOLUME (fs)) + { + icon = g_volume_get_symbolic_icon (G_VOLUME (fs)); + *name = g_volume_get_name (G_VOLUME (fs)); + *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME; + } + else if (G_IS_MOUNT (fs)) + { + icon = g_mount_get_symbolic_icon (G_MOUNT (fs)); + *name = g_mount_get_name (G_MOUNT (fs)); + *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT; + } + + if (icon) + g_object_unref (icon); +} + +static void +add_fs (GeditFileBookmarksStore *model, + gpointer fs, + guint flags, + GtkTreeIter *iter) +{ + gchar *icon_name = NULL; + gchar *name = NULL; + guint fsflags; + + get_fs_properties (fs, &name, &icon_name, &fsflags); + add_node (model, NULL, icon_name, name, fs, flags | fsflags, iter); + + g_free (name); + g_free (icon_name); + check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_FS, TRUE); +} + +static void +process_volume_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GMount *mount = g_volume_get_mount (volume); + guint flags = GEDIT_FILE_BOOKMARKS_STORE_NONE; + + /* 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 = 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 = g_volume_monitor_get_connected_drives (model->priv->volume_monitor); + + g_list_foreach (drives, (GFunc)process_drive_cb, model); + g_list_free_full (drives, g_object_unref); +} + +static void +process_volume_nodrive_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GDrive *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 = g_volume_monitor_get_volumes (model->priv->volume_monitor); + + g_list_foreach (volumes, (GFunc)process_volume_nodrive_cb, model); + g_list_free_full (volumes, g_object_unref); +} + +static void +process_mount_novolume_cb (GMount *mount, + GeditFileBookmarksStore *model) +{ + GVolume *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 = g_volume_monitor_get_mounts (model->priv->volume_monitor); + + g_list_foreach (mounts, (GFunc)process_mount_novolume_cb, model); + g_list_free_full (mounts, g_object_unref); +} + +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 != NULL; ++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 = g_file_new_for_uri (uri); + guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK; + GtkTreeIter iter; + gboolean ret; + + 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 gchar * +get_bookmarks_file (void) +{ + return g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL); +} + +static gchar * +get_legacy_bookmarks_file (void) +{ + return g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL); +} + +static gboolean +parse_bookmarks_file (GeditFileBookmarksStore *model, + const gchar *bookmarks, + gboolean *added) +{ + GError *error = NULL; + gchar *contents; + gchar **lines; + gchar **line; + + if (!g_file_get_contents (bookmarks, &contents, NULL, &error)) + { + /* The bookmarks file doesn't exist (which is perfectly fine) */ + g_error_free (error); + + return FALSE; + } + + lines = g_strsplit (contents, "\n", 0); + + for (line = lines; *line; ++line) + { + if (**line) + { + GFile *location; + + 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 */ + location = g_file_new_for_uri (*line); + if (gedit_utils_is_valid_location (location)) + { + *added |= add_bookmark (model, name, *line); + } + g_object_unref (location); + } + } + + g_strfreev (lines); + g_free (contents); + + /* Add a watch */ + if (model->priv->bookmarks_monitor == NULL) + { + GFile *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", + G_CALLBACK (on_bookmarks_file_changed), + model); + } + + return TRUE; +} + +static void +init_bookmarks (GeditFileBookmarksStore *model) +{ + gchar *bookmarks = get_bookmarks_file (); + gboolean added = FALSE; + + if (!parse_bookmarks_file (model, bookmarks, &added)) + { + g_free (bookmarks); + + /* try the old location (gtk <= 3.4) */ + bookmarks = get_legacy_bookmarks_file (); + parse_bookmarks_file (model, bookmarks, &added); + } + + if (added) + { + /* Bookmarks separator */ + add_node (model, NULL, 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 nautilus */ + 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 sep = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR; + guint f1; + guint f2; + gint *flags; + + 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 = 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) && + 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_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; +} + +GFile * +gedit_file_bookmarks_store_get_location (GeditFileBookmarksStore *model, + GtkTreeIter *iter) +{ + GObject *obj; + GFile *file = NULL; + guint flags; + GFile * 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 = (GFile *)g_object_ref (obj); + + g_object_unref (obj); + + if (file) + { + ret = g_file_dup (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; + /* FIXME: shouldn't we also monitor the directory? */ + case G_FILE_MONITOR_EVENT_DELETED: + /* Remove bookmarks */ + remove_bookmarks (model); + g_object_unref (monitor); + model->priv->bookmarks_monitor = NULL; + break; + default: + break; + } +} + +void +_gedit_file_bookmarks_store_register_type (GTypeModule *type_module) +{ + gedit_file_bookmarks_store_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.h b/plugins/filebrowser/gedit-file-bookmarks-store.h new file mode 100644 index 0000000..19de53b --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.h @@ -0,0 +1,91 @@ +/* + * gedit-file-bookmarks-store.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#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_ICON_NAME, + 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; + +GeditFileBookmarksStore *gedit_file_bookmarks_store_new (void); +GFile *gedit_file_bookmarks_store_get_location (GeditFileBookmarksStore *model, + GtkTreeIter *iter); +void gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore *model); + +void _gedit_file_bookmarks_store_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BOOKMARKS_STORE_H */ +/* ex:set 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 100644 index 0000000..c97ffff --- /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 "@basename@" */ + +/*** 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-stage1.c.template b/plugins/filebrowser/gedit-file-browser-enum-types-stage1.c.template new file mode 100644 index 0000000..fd58866 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types-stage1.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 "@basename@" + +/*** 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 100644 index 0000000..c13167a --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types.h.template @@ -0,0 +1,28 @@ +/*** 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 "@basename@" */ + +/*** 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 100644 index 0000000..89e6ae3 --- /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 <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#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 */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-messages.c b/plugins/filebrowser/gedit-file-browser-messages.c new file mode 100644 index 0000000..7f25424 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.c @@ -0,0 +1,1074 @@ +/* + * gedit-file-browser-messages.c + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "gedit-file-browser-messages.h" +#include "gedit-file-browser-store.h" +#include "messages/messages.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) +#define BUS_DISCONNECT(bus, name, data) gedit_message_bus_disconnect_by_func(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data) + +typedef struct +{ + GeditWindow *window; + GeditMessage *message; +} MessageCacheData; + +typedef struct +{ + guint row_inserted_id; + guint before_row_deleted_id; + guint root_changed_id; + guint begin_loading_id; + guint end_loading_id; + + 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); + + 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); + + 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); + + g_hash_table_destroy (data->row_tracking); + g_hash_table_destroy (data->filters); + + 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; + GFile *location; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + location = gedit_file_browser_store_get_virtual_root (store); + + if (location) + { + g_object_set (message, "location", location, NULL); + g_object_unref (location); + } +} + +static void +message_set_root_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GFile *root; + GFile *virtual = NULL; + + g_object_get (message, "location", &root, NULL); + + if (!root) + { + return; + } + + g_object_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); + } +} + +static void +message_set_emblem_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *id = NULL; + gchar *emblem = NULL; + GtkTreePath *path; + GeditFileBrowserStore *store; + + g_object_get (message, "id", &id, "emblem", &emblem, NULL); + + if (!id) + { + g_free (id); + g_free (emblem); + + return; + } + + path = track_row_lookup (data, id); + + if (path != NULL) + { + GtkTreeIter iter; + GValue value = G_VALUE_INIT; + GdkPixbuf *pixbuf = NULL; + + if (emblem != NULL) + { + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + emblem, + 10, + GTK_ICON_LOOKUP_FORCE_SIZE, + NULL); + } + + 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); + } + + if (pixbuf) + { + g_object_unref (pixbuf); + } + } + + g_free (id); + g_free (emblem); +} + +static void +message_set_markup_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *id = NULL; + gchar *markup = NULL; + GtkTreePath *path; + GeditFileBrowserStore *store; + + g_object_get (message, "id", &id, "markup", &markup, NULL); + + if (!id) + { + g_free (id); + g_free (markup); + + return; + } + + path = track_row_lookup (data, id); + + if (path != NULL) + { + GtkTreeIter iter; + GValue value = G_VALUE_INIT; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path)) + { + if (markup == NULL) + { + gchar *name; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name, + -1); + markup = g_markup_escape_text (name, -1); + + g_free (name); + } + + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, markup); + + gedit_file_browser_store_set_value (store, + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, + &value); + + g_value_unset (&value); + } + + gtk_tree_path_free (path); + } + + g_free (id); + g_free (markup); +} + +static gchar * +item_id (const gchar *path, + GFile *location) +{ + gchar *uri; + gchar *id; + + uri = g_file_get_uri (location); + id = g_strconcat (path, "::", uri, NULL); + g_free (uri); + + return id; +} + +static gchar * +track_row (WindowData *data, + GeditFileBrowserStore *store, + GtkTreePath *path, + GFile *location) +{ + GtkTreeRowReference *ref; + gchar *id; + gchar *pathstr; + + pathstr = gtk_tree_path_to_string (path); + id = item_id (pathstr, location); + + 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 *name; + GFile *location; + guint flags = 0; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (location) + { + gchar *track_id; + + if (path && gtk_tree_path_get_depth (path) != 0) + { + track_id = track_row (data, store, path, location); + } + else + { + track_id = NULL; + } + + g_object_set (message, + "id", track_id, + "location", location, + NULL); + + if (gedit_message_has (message, "name")) + { + g_object_set (message, + "name", name, + NULL); + } + + if (gedit_message_has (message, "is_directory")) + { + g_object_set (message, + "is_directory", + FILE_IS_DIR (flags), + NULL); + } + + g_free (track_id); + g_object_unref (location); + } + + g_free (name); +} + +static gboolean +custom_message_filter_func (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GtkTreeIter *iter, + FilterData *data) +{ + WindowData *wdata = get_window_data (data->window); + GFile *location; + guint flags = 0; + gboolean filter = FALSE; + GtkTreePath *path; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!location || FILE_IS_DUMMY (flags)) + 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); + + g_object_set (data->message, "filter", filter, NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + g_object_get (data->message, "filter", &filter, NULL); + + g_object_unref (location); + + return !filter; +} + +static void +message_add_filter_cb (GeditMessageBus *bus, + GeditMessage *message, + GeditWindow *window) +{ + const gchar *object_path = NULL; + const gchar *method = NULL; + gulong id; + GeditMessage *cbmessage; + FilterData *filter_data; + WindowData *data; + GType message_type; + + data = get_window_data (window); + + object_path = gedit_message_get_object_path (message); + method = gedit_message_get_method (message); + + message_type = gedit_message_bus_lookup (bus, object_path, method); + + if (message_type == G_TYPE_INVALID) + { + return; + } + + /* Check if the message type has the correct arguments */ + if (!gedit_message_type_check (message_type, "id", G_TYPE_STRING) || + !gedit_message_type_check (message_type, "location", G_TYPE_FILE) || + !gedit_message_type_check (message_type, "is-directory", G_TYPE_BOOLEAN) || + !gedit_message_type_check (message_type, "filter", G_TYPE_BOOLEAN)) + { + return; + } + + cbmessage = g_object_new (message_type, + "object-path", object_path, + "method", method, + "id", NULL, + "location", 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; + + g_object_get (message, "id", &id, NULL); + + if (!id) + return; + + gedit_file_browser_widget_remove_filter (data->widget, id); +} + +static void +message_extend_context_menu_cb (GeditMessageBus *bus, + GeditMessage *message, + GeditWindow *window) +{ + WindowData *data; + GeditMenuExtension *ext; + + data = get_window_data (window); + + ext = gedit_file_browser_widget_extend_context_menu (data->widget); + + g_object_set (message, "extension", ext, NULL); + g_object_unref (ext); +} + +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; + + g_object_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; + + g_object_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_get_view_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserView *view; + view = gedit_file_browser_widget_get_browser_view (data->widget); + + g_object_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, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT, + MESSAGE_OBJECT_PATH, + "get_root"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT, + MESSAGE_OBJECT_PATH, + "set_root"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM, + MESSAGE_OBJECT_PATH, + "set_emblem"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP, + MESSAGE_OBJECT_PATH, + "set_markup"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER, + MESSAGE_OBJECT_PATH, + "add_filter"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID, + MESSAGE_OBJECT_PATH, + "remove_filter"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU, + MESSAGE_OBJECT_PATH, + "extend_context_menu"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "up"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "history_back"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "history_forward"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "refresh"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION, + MESSAGE_OBJECT_PATH, + "set_show_hidden"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION, + MESSAGE_OBJECT_PATH, + "set_show_binary"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "show_bookmarks"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "show_files"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW, + MESSAGE_OBJECT_PATH, + "get_view"); + + BUS_CONNECT (bus, get_root, data); + BUS_CONNECT (bus, set_root, data); + BUS_CONNECT (bus, set_emblem, data); + BUS_CONNECT (bus, set_markup, data); + BUS_CONNECT (bus, add_filter, window); + BUS_CONNECT (bus, remove_filter, data); + BUS_CONNECT (bus, extend_context_menu, window); + + 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) +{ + guint flags = 0; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + 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); + } +} + +static void +store_before_row_deleted (GeditFileBrowserStore *store, + GtkTreePath *path, + MessageCacheData *data) +{ + GtkTreeIter iter; + 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_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags)) + { + WindowData *wdata = get_window_data (data->window); + gchar *id; + + set_item_message (wdata, &iter, path, data->message); + + /* Must get the ID before the plugin can modify it */ + g_object_get (data->message, "id", &id, NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + + g_hash_table_remove (wdata->row_tracking, id); + g_free (id); + } +} + +static void +store_virtual_root_changed (GeditFileBrowserStore *store, + GParamSpec *spec, + MessageCacheData *data) +{ + WindowData *wdata = get_window_data (data->window); + GFile *vroot; + + vroot = gedit_file_browser_store_get_virtual_root (store); + + if (!vroot) + { + return; + } + + g_object_set (data->message, + "location", vroot, + NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + + g_object_unref (vroot); +} + +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; + + GeditMessage *message; + WindowData *data; + + /* Register signals */ + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "root_changed"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "begin_loading"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "end_loading"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "inserted"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "deleted"); + + store = gedit_file_browser_widget_get_browser_store (widget); + + message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "inserted", + 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 = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "deleted", + NULL); + + data->before_row_deleted_id = + g_signal_connect_data (store, + "before-row-deleted", + G_CALLBACK (store_before_row_deleted), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "root_changed", + 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 = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "begin_loading", + 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 = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "end_loading", + 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, + const gchar *object_path, + const gchar *method, + GeditWindow *window) +{ + gchar *identifier; + FilterData *data; + WindowData *wdata; + + wdata = get_window_data (window); + + identifier = gedit_message_type_identifier (object_path, method); + + 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->before_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); + WindowData *data = get_window_data (window); + + cleanup_signals (window); + + BUS_DISCONNECT (bus, get_root, data); + BUS_DISCONNECT (bus, set_root, data); + BUS_DISCONNECT (bus, set_emblem, data); + BUS_DISCONNECT (bus, set_markup, data); + BUS_DISCONNECT (bus, add_filter, window); + BUS_DISCONNECT (bus, remove_filter, data); + + BUS_DISCONNECT (bus, up, data); + BUS_DISCONNECT (bus, history_back, data); + BUS_DISCONNECT (bus, history_forward, data); + + BUS_DISCONNECT (bus, refresh, data); + + BUS_DISCONNECT (bus, set_show_hidden, data); + BUS_DISCONNECT (bus, set_show_binary, data); + + BUS_DISCONNECT (bus, show_bookmarks, data); + BUS_DISCONNECT (bus, show_files, data); + + BUS_DISCONNECT (bus, get_view, data); + + gedit_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH); + + window_data_free (window); +} +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-messages.h b/plugins/filebrowser/gedit-file-browser-messages.h new file mode 100644 index 0000000..9fcb316 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.h @@ -0,0 +1,33 @@ +/* + * gedit-file-browser-messages.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2008 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#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:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-plugin.c b/plugins/filebrowser/gedit-file-browser-plugin.c new file mode 100644 index 0000000..2e8e13e --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.c @@ -0,0 +1,972 @@ +/* + * gedit-file-browser-plugin.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <glib/gi18n-lib.h> +#include <gmodule.h> +#include <gedit/gedit-app.h> +#include <gedit/gedit-commands.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-window.h> +#include <gedit/gedit-window-activatable.h> +#include <gedit/gedit-utils.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 FILEBROWSER_BASE_SETTINGS "org.gnome.gedit.plugins.filebrowser" +#define FILEBROWSER_TREE_VIEW "tree-view" +#define FILEBROWSER_ROOT "root" +#define FILEBROWSER_VIRTUAL_ROOT "virtual-root" +#define FILEBROWSER_ENABLE_REMOTE "enable-remote" +#define FILEBROWSER_OPEN_AT_FIRST_DOC "open-at-first-doc" +#define FILEBROWSER_FILTER_MODE "filter-mode" +#define FILEBROWSER_FILTER_PATTERN "filter-pattern" +#define FILEBROWSER_BINARY_PATTERNS "binary-patterns" + +#define NAUTILUS_BASE_SETTINGS "org.gnome.nautilus.preferences" +#define NAUTILUS_FALLBACK_SETTINGS "org.gnome.gedit.plugins.filebrowser.nautilus" +#define NAUTILUS_CLICK_POLICY_KEY "click-policy" +#define NAUTILUS_CONFIRM_TRASH_KEY "confirm-trash" + +#define TERMINAL_BASE_SETTINGS "org.gnome.desktop.default-applications.terminal" +#define TERMINAL_EXEC_KEY "exec" + +struct _GeditFileBrowserPluginPrivate +{ + GSettings *settings; + GSettings *nautilus_settings; + GSettings *terminal_settings; + + GeditWindow *window; + + GeditFileBrowserWidget *tree_widget; + gboolean auto_root; + gulong end_loading_handle; + gboolean confirm_trash; + + guint click_policy_handle; + guint confirm_trash_handle; +}; + +enum +{ + PROP_0, + PROP_WINDOW +}; + +static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface); + +static void on_location_activated_cb (GeditFileBrowserWidget *widget, + GFile *location, + GeditWindow *window); +static void on_error_cb (GeditFileBrowserWidget *widget, + guint code, + gchar const *message, + GeditFileBrowserPlugin *plugin); +static void on_model_set_cb (GeditFileBrowserView *widget, + GParamSpec *param, + GeditFileBrowserPlugin *plugin); +static void on_virtual_root_changed_cb (GeditFileBrowserStore *model, + GParamSpec *param, + GeditFileBrowserPlugin *plugin); +static void on_rename_cb (GeditFileBrowserStore *model, + GFile *oldfile, + GFile *newfile, + GeditWindow *window); +static void on_tab_added_cb (GeditWindow *window, + GeditTab *tab, + GeditFileBrowserPlugin *plugin); +static gboolean on_confirm_delete_cb (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GList *rows, + GeditFileBrowserPlugin *plugin); +static gboolean on_confirm_no_trash_cb (GeditFileBrowserWidget *widget, + GList *files, + GeditWindow *window); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserPlugin, + gedit_file_browser_plugin, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserPlugin) + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE, + gedit_window_activatable_iface_init) \ + \ + gedit_file_browser_enum_and_flag_register_type (type_module); \ + _gedit_file_bookmarks_store_register_type (type_module); \ + _gedit_file_browser_store_register_type (type_module); \ + _gedit_file_browser_view_register_type (type_module); \ + _gedit_file_browser_widget_register_type (type_module); \ +) + +static GSettings * +settings_try_new (const gchar *schema_id) +{ + GSettings *settings = NULL; + GSettingsSchemaSource *source; + GSettingsSchema *schema; + + source = g_settings_schema_source_get_default (); + + schema = g_settings_schema_source_lookup (source, schema_id, TRUE); + + if (schema != NULL) + { + settings = g_settings_new_full (schema, NULL, NULL); + g_settings_schema_unref (schema); + } + + return settings; +} + +static void +gedit_file_browser_plugin_init (GeditFileBrowserPlugin *plugin) +{ + plugin->priv = gedit_file_browser_plugin_get_instance_private (plugin); + + plugin->priv->settings = g_settings_new (FILEBROWSER_BASE_SETTINGS); + plugin->priv->terminal_settings = g_settings_new (TERMINAL_BASE_SETTINGS); + plugin->priv->nautilus_settings = settings_try_new (NAUTILUS_BASE_SETTINGS); + + if (plugin->priv->nautilus_settings == NULL) + { + plugin->priv->nautilus_settings = g_settings_new (NAUTILUS_FALLBACK_SETTINGS); + } +} + +static void +gedit_file_browser_plugin_dispose (GObject *object) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + g_clear_object (&plugin->priv->settings); + g_clear_object (&plugin->priv->nautilus_settings); + g_clear_object (&plugin->priv->terminal_settings); + g_clear_object (&plugin->priv->window); + + G_OBJECT_CLASS (gedit_file_browser_plugin_parent_class)->dispose (object); +} + +static void +gedit_file_browser_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, plugin->priv->window); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_end_loading_cb (GeditFileBrowserStore *store, + GtkTreeIter *iter, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + + /* Disconnect the signal */ + g_signal_handler_disconnect (store, priv->end_loading_handle); + priv->end_loading_handle = 0; + priv->auto_root = FALSE; +} + +static void +prepare_auto_root (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GeditFileBrowserStore *store; + + priv->auto_root = TRUE; + + store = gedit_file_browser_widget_get_browser_store (priv->tree_widget); + + if (priv->end_loading_handle != 0) + { + g_signal_handler_disconnect (store, priv->end_loading_handle); + priv->end_loading_handle = 0; + } + + priv->end_loading_handle = g_signal_connect (store, + "end-loading", + G_CALLBACK (on_end_loading_cb), + plugin); +} + +static void +restore_default_location (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *root; + gchar *virtual_root; + gboolean bookmarks; + gboolean remote; + + bookmarks = !g_settings_get_boolean (priv->settings, + FILEBROWSER_TREE_VIEW); + + if (bookmarks) + { + gedit_file_browser_widget_show_bookmarks (priv->tree_widget); + return; + } + + root = g_settings_get_string (priv->settings, + FILEBROWSER_ROOT); + virtual_root = g_settings_get_string (priv->settings, + FILEBROWSER_VIRTUAL_ROOT); + + remote = g_settings_get_boolean (priv->settings, + FILEBROWSER_ENABLE_REMOTE); + + if (root != NULL && *root != '\0') + { + GFile *rootfile; + GFile *vrootfile; + + rootfile = g_file_new_for_uri (root); + vrootfile = g_file_new_for_uri (virtual_root); + + if (remote || g_file_is_native (rootfile)) + { + if (virtual_root != NULL && *virtual_root != '\0') + { + prepare_auto_root (plugin); + gedit_file_browser_widget_set_root_and_virtual_root (priv->tree_widget, + rootfile, + vrootfile); + } + else + { + prepare_auto_root (plugin); + gedit_file_browser_widget_set_root (priv->tree_widget, + rootfile, + TRUE); + } + } + + g_object_unref (rootfile); + g_object_unref (vrootfile); + } + + g_free (root); + g_free (virtual_root); +} + +static void +on_click_policy_changed (GSettings *settings, + const gchar *key, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GeditFileBrowserViewClickPolicy policy; + GeditFileBrowserView *view; + + policy = g_settings_get_enum (settings, key); + + view = gedit_file_browser_widget_get_browser_view (priv->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); +} + +static void +on_confirm_trash_changed (GSettings *settings, + const gchar *key, + GeditFileBrowserPlugin *plugin) +{ + plugin->priv->confirm_trash = g_settings_get_boolean (settings, key); +} + +static void +install_nautilus_prefs (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gboolean prefb; + GeditFileBrowserViewClickPolicy policy; + GeditFileBrowserView *view; + + /* Get click_policy */ + policy = g_settings_get_enum (priv->nautilus_settings, + NAUTILUS_CLICK_POLICY_KEY); + + view = gedit_file_browser_widget_get_browser_view (priv->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); + + priv->click_policy_handle = + g_signal_connect (priv->nautilus_settings, + "changed::" NAUTILUS_CLICK_POLICY_KEY, + G_CALLBACK (on_click_policy_changed), + plugin); + + /* Get confirm_trash */ + prefb = g_settings_get_boolean (priv->nautilus_settings, + NAUTILUS_CONFIRM_TRASH_KEY); + + priv->confirm_trash = prefb; + + priv->confirm_trash_handle = + g_signal_connect (priv->nautilus_settings, + "changed::" NAUTILUS_CONFIRM_TRASH_KEY, + G_CALLBACK (on_confirm_trash_changed), + plugin); +} + +static void +set_root_from_doc (GeditFileBrowserPlugin *plugin, + GeditDocument *doc) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GtkSourceFile *file; + GFile *location; + GFile *parent; + + if (doc == NULL) + { + return; + } + + file = gedit_document_get_file (doc); + location = gtk_source_file_get_location (file); + if (location == NULL) + { + return; + } + + parent = g_file_get_parent (location); + + if (parent != NULL) + { + gedit_file_browser_widget_set_root (priv->tree_widget, + parent, + TRUE); + + g_object_unref (parent); + } +} + +static void +set_active_root (GeditFileBrowserWidget *widget, + GeditFileBrowserPlugin *plugin) +{ + set_root_from_doc (plugin, + gedit_window_get_active_document (plugin->priv->window)); +} + +static gchar * +get_terminal (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *terminal; + + terminal = g_settings_get_string (priv->terminal_settings, + TERMINAL_EXEC_KEY); + + 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 +open_in_terminal (GeditFileBrowserWidget *widget, + GFile *location, + GeditFileBrowserPlugin *plugin) +{ + if (location) + { + gchar *terminal; + gchar *local; + gchar *argv[2]; + + terminal = get_terminal (plugin); + local = g_file_get_path (location); + + 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 (local); + } +} + +static void +gedit_file_browser_plugin_update_state (GeditWindowActivatable *activatable) +{ + GeditFileBrowserPluginPrivate *priv = GEDIT_FILE_BROWSER_PLUGIN (activatable)->priv; + GeditDocument *doc; + + doc = gedit_window_get_active_document (priv->window); + gedit_file_browser_widget_set_active_root_enabled (priv->tree_widget, + doc != NULL && !gedit_document_is_untitled (doc)); +} + +static void +gedit_file_browser_plugin_activate (GeditWindowActivatable *activatable) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (activatable); + GeditFileBrowserPluginPrivate *priv; + GtkWidget *panel; + GeditFileBrowserStore *store; + + priv = plugin->priv; + + priv->tree_widget = GEDIT_FILE_BROWSER_WIDGET (gedit_file_browser_widget_new ()); + + g_signal_connect (priv->tree_widget, + "location-activated", + G_CALLBACK (on_location_activated_cb), priv->window); + + g_signal_connect (priv->tree_widget, + "error", G_CALLBACK (on_error_cb), plugin); + + g_signal_connect (priv->tree_widget, + "confirm-delete", + G_CALLBACK (on_confirm_delete_cb), + plugin); + + g_signal_connect (priv->tree_widget, + "confirm-no-trash", + G_CALLBACK (on_confirm_no_trash_cb), + priv->window); + + g_signal_connect (priv->tree_widget, + "open-in-terminal", + G_CALLBACK (open_in_terminal), + plugin); + + g_signal_connect (priv->tree_widget, + "set-active-root", + G_CALLBACK (set_active_root), + plugin); + + g_settings_bind (priv->settings, + FILEBROWSER_FILTER_PATTERN, + priv->tree_widget, + FILEBROWSER_FILTER_PATTERN, + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET); + + panel = gedit_window_get_side_panel (priv->window); + + gtk_stack_add_titled (GTK_STACK (panel), + GTK_WIDGET (priv->tree_widget), + "GeditFileBrowserPanel", + _("File Browser")); + + gtk_widget_show (GTK_WIDGET (priv->tree_widget)); + + /* Install nautilus preferences */ + install_nautilus_prefs (plugin); + + /* Connect signals to store the last visited location */ + g_signal_connect (gedit_file_browser_widget_get_browser_view (priv->tree_widget), + "notify::model", + G_CALLBACK (on_model_set_cb), + plugin); + + store = gedit_file_browser_widget_get_browser_store (priv->tree_widget); + + g_settings_bind (priv->settings, + FILEBROWSER_FILTER_MODE, + store, + FILEBROWSER_FILTER_MODE, + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET); + + g_settings_bind (priv->settings, + FILEBROWSER_BINARY_PATTERNS, + store, + FILEBROWSER_BINARY_PATTERNS, + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET); + + g_signal_connect (store, + "notify::virtual-root", + G_CALLBACK (on_virtual_root_changed_cb), + plugin); + + g_signal_connect (store, + "rename", + G_CALLBACK (on_rename_cb), + priv->window); + + g_signal_connect (priv->window, + "tab-added", + G_CALLBACK (on_tab_added_cb), + plugin); + + /* Register messages on the bus */ + gedit_file_browser_messages_register (priv->window, priv->tree_widget); + + gedit_file_browser_plugin_update_state (activatable); +} + +static void +gedit_file_browser_plugin_deactivate (GeditWindowActivatable *activatable) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (activatable); + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GtkWidget *panel; + + + /* Unregister messages from the bus */ + gedit_file_browser_messages_unregister (priv->window); + + /* Disconnect signals */ + g_signal_handlers_disconnect_by_func (priv->window, + G_CALLBACK (on_tab_added_cb), + plugin); + + if (priv->click_policy_handle) + { + g_signal_handler_disconnect (priv->nautilus_settings, + priv->click_policy_handle); + } + + if (priv->confirm_trash_handle) + { + g_signal_handler_disconnect (priv->nautilus_settings, + priv->confirm_trash_handle); + } + + panel = gedit_window_get_side_panel (priv->window); + gtk_container_remove (GTK_CONTAINER (panel), GTK_WIDGET (priv->tree_widget)); +} + +static void +gedit_file_browser_plugin_class_init (GeditFileBrowserPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_file_browser_plugin_dispose; + object_class->set_property = gedit_file_browser_plugin_set_property; + object_class->get_property = gedit_file_browser_plugin_get_property; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); +} + +static void +gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface) +{ + iface->activate = gedit_file_browser_plugin_activate; + iface->deactivate = gedit_file_browser_plugin_deactivate; + iface->update_state = gedit_file_browser_plugin_update_state; +} + +static void +gedit_file_browser_plugin_class_finalize (GeditFileBrowserPluginClass *klass) +{ +} + +/* Callbacks */ +static void +on_location_activated_cb (GeditFileBrowserWidget *tree_widget, + GFile *location, + GeditWindow *window) +{ + gedit_commands_load_location (window, location, NULL, 0, 0); +} + +static void +on_error_cb (GeditFileBrowserWidget *tree_widget, + guint code, + gchar const *message, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *title; + GtkWidget *dlg; + + /* Do not show the error when the root has been set automatically */ + if (priv->auto_root && (code == GEDIT_FILE_BROWSER_ERROR_SET_ROOT || + code == GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY)) + { + /* Show bookmarks */ + gedit_file_browser_widget_show_bookmarks (priv->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 (priv->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 *param, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (priv->tree_widget))); + + if (model == NULL) + return; + + g_settings_set_boolean (priv->settings, + FILEBROWSER_TREE_VIEW, + GEDIT_IS_FILE_BROWSER_STORE (model)); +} + +static void +on_rename_cb (GeditFileBrowserStore *store, + GFile *oldfile, + GFile *newfile, + GeditWindow *window) +{ + GList *documents; + GList *item; + + /* Find all documents and set its uri to newuri where it matches olduri */ + documents = gedit_app_get_documents (GEDIT_APP (g_application_get_default ())); + + for (item = documents; item; item = item->next) + { + GeditDocument *doc; + GtkSourceFile *source_file; + GFile *docfile; + + doc = GEDIT_DOCUMENT (item->data); + source_file = gedit_document_get_file (doc); + docfile = gtk_source_file_get_location (source_file); + + if (docfile == NULL) + { + continue; + } + + if (g_file_equal (docfile, oldfile)) + { + gtk_source_file_set_location (source_file, newfile); + } + else + { + gchar *relative; + + relative = g_file_get_relative_path (oldfile, docfile); + + if (relative != NULL) + { + /* Relative now contains the part in docfile without + the prefix oldfile */ + + docfile = g_file_get_child (newfile, relative); + + gtk_source_file_set_location (source_file, docfile); + + g_object_unref (docfile); + } + + g_free (relative); + } + } + + g_list_free (documents); +} + +static void +on_virtual_root_changed_cb (GeditFileBrowserStore *store, + GParamSpec *param, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GFile *root; + GFile *virtual_root; + gchar *uri_root = NULL; + + root = gedit_file_browser_store_get_root (store); + + if (!root) + { + return; + } + else + { + uri_root = g_file_get_uri (root); + g_object_unref (root); + } + + g_settings_set_string (priv->settings, + FILEBROWSER_ROOT, + uri_root); + + virtual_root = gedit_file_browser_store_get_virtual_root (store); + + if (!virtual_root) + { + /* Set virtual to same as root then */ + g_settings_set_string (priv->settings, + FILEBROWSER_VIRTUAL_ROOT, + uri_root); + } + else + { + gchar *uri_vroot; + + uri_vroot = g_file_get_uri (virtual_root); + + g_settings_set_string (priv->settings, + FILEBROWSER_VIRTUAL_ROOT, + uri_vroot); + g_free (uri_vroot); + g_object_unref (virtual_root); + } + + g_signal_handlers_disconnect_by_func (priv->window, + G_CALLBACK (on_tab_added_cb), + plugin); + g_free (uri_root); +} + +static void +on_tab_added_cb (GeditWindow *window, + GeditTab *tab, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gboolean open; + gboolean load_default = TRUE; + + open = g_settings_get_boolean (priv->settings, + FILEBROWSER_OPEN_AT_FIRST_DOC); + + if (open) + { + GeditDocument *doc; + GtkSourceFile *file; + GFile *location; + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + location = gtk_source_file_get_location (file); + + if (location != NULL) + { + if (g_file_has_uri_scheme (location, "file")) + { + prepare_auto_root (plugin); + set_root_from_doc (plugin, doc); + load_default = FALSE; + } + } + } + + if (load_default) + restore_default_location (plugin); + + /* Disconnect this signal, it's only called once */ + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + plugin); +} + +static gchar * +get_filename_from_path (GtkTreeModel *model, + GtkTreePath *path) +{ + GtkTreeIter iter; + GFile *location; + gchar *ret = NULL; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + { + return NULL; + } + + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (location) + { + ret = gedit_file_browser_utils_file_basename (location); + g_object_unref (location); + } + + return ret; +} + +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, + _("_Delete")); + g_free (secondary); + + return result; +} + +static gboolean +on_confirm_delete_cb (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GList *paths, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *normal; + gchar *message; + gchar *secondary; + gboolean result; + + if (!priv->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 (priv->window, + GTK_MESSAGE_QUESTION, + message, + secondary, + _("_Delete")); + + g_free (message); + + return result; +} + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_file_browser_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_WINDOW_ACTIVATABLE, + GEDIT_TYPE_FILE_BROWSER_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-plugin.h b/plugins/filebrowser/gedit-file-browser-plugin.h new file mode 100644 index 0000000..27fe706 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.h @@ -0,0 +1,70 @@ +/* + * gedit-file-browser-plugin.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_FILE_BROWSER_PLUGIN_H +#define GEDIT_FILE_BROWSER_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> + +G_BEGIN_DECLS +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_FILE_BROWSER_PLUGIN (gedit_file_browser_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 +{ + GObject parent_instance; + + /* < private > */ + GeditFileBrowserPluginPrivate *priv; +}; + +struct _GeditFileBrowserPluginClass +{ + GObjectClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_file_browser_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_PLUGIN_H */ + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-store.c b/plugins/filebrowser/gedit-file-browser-store.c new file mode 100644 index 0000000..6a5ccaf --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.c @@ -0,0 +1,3672 @@ +/* + * gedit-file-browser-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include <gedit/gedit-utils.h> + +#include "gedit-file-browser-store.h" +#include "gedit-file-browser-enum-types.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-utils.h" + +#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; + GFile *virtual_root; + GMountOperation *operation; + GCancellable *cancellable; +} MountInfo; + +struct _FileBrowserNode +{ + GFile *file; + guint flags; + gchar *icon_name; + gchar *name; + gchar *markup; + + GdkPixbuf *icon; + GdkPixbuf *emblem; + + FileBrowserNode *parent; + gint pos; + gboolean inserted; +}; + +struct _FileBrowserNodeDir +{ + FileBrowserNode node; + GSList *children; + + 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; + + gchar **binary_patterns; + GPtrArray *binary_pattern_specs; + + 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); + +static void delete_files (AsyncData *data); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserStore, gedit_file_browser_store, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserStore) + G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_MODEL, + gedit_file_browser_store_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_DRAG_SOURCE, + gedit_file_browser_store_drag_source_init)) + +/* Properties */ +enum { + PROP_0, + + PROP_ROOT, + PROP_VIRTUAL_ROOT, + PROP_FILTER_MODE, + PROP_BINARY_PATTERNS +}; + +/* Signals */ +enum +{ + BEGIN_LOADING, + END_LOADING, + ERROR, + NO_TRASH, + RENAME, + BEGIN_REFRESH, + END_REFRESH, + UNLOAD, + BEFORE_ROW_DELETED, + 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); + + /* Free all the nodes */ + file_browser_node_free (obj, obj->priv->root); + + if (obj->priv->binary_patterns != NULL) + { + g_strfreev (obj->priv->binary_patterns); + g_ptr_array_unref (obj->priv->binary_pattern_specs); + } + + /* Cancel any asynchronous operations */ + for (GSList *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) +{ + if (node == NULL) + g_value_set_object (value, NULL); + else + g_value_set_object (value, node->file); +} + +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; + case PROP_BINARY_PATTERNS: + g_value_set_boxed (value, obj->priv->binary_patterns); + 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_ROOT: + gedit_file_browser_store_set_root (obj, G_FILE (g_value_get_object (value))); + break; + case PROP_FILTER_MODE: + gedit_file_browser_store_set_filter_mode (obj, g_value_get_flags (value)); + break; + case PROP_BINARY_PATTERNS: + gedit_file_browser_store_set_binary_patterns (obj, g_value_get_boxed (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_object ("root", + "Root", + "The root location", + G_TYPE_FILE, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_VIRTUAL_ROOT, + g_param_spec_object ("virtual-root", + "Virtual Root", + "The virtual root location", + G_TYPE_FILE, + 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)); + + g_object_class_install_property (object_class, PROP_BINARY_PATTERNS, + g_param_spec_boxed ("binary-patterns", + "Binary Patterns", + "The binary patterns", + G_TYPE_STRV, + 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, NULL, + 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, NULL, + 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, NULL, + 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, NULL, + 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, NULL, + G_TYPE_NONE, 2, G_TYPE_FILE, G_TYPE_FILE); + 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, NULL, + 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, NULL, + 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, NULL, + G_TYPE_NONE, 1, G_TYPE_FILE); + model_signals[BEFORE_ROW_DELETED] = + g_signal_new ("before-row-deleted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, before_row_deleted), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_PATH | G_SIGNAL_TYPE_STATIC_SCOPE); +} + +static void +gedit_file_browser_store_class_finalize (GeditFileBrowserStoreClass *klass) +{ +} + +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_instance_private (obj); + + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION] = G_TYPE_FILE; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP] = 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_ICON_NAME] = 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_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) +{ + GeditFileBrowserStore *model; + FileBrowserNode *node; + gint *indices, depth; + + 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 (guint i = 0; i < depth; ++i) + { + GSList *item; + gint num = 0; + + if (node == NULL) + return FALSE; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + FileBrowserNode *child = (FileBrowserNode *)(item->data); + + if (model_node_inserted (model, child)) + { + if (num == indices[i]) + 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 = gtk_tree_path_new (); + gint num = 0; + + while (node != model->priv->virtual_root) + { + if (node->parent == NULL) + { + gtk_tree_path_free (path); + return NULL; + } + + num = 0; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node->parent)->children; item; item = item->next) + { + FileBrowserNode *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_LOCATION: + set_gvalue_from_node (value, node); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP: + g_value_set_string (value, node->markup); + 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_ICON_NAME: + g_value_set_string (value, node->icon_name); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_NAME: + g_value_set_string (value, node->name); + 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 *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 (GSList *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; + + 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 (GSList *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) +{ + if (!NODE_IS_DIR (node)) + return FALSE; + + for (GSList *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; + 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 (GSList *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; + 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 (GSList *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; + GFile *location; + 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_LOCATION, &location, + -1); + + g_assert (location); + + uris[0] = g_file_get_uri (location); + ret = gtk_selection_data_set_uris (selection_data, uris); + + g_free (uris[0]); + g_object_unref (location); + + 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; + return; + } + + if (FILTER_BINARY (model->priv->filter_mode) && !NODE_IS_DIR (node)) + { + if (!NODE_IS_TEXT (node)) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + return; + } + else if (model->priv->binary_patterns != NULL) + { + gssize name_length = strlen (node->name); + gchar *name_reversed = g_utf8_strreverse (node->name, name_length); + + for (guint i = 0; i < model->priv->binary_pattern_specs->len; ++i) + { + GPatternSpec *spec = g_ptr_array_index (model->priv->binary_pattern_specs, i); + + if (g_pattern_match (spec, name_length, node->name, name_reversed)) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + g_free (name_reversed); + return; + } + } + + g_free (name_reversed); + } + } + + 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 = g_utf8_collate_key_for_filename (node1->name, -1); + gchar *k2 = g_utf8_collate_key_for_filename (node2->name, -1); + gint result = strcmp (k1, k2); + + g_free (k1); + g_free (k2); + + return result; + } +} + +static gint +model_sort_default (FileBrowserNode *node1, + FileBrowserNode *node2) +{ + gint f1 = NODE_IS_DUMMY (node1); + gint 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; + + return collate_nodes (node1, node2); +} + +static void +model_resort_node (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + FileBrowserNodeDir *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 + { + GtkTreeIter iter; + GtkTreePath *path; + gint *neworder; + gint pos = 0; + + /* Store current positions */ + for (GSList *item = dir->children; item; item = item->next) + { + FileBrowserNode *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 (GSList *item = dir->children; item; item = item->next) + { + FileBrowserNode *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, + FileBrowserNode *node, + const GtkTreePath *path) +{ + gboolean hidden; + GtkTreePath *copy; + + /* We should always be called when the row is still inserted */ + g_return_if_fail (node->inserted == TRUE || NODE_IS_DUMMY (node)); + + hidden = FILE_IS_HIDDEN (node->flags); + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + /* Create temporary copies of the path as the signals may alter it */ + + copy = gtk_tree_path_copy (path); + g_signal_emit (model, model_signals[BEFORE_ROW_DELETED], 0, copy); + gtk_tree_path_free (copy); + + node->inserted = FALSE; + + if (hidden) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + copy = gtk_tree_path_copy (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) + { + row_deleted (model, node, *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); + g_free (node->markup); + + if (node->file) + node->name = gedit_file_browser_utils_file_basename (node->file); + else + node->name = NULL; + + if (node->name) + node->markup = g_markup_escape_text (node->name, -1); + else + node->markup = 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) +{ + if (node == NULL || !NODE_IS_DIR (node)) + return; + + for (GSList *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) +{ + if (node == NULL) + return; + + if (NODE_IS_DIR (node)) + { + FileBrowserNodeDir *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 (node->file) + { + g_signal_emit (model, model_signals[UNLOAD], 0, node->file); + g_object_unref (node->file); + } + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) + g_object_unref (node->emblem); + + g_free (node->icon_name); + g_free (node->name); + g_free (node->markup); + + 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; + + 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 (GSList *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) + row_deleted (model, node, path); + + if (free_path) + gtk_tree_path_free (path); + + parent = node->parent; + + /* Remove the node from the parents children list */ + if (free_nodes && 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 = 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) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (model->priv->virtual_root); + + if (dir->children != NULL) + { + FileBrowserNode *dummy = (FileBrowserNode *)(dir->children->data); + + if (NODE_IS_DUMMY (dummy) && model_node_visibility (model, dummy)) + { + path = gtk_tree_path_new_first (); + row_deleted (model, dummy, 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, FALSE); + } + + /* Fallback to the same icon as the file browser */ + if (!icon) + icon = gedit_file_browser_utils_pixbuf_from_theme ("text-x-generic", 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->markup = g_markup_escape_text (dummy->name, -1); + + 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)) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node); + FileBrowserNode *dummy; + GtkTreeIter iter; + GtkTreePath *path; + guint flags; + + 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; + + row_deleted (model, dummy, path); + gtk_tree_path_free (path); + } + } +} + +static void +insert_node_sorted (GeditFileBrowserStore *model, + FileBrowserNode *child, + FileBrowserNode *parent) +{ + FileBrowserNodeDir *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)) + { + GtkTreePath *path = gedit_file_browser_store_get_path_real (model, child); + GtkTreeIter iter; + + iter.user_data = 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) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + GSList *sorted_children = g_slist_sort (children, (GCompareFunc)model->priv->sort_func); + GSList *child = sorted_children; + GSList *prev = NULL; + GSList *l = dir->children; + + 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 gboolean +content_type_is_text (gchar const *content_type) +{ +#ifdef G_OS_WIN32 + gchar *mime; + gboolean ret; +#endif + + if (!content_type || g_content_type_is_unknown (content_type)) + return TRUE; + +#ifndef G_OS_WIN32 + return g_content_type_is_a (content_type, "text/plain"); +#else + if (g_content_type_is_a (content_type, "text")) + return TRUE; + + /* This covers a rare case in which on Windows the PerceivedType is + not set to "text" but the Content Type is set to text/plain */ + mime = g_content_type_get_mime_type (content_type); + ret = g_strcmp0 (mime, "text/plain"); + + g_free (mime); + + return ret; +#endif +} + +static void +file_browser_node_set_from_info (GeditFileBrowserStore *model, + FileBrowserNode *node, + GFileInfo *info, + gboolean isadded) +{ + gchar const *content; + 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; + } + + if (g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info)) + 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_type_is_text (content)) + 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) +{ + for (GSList *item = children; item; item = item->next) + { + FileBrowserNode *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) +{ + GSList *nodes = NULL; + + for (GList *item = files; item; item = item->next) + { + GFileInfo *info = G_FILE_INFO (item->data); + GFileType type = g_file_info_get_file_type (info); + gchar const *name; + GFile *file; + FileBrowserNode *node; + + /* 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)) + { + g_object_unref (info); + continue; + } + + file = g_file_get_child (parent->file, name); + if (!(node = node_list_contains_file (original_children, file))) + { + if (type == 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); + + node->icon_name = g_strdup ("folder-symbolic"); + + model_add_node (model, node, parent); + } + + return node; +} + +static void +on_directory_monitor_event (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + FileBrowserNode *parent) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + FileBrowserNode *node; + + 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_slice_free (AsyncNode, async); +} + +static void +model_iterate_next_files_cb (GFileEnumerator *enumerator, + GAsyncResult *result, + AsyncNode *async) +{ + GError *error = NULL; + GList *files = g_file_enumerator_next_files_finish (enumerator, result, &error); + FileBrowserNodeDir *dir = async->dir; + FileBrowserNode *parent = (FileBrowserNode *)dir; + + if (files == NULL) + { + g_file_enumerator_close (enumerator, NULL, NULL); + g_object_unref (enumerator); + 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.gnome.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); + g_object_unref (enumerator); + 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; + } + + if (!(enumerator = g_file_enumerate_children_finish (file, result, &error))) + { + /* 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); + + dir->cancellable = g_cancellable_new (); + + async = g_slice_new (AsyncNode); + 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 *prev = node; + FileBrowserNode *next = prev->parent; + FileBrowserNode *check; + FileBrowserNodeDir *dir; + GSList *copy; + GtkTreePath *empty = NULL; + + /* 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 (GSList *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 (GSList *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; + 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 (GList *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; + + if (!NODE_IS_DIR (parent)) + return NULL; + + dir = FILE_BROWSER_NODE_DIR (parent); + + for (GSList *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, + GFile *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_location + (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); + + if (mount_info->virtual_root) + g_object_unref (mount_info->virtual_root); + + g_slice_free (MountInfo, mount_info); +} + +static GeditFileBrowserStoreResult +model_mount_root (GeditFileBrowserStore *model, + GFile *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_slice_new (MountInfo); + mount_info->model = model; + mount_info->virtual_root = g_file_dup (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 (GFile *root) +{ + return GEDIT_FILE_BROWSER_STORE (g_object_new (GEDIT_TYPE_FILE_BROWSER_STORE, + "root", root, + NULL)); +} + +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 (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *)(iter->user_data); + + if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP) + { + g_return_if_fail (G_VALUE_HOLDS_STRING (value)); + + data = g_value_dup_string (value); + + if (!data) + data = g_strdup (node->name); + + g_free (node->markup); + node->markup = data; + } + else if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM) + { + g_return_if_fail (G_VALUE_HOLDS_OBJECT (value)); + + data = g_value_get_object (value); + + g_return_if_fail (GDK_IS_PIXBUF (data) || data == NULL); + + 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); + } + else + { + g_return_if_fail (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP || + column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM); + } + + 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_location (GeditFileBrowserStore *model, + GFile *root) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (root == NULL) + { + gchar *uri = g_file_get_uri (root); + + g_warning ("Invalid uri (%s)", uri); + g_free (uri); + 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, root)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + /* Check if uri is the root itself */ + if (g_file_equal (model->priv->root->file, root)) + { + /* 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 (root, model->priv->root->file)) + { + gchar *str = g_file_get_parse_name (model->priv->root->file); + gchar *str1 = g_file_get_parse_name (root); + + g_warning ("Virtual root (%s) is not below actual root (%s)", str1, str); + + g_free (str); + g_free (str1); + + return GEDIT_FILE_BROWSER_STORE_RESULT_ERROR; + } + + set_virtual_root_from_file (model, root); + + 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, + GFile *root, + GFile *virtual_root) +{ + 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 && model->priv->root != NULL) + { + equal = g_file_equal (root, model->priv->root->file); + + if (equal && virtual_root == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + if (virtual_root) + { + if (equal && g_file_equal (virtual_root, model->priv->virtual_root->file)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* 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 (root != NULL) + { + /* Create the root node */ + node = file_browser_node_dir_new (model, root, NULL); + + 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, + GFile *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); +} + +GFile * +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_dup (model->priv->root->file); +} + +GFile * +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_dup (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; + + 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 (GSList *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); +} + +const gchar * const * +gedit_file_browser_store_get_binary_patterns (GeditFileBrowserStore *model) +{ + return (const gchar * const *)model->priv->binary_patterns; +} + +void +gedit_file_browser_store_set_binary_patterns (GeditFileBrowserStore *model, + const gchar **binary_patterns) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->binary_patterns != NULL) + { + g_strfreev (model->priv->binary_patterns); + g_ptr_array_unref (model->priv->binary_pattern_specs); + } + + model->priv->binary_patterns = g_strdupv ((gchar **)binary_patterns); + + if (binary_patterns == NULL) + { + model->priv->binary_pattern_specs = NULL; + } + else + { + gssize n_patterns = g_strv_length ((gchar **) binary_patterns); + + model->priv->binary_pattern_specs = g_ptr_array_sized_new (n_patterns); + g_ptr_array_set_free_func (model->priv->binary_pattern_specs, (GDestroyNotify) g_pattern_spec_free); + + for (guint i = 0; binary_patterns[i] != NULL; ++i) + g_ptr_array_add (model->priv->binary_pattern_specs, g_pattern_spec_new (binary_patterns[i])); + } + + model_refilter (model); + + g_object_notify (G_OBJECT (model), "binary-patterns"); +} + +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) +{ + if (!node->file) + return; + + if (reparent) + { + GFile *parent = node->parent->file; + gchar *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)) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node); + + for (GSList *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; + 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; + } + + g_signal_emit (model, model_signals[RENAME], 0, previous, node->file); + + g_object_unref (previous); + + 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_free_full (data->files, g_object_unref); + + if (!data->removed) + data->model->priv->async_handles = g_slist_remove (data->model->priv->async_handles, data); + + g_slice_free (AsyncData, 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; +} + +static void +delete_file_finished (GFile *file, + GAsyncResult *res, + AsyncData *data) +{ + GError *error = NULL; + gboolean ok; + + if (data->trash) + ok = g_file_trash_finish (file, res, &error); + else + ok = g_file_delete_finish (file, res, &error); + + if (ok) + { + /* Remove the file from the model */ + FileBrowserNode *node = model_find_node (data->model, NULL, file); + + if (node != NULL) + model_remove_node (data->model, node, NULL, TRUE); + + /* Process the next file */ + data->iter = data->iter->next; + } + else if (!ok && error != NULL) + { + gint code = error->code; + g_error_free (error); + + if (data->trash && code == G_IO_ERROR_NOT_SUPPORTED) + { + /* Trash is not supported on this system. Ask the user + * if he wants to delete completely the files instead. + */ + if (emit_no_trash (data)) + { + /* Changes this into a delete job */ + data->trash = FALSE; + data->iter = data->files; + } + else + { + /* End the job */ + async_data_free (data); + return; + } + } + else if (code == G_IO_ERROR_CANCELLED) + { + /* Job has been cancelled, end the job */ + async_data_free (data); + return; + } + } + + /* Continue the job */ + delete_files (data); +} + +static void +delete_files (AsyncData *data) +{ + GFile *file; + + /* Check if our job is done */ + if (data->iter == NULL) + { + async_data_free (data); + return; + } + + file = G_FILE (data->iter->data); + + if (data->trash) + { + g_file_trash_async (file, + G_PRIORITY_DEFAULT, + data->cancellable, + (GAsyncReadyCallback)delete_file_finished, + data); + } + else + { + g_file_delete_async (file, + G_PRIORITY_DEFAULT, + data->cancellable, + (GAsyncReadyCallback)delete_file_finished, + data); + } +} + +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_slice_new (AsyncData); + + 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); + + delete_files (data); + 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + 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, _("Untitled 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, _("Untitled Folder")); + + 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; +} + +void +_gedit_file_browser_store_register_type (GTypeModule *type_module) +{ + gedit_file_browser_store_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-store.h b/plugins/filebrowser/gedit-file-browser-store.h new file mode 100644 index 0000000..02df0cb --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.h @@ -0,0 +1,188 @@ +/* + * gedit-file-browser-store.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#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_ICON_NAME, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, + + /* Columns not in common with GeditFileBookmarksStore */ + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, + 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, + GFile *oldfile, + GFile *newfile); + void (* begin_refresh) (GeditFileBrowserStore *model); + void (* end_refresh) (GeditFileBrowserStore *model); + void (* unload) (GeditFileBrowserStore *model, + GFile *location); + void (* before_row_deleted) (GeditFileBrowserStore *model, + GtkTreePath *path); +}; + +GType gedit_file_browser_store_get_type (void) G_GNUC_CONST; + +GeditFileBrowserStore *gedit_file_browser_store_new (GFile *root); +GeditFileBrowserStoreResult gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore *model, + GFile *root, + GFile *virtual_root); +GeditFileBrowserStoreResult gedit_file_browser_store_set_root (GeditFileBrowserStore *model, + GFile *root); +GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore *model, + GtkTreeIter *iter); +GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root_from_location (GeditFileBrowserStore *model, + GFile *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); +GFile *gedit_file_browser_store_get_root (GeditFileBrowserStore *model); +GFile *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); +const gchar * const *gedit_file_browser_store_get_binary_patterns (GeditFileBrowserStore *model); +void gedit_file_browser_store_set_binary_patterns (GeditFileBrowserStore *model, + const gchar **binary_patterns); +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); + +void _gedit_file_browser_store_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_STORE_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-utils.c b/plugins/filebrowser/gedit-file-browser-utils.c new file mode 100644 index 0000000..dbca26a --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.c @@ -0,0 +1,223 @@ +/* + * gedit-file-bookmarks-utils.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <glib/gi18n-lib.h> +#include <gedit/gedit-utils.h> + +#include "gedit-file-browser-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); + g_object_unref (info); + + return ret; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_file (GFile *file, + GtkIconSize size, + gboolean use_symbolic) +{ + GIcon *icon; + GFileInfo *info; + GdkPixbuf *ret = NULL; + const char *attribute; + + attribute = use_symbolic ? G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON : + G_FILE_ATTRIBUTE_STANDARD_ICON; + + info = g_file_query_info (file, + attribute, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (!info) + return NULL; + + icon = use_symbolic ? g_file_info_get_symbolic_icon (info) : + 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_symbolic_icon_name_from_file (GFile *file) +{ + GFileInfo *info; + GIcon *icon; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (!info) + return NULL; + + if ((icon = g_file_info_get_symbolic_icon (info)) && G_IS_THEMED_ICON (icon)) + { + const gchar * const *names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + return g_strdup (names[0]); + } + + g_object_unref (info); + return NULL; +} + +gchar * +gedit_file_browser_utils_name_from_themed_icon (GIcon *icon) +{ + GtkIconTheme *theme; + const gchar * const *names; + + if (!G_IS_THEMED_ICON (icon)) + return NULL; + + theme = gtk_icon_theme_get_default (); + names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + + if (gtk_icon_theme_has_icon (theme, names[0])) + return g_strdup (names[0]); + + return NULL; +} + +gchar * +gedit_file_browser_utils_file_basename (GFile *file) +{ + return gedit_utils_basename_for_display (file); +} + +gboolean +gedit_file_browser_utils_confirmation_dialog (GeditWindow *window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const *button_label) +{ + GtkWidget *dlg; + gint ret; + + 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); + } + + gtk_dialog_add_buttons (GTK_DIALOG (dlg), + _("_Cancel"), GTK_RESPONSE_CANCEL, + button_label, GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_CANCEL); + + ret = gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); + + return (ret == GTK_RESPONSE_OK); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-utils.h b/plugins/filebrowser/gedit-file-browser-utils.h new file mode 100644 index 0000000..165f513 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.h @@ -0,0 +1,46 @@ +/* + * gedit-file-browser-utils.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_FILE_BROWSER_UTILS_H +#define GEDIT_FILE_BROWSER_UTILS_H + +#include <gedit/gedit-window.h> +#include <gio/gio.h> + +gchar *gedit_file_browser_utils_name_from_themed_icon (GIcon *icon); +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, + gboolean use_symbolic); +gchar *gedit_file_browser_utils_symbolic_icon_name_from_file (GFile *file); +gchar *gedit_file_browser_utils_file_basename (GFile *file); + +gboolean gedit_file_browser_utils_confirmation_dialog (GeditWindow *window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const *button_label); + +#endif /* GEDIT_FILE_BROWSER_UTILS_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-view.c b/plugins/filebrowser/gedit-file-browser-view.c new file mode 100644 index 0000000..37a0a23 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-view.c @@ -0,0 +1,1323 @@ +/* + * gedit-file-browser-view.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> + +#include <glib-object.h> +#include <gio/gio.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-enum-types.h" + +struct _GeditFileBrowserViewPrivate +{ + GtkTreeViewColumn *column; + GtkCellRenderer *pixbuf_renderer; + GtkCellRenderer *text_renderer; + + GtkTreeModel *model; + + /* Used when renaming */ + gchar *orig_markup; + GtkTreeRowReference *editable; + + /* Click policy */ + GeditFileBrowserViewClickPolicy click_policy; + /* Both clicks in a double click need to be on the same row */ + GtkTreePath *double_click_path[2]; + 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 } +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserView, + gedit_file_browser_view, + GTK_TYPE_TREE_VIEW, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserView)) + +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, + GFile *location, + 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) + g_object_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; + } + + G_OBJECT_CLASS (gedit_file_browser_view_parent_class)->finalize (object); +} + +static void +add_expand_state (GeditFileBrowserView *view, + GFile *location) +{ + if (!location) + return; + + if (view->priv->expand_state) + g_hash_table_insert (view->priv->expand_state, location, g_object_ref (location)); +} + +static void +remove_expand_state (GeditFileBrowserView *view, + GFile *location) +{ + if (!location) + return; + + if (view->priv->expand_state) + g_hash_table_remove (view->priv->expand_state, location); +} + +static void +row_expanded (GtkTreeView *tree_view, + GtkTreeIter *iter, + GtkTreePath *path) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view); + + 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) + { + GFile *location; + + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + add_expand_state (view, location); + + if (location) + g_object_unref (location); + } + + _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); + + 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) + { + GFile *location; + + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + remove_expand_state (view, location); + + if (location) + g_object_unref (location); + } + + _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) +{ + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (obj)); + + 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_from_name (display, "pointer"); + } + else if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE) + { + if (obj->priv->hover_path != NULL) + { + GtkTreeIter iter; + + 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_get_realized (GTK_WIDGET (obj))) + { + GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (obj)); + + gdk_window_set_cursor (win, NULL); + + if (display != NULL) + gdk_display_flush (display); + } + + if (obj->priv->hand_cursor) + { + g_object_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 = gtk_tree_selection_get_selected_rows (selection, &view->priv->model); + GList *row; + GtkTreePath *directory = NULL; + GtkTreePath *path; + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + + 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) && directory == NULL) + directory = path; + else if (!FILE_IS_DUMMY (flags)) + g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter); + } + + if (directory != NULL && + gtk_tree_model_get_iter (view->priv->model, &iter, directory)) + { + g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter); + } + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); +} + +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 +row_activated (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + + /* Make sure the activated row is the only one selected */ + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + + activate_selected_items (GEDIT_FILE_BROWSER_VIEW (tree_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 = GTK_TREE_VIEW (view); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + GtkTreePath *path; + + 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) +{ + GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class); + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + int double_click_time; + static int click_count = 0; + static guint32 last_click_time = 0; + GtkTreePath *path; + int expander_size; + int horizontal_separator; + gboolean on_expander; + gboolean call_parent; + gboolean selected; + + /* 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) + { + /* Do not chain up. The row-activated signal is normally + * already sent, which will activate the selected item + * and open the file. + */ + } + 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 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 == GDK_BUTTON_SECONDARY && 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 = GEDIT_FILE_BROWSER_VIEW (widget); + guint modifiers = gtk_accelerator_get_default_mod_mask (); + gboolean handled = FALSE; + + switch (event->keyval) + { + case GDK_KEY_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_KEY_Return: + case GDK_KEY_KP_Enter: + activate_selected_items (view); + handled = TRUE; + break; + + case GDK_KEY_h: + if ((event->state & modifiers) == GDK_CONTROL_MASK) + { + toggle_hidden_filter (view); + handled = TRUE; + break; + } + + default: + handled = FALSE; + break; + } + + /* 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; + + 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)) + { + GFile *location; + + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + add_expand_state (view, location); + + if (location) + g_object_unref (location); + } + + 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_activated = row_activated; + 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, NULL, + 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, NULL, + 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, NULL, + 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, NULL, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); +} + +static void +gedit_file_browser_view_class_finalize (GeditFileBrowserViewClass *klass) +{ +} + +static void +cell_data_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GeditFileBrowserView *obj) +{ + GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter); + PangoUnderline underline = PANGO_UNDERLINE_NONE; + gboolean editable = FALSE; + + if (obj->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && + 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) && + 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 (edpath); + } + + gtk_tree_path_free (path); + g_object_set (cell, "editable", editable, "underline", underline, NULL); +} + +static void +icon_renderer_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GeditFileBrowserView *obj) +{ + GdkPixbuf *pixbuf; + gchar *icon_name; + gboolean set_pixbuf = FALSE; + + gtk_tree_model_get (tree_model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME, &icon_name, + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON, &pixbuf, + -1); + + if (pixbuf != NULL && (GEDIT_IS_FILE_BROWSER_STORE (tree_model) || icon_name == NULL)) + set_pixbuf = TRUE; + + if (set_pixbuf) + g_object_set (cell, "pixbuf", pixbuf, NULL); + else + g_object_set (cell, "icon-name", icon_name, NULL); + + g_clear_object (&pixbuf); + g_free (icon_name); +} + +static void +gedit_file_browser_view_init (GeditFileBrowserView *obj) +{ + obj->priv = gedit_file_browser_view_get_instance_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_set_cell_data_func (obj->priv->column, + obj->priv->pixbuf_renderer, + (GtkTreeCellDataFunc)icon_renderer_cb, + obj, + NULL); + + 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, + "markup", + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP); + + 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); +} + +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; + gint search_column; + + 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); + search_column = GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME; + } + 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); + search_column = GEDIT_FILE_BROWSER_STORE_COLUMN_NAME; + + 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) && + 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); + gtk_tree_view_set_search_column (GTK_TREE_VIEW (tree_view), search_column); +} + +void +gedit_file_browser_view_start_rename (GeditFileBrowserView *tree_view, + GtkTreeIter *iter) +{ + gchar *name; + gchar *markup; + guint flags; + GValue name_escaped = G_VALUE_INIT; + 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_NAME, &name, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &markup, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags))) + { + g_free (name); + g_free (markup); + return; + } + + /* Restore the markup to the original + * name, a plugin might have changed the markup. + */ + g_value_init (&name_escaped, G_TYPE_STRING); + g_value_take_string (&name_escaped, g_markup_escape_text (name, -1)); + gedit_file_browser_store_set_value (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model), + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &name_escaped); + + 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->orig_markup = markup; + tree_view->priv->editable = rowref; + + /* grab focus on the text cell which is editable */ + gtk_tree_view_column_focus_cell (tree_view->priv->column, tree_view->priv->text_renderer); + + path = gtk_tree_row_reference_get_path (tree_view->priv->editable), + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), path, tree_view->priv->column, TRUE); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), + path, tree_view->priv->column, + FALSE, 0.0, 0.0); + + gtk_tree_path_free (path); + g_value_unset (&name_escaped); + g_free (name); +} + +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 = gtk_tree_path_new_from_string (path); + GtkTreeIter iter; + gboolean ret; + GValue orig_markup = G_VALUE_INIT; + GError *error = NULL; + + ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath); + gtk_tree_path_free (treepath); + + if (ret) + { + /* Restore the original markup */ + g_value_init (&orig_markup, G_TYPE_STRING); + g_value_set_string (&orig_markup, tree_view->priv->orig_markup); + gedit_file_browser_store_set_value (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &orig_markup); + + if (new_text != NULL && *new_text != '\0' && + 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); + } + + g_value_unset (&orig_markup); + } + + g_free (tree_view->priv->orig_markup); + tree_view->priv->orig_markup = NULL; + + gtk_tree_row_reference_free (tree_view->priv->editable); + tree_view->priv->editable = NULL; +} + +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, + GFile *location, + 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, location); +} + +static void +restore_expand_state (GeditFileBrowserView *view, + GeditFileBrowserStore *model, + GtkTreeIter *iter) +{ + GFile *location; + + gtk_tree_model_get (GTK_TREE_MODEL (model), + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (location) + { + GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + + if (g_hash_table_lookup (view->priv->expand_state, location)) + gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, FALSE); + + gtk_tree_path_free (path); + g_object_unref (location); + } +} + +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); +} + +void +_gedit_file_browser_view_register_type (GTypeModule *type_module) +{ + gedit_file_browser_view_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-view.h b/plugins/filebrowser/gedit-file-browser-view.h new file mode 100644 index 0000000..8c2efc8 --- /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 <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#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_SINGLE, + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE +} 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; + +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); + +void _gedit_file_browser_view_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_VIEW_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-widget.c b/plugins/filebrowser/gedit-file-browser-widget.c new file mode 100644 index 0000000..eab78ed --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.c @@ -0,0 +1,3150 @@ +/* + * gedit-file-browser-widget.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#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-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-enum-types.h" + +#define LOCATION_DATA_KEY "gedit-file-browser-widget-location" + +enum +{ + BOOKMARKS_ID, + SEPARATOR_CUSTOM_ID, + SEPARATOR_ID, + PATH_ID, + NUM_DEFAULT_IDS +}; + +enum +{ + COLUMN_ICON, + COLUMN_ICON_NAME, + COLUMN_NAME, + COLUMN_FILE, + COLUMN_ID, + N_COLUMNS +}; + +/* Properties */ +enum +{ + PROP_0, + + PROP_FILTER_PATTERN, +}; + +/* Signals */ +enum +{ + LOCATION_ACTIVATED, + ERROR, + CONFIRM_DELETE, + CONFIRM_NO_TRASH, + OPEN_IN_TERMINAL, + SET_ACTIVE_ROOT, + 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; + gchar *icon_name; + GdkPixbuf *icon; +} NameIcon; + +struct _GeditFileBrowserWidgetPrivate +{ + GeditFileBrowserView *treeview; + GeditFileBrowserStore *file_store; + GeditFileBookmarksStore *bookmarks_store; + + GHashTable *bookmarks_hash; + + GMenuModel *dir_menu; + GMenuModel *bookmarks_menu; + + GtkWidget *previous_button; + GtkWidget *next_button; + + GtkWidget *locations_button; + GtkWidget *locations_popover; + GtkWidget *locations_treeview; + GtkTreeViewColumn *treeview_icon_column; + GtkCellRenderer *treeview_icon_renderer; + GtkTreeSelection *locations_treeview_selection; + GtkWidget *locations_button_arrow; + GtkWidget *locations_cellview; + GtkListStore *locations_model; + + GtkWidget *location_entry; + + GtkWidget *filter_entry_revealer; + GtkWidget *filter_entry; + + GSimpleActionGroup *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; + + GCancellable *cancellable; + + GdkCursor *busy_cursor; +}; + +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 gboolean on_location_button_press_event (GtkWidget *button, + GdkEventButton *event, + GeditFileBrowserWidget *obj); +static void on_location_entry_activate (GtkEntry *entry, + GeditFileBrowserWidget *obj); +static gboolean + on_location_entry_focus_out_event (GtkWidget *entry, + GdkEvent *event, + GeditFileBrowserWidget *obj); +static gboolean + on_location_entry_key_press_event (GtkWidget *entry, + GdkEventKey *event, + 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 previous_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void next_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void up_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void home_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void new_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void open_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void new_file_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void rename_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void delete_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void move_to_trash_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void refresh_view_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void view_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void change_show_hidden_state (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void change_show_binary_state (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void change_show_match_filename (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void open_in_terminal_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void set_active_root_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void on_locations_treeview_selection_changed (GtkTreeSelection *treeselection, + GeditFileBrowserWidget *obj); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserWidget, + gedit_file_browser_widget, + GTK_TYPE_GRID, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserWidget)) + +static void +free_name_icon (gpointer data) +{ + NameIcon *item = (NameIcon *)(data); + + if (item == NULL) + return; + + g_free (item->icon_name); + g_free (item->name); + + if (item->icon) + g_object_unref (item->icon); + + g_slice_free (NameIcon, item); +} + +static FilterFunc * +filter_func_new (GeditFileBrowserWidget *obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + FilterFunc *result = g_slice_new (FilterFunc); + + 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_slice_free (Location, loc); +} + +static gboolean +locations_find_by_id (GeditFileBrowserWidget *obj, + guint id, + GtkTreeIter *iter) +{ + GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->locations_model); + guint checkid; + + 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 +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 +filter_func_free (FilterFunc *func) +{ + g_slice_free (FilterFunc, func); +} + +static void +gedit_file_browser_widget_finalize (GObject *object) +{ + GeditFileBrowserWidgetPrivate *priv = GEDIT_FILE_BROWSER_WIDGET (object)->priv; + + g_free (priv->filter_pattern_str); + + G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->finalize (object); +} + +static void +clear_signals (GeditFileBrowserWidget *obj) +{ + GSList *item = obj->priv->signal_pool; + SignalNode *node; + + while (item != NULL) + { + node = (SignalNode *) (item->data); + item = g_slist_delete_link (item, item); + + g_signal_handler_disconnect (node->object, node->id); + g_slice_free (SignalNode, node); + } + + obj->priv->signal_pool = NULL; +} + +static void +gedit_file_browser_widget_dispose (GObject *object) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + GeditFileBrowserWidgetPrivate *priv = obj->priv; + + clear_signals (obj); + g_clear_object (&priv->file_store); + g_clear_object (&priv->bookmarks_store); + + g_slist_free_full (priv->filter_funcs, (GDestroyNotify)filter_func_free); + g_list_free_full (priv->locations, (GDestroyNotify)location_free); + + if (priv->bookmarks_hash != NULL) + { + g_hash_table_unref (priv->bookmarks_hash); + priv->bookmarks_hash = NULL; + } + + cancel_async_operation (obj); + + g_clear_object (&obj->priv->current_location_menu_item); + g_clear_object (&priv->busy_cursor); + g_clear_object (&priv->dir_menu); + g_clear_object (&priv->bookmarks_menu); + + G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->dispose (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; + 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; + 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); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gedit_file_browser_widget_finalize; + object_class->dispose = gedit_file_browser_widget_dispose; + + 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", + "", + G_PARAM_READWRITE)); + + signals[LOCATION_ACTIVATED] = + g_signal_new ("location-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, location_activated), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_FILE); + + signals[ERROR] = + g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, error), + NULL, NULL, NULL, + 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, NULL, + 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, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); + + signals[OPEN_IN_TERMINAL] = + g_signal_new ("open-in-terminal", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, open_in_terminal), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_FILE); + + signals[SET_ACTIVE_ROOT] = + g_signal_new ("set-active-root", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, set_active_root), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/gedit/plugins/file-browser/ui/gedit-file-browser-widget.ui"); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, previous_button); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, next_button); + + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_button); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_popover); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_treeview); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_treeview_selection); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview_icon_column); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview_icon_renderer); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_cellview); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_button_arrow); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_model); + + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_entry); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, filter_entry_revealer); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, filter_entry); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_previous_menu); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_next_menu); +} + +static void +gedit_file_browser_widget_class_finalize (GeditFileBrowserWidgetClass *klass) +{ +} + +static void +add_signal (GeditFileBrowserWidget *obj, + gpointer object, + gulong id) +{ + SignalNode *node = g_slice_new (SignalNode); + + node->object = G_OBJECT (object); + node->id = id; + + obj->priv->signal_pool = g_slist_prepend (obj->priv->signal_pool, node); +} + +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, + gchar **icon_name, + GdkPixbuf **icon) +{ + NameIcon *item = (NameIcon *)g_hash_table_lookup (obj->priv->bookmarks_hash, file); + + if (item == NULL) + return FALSE; + + *name = g_strdup (item->name); + *icon_name = g_strdup (item->icon_name); + + if (icon != NULL && item->icon != NULL) + { + *icon = g_object_ref (item->icon); + } + + return TRUE; +} + +static void +insert_path_item (GeditFileBrowserWidget *obj, + GFile *file, + GtkTreeIter *after, + GtkTreeIter *iter) +{ + gchar *unescape = NULL; + gchar *icon_name = NULL; + GdkPixbuf *icon = NULL; + + /* Try to get the icon and name from the bookmarks hash */ + if (!get_from_bookmark_file (obj, file, &unescape, &icon_name, &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_name = gedit_file_browser_utils_symbolic_icon_name_from_file (file); + } + + gtk_list_store_insert_after (obj->priv->locations_model, iter, after); + + gtk_list_store_set (obj->priv->locations_model, + iter, + COLUMN_ICON, icon, + COLUMN_ICON_NAME, icon_name, + COLUMN_NAME, unescape, + COLUMN_FILE, file, + COLUMN_ID, PATH_ID, + -1); + + if (icon) + g_object_unref (icon); + + g_free (icon_name); + g_free (unescape); +} + +static void +insert_separator_item (GeditFileBrowserWidget *obj) +{ + GtkTreeIter iter; + + gtk_list_store_insert (obj->priv->locations_model, &iter, 1); + gtk_list_store_set (obj->priv->locations_model, &iter, + COLUMN_ICON, NULL, + COLUMN_ICON_NAME, NULL, + COLUMN_NAME, NULL, + COLUMN_ID, SEPARATOR_ID, -1); +} + +static void +insert_location_path (GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + Location *loc; + GFile *current = NULL; + GFile *tmp; + GtkTreeIter separator; + GtkTreeIter iter; + + if (!priv->current_location) + { + g_message ("insert_location_path: no current location"); + return; + } + + loc = (Location *)(priv->current_location->data); + + current = loc->virtual_root; + locations_find_by_id (obj, SEPARATOR_ID, &separator); + + while (current != NULL) + { + insert_path_item (obj, current, &separator, &iter); + + if (current == loc->virtual_root) + { + + g_signal_handlers_block_by_func (priv->locations_treeview, + on_locations_treeview_selection_changed, + obj); + + gtk_tree_selection_select_iter (priv->locations_treeview_selection, &iter); + + g_signal_handlers_unblock_by_func (priv->locations_treeview, + on_locations_treeview_selection_changed, + obj); + } + + if (g_file_equal (current, loc->root) || + !g_file_has_parent (current, NULL)) + { + 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 +remove_path_items (GeditFileBrowserWidget *obj) +{ + GtkTreeIter iter; + + while (locations_find_by_id (obj, PATH_ID, &iter)) + { + gtk_list_store_remove (obj->priv->locations_model, &iter); + } +} + +static void +check_current_item (GeditFileBrowserWidget *obj, + gboolean show_path) +{ + GtkTreeIter separator; + gboolean has_sep; + + remove_path_items (obj); + has_sep = locations_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_list_store_remove (obj->priv->locations_model, &separator); + } +} + +static void +fill_locations_model (GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + GtkTreeIter iter; + + gtk_list_store_append (priv->locations_model, &iter); + gtk_list_store_set (priv->locations_model, + &iter, + COLUMN_ICON, NULL, + COLUMN_ICON_NAME, "user-bookmarks-symbolic", + COLUMN_NAME, _("Bookmarks"), + COLUMN_ID, BOOKMARKS_ID, -1); + + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (priv->locations_treeview), + separator_func, + obj, + NULL); + + gtk_tree_selection_select_iter (priv->locations_treeview_selection, &iter); + + on_locations_treeview_selection_changed (priv->locations_treeview_selection, obj); + gedit_file_browser_widget_show_bookmarks (obj); +} + +static gboolean +filter_real (GeditFileBrowserStore *model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GSList *item; + + for (item = obj->priv->filter_funcs; item; item = item->next) + { + FilterFunc *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 = GTK_TREE_MODEL (obj->priv->bookmarks_store); + GdkPixbuf *pixbuf; + gchar *name; + gchar *icon_name; + GFile *location; + NameIcon *item; + + if (!(location = gedit_file_bookmarks_store_get_location (obj->priv->bookmarks_store, iter))) + return; + + gtk_tree_model_get (model, + iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, &pixbuf, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME, &icon_name, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &name, + -1); + + item = g_slice_new (NameIcon); + item->name = name; + item->icon_name = icon_name; + item->icon = pixbuf; + + g_hash_table_insert (obj->priv->bookmarks_hash, + location, + item); +} + +static void +init_bookmarks_hash (GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->bookmarks_store); + GtkTreeIter iter; + + 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 +on_locations_treeview_row_activated (GtkTreeView *locations_treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + GtkTreeIter iter; + guint id = G_MAXUINT; + GFile *file; + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->locations_model), &iter, path)) + { + gtk_tree_model_get (GTK_TREE_MODEL (priv->locations_model), &iter, COLUMN_ID, &id, -1); + } + + if (id == BOOKMARKS_ID) + { + gedit_file_browser_widget_show_bookmarks (obj); + } + else if (id == PATH_ID) + { + gtk_tree_model_get (GTK_TREE_MODEL (priv->locations_model), + &iter, + COLUMN_FILE, &file, + -1); + + gedit_file_browser_store_set_virtual_root_from_location (priv->file_store, file); + + g_object_unref (file); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->locations_cellview), path); + } + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->locations_button), FALSE); +} + +static GActionEntry browser_entries[] = { + { "open", open_activated }, + { "set_active_root", set_active_root_activated }, + { "new_folder", new_folder_activated }, + { "new_file", new_file_activated }, + { "rename", rename_activated }, + { "move_to_trash", move_to_trash_activated }, + { "delete", delete_activated }, + { "refresh_view", refresh_view_activated }, + { "view_folder", view_folder_activated }, + { "open_in_terminal", open_in_terminal_activated }, + { "show_hidden", NULL, NULL, "false", change_show_hidden_state }, + { "show_binary", NULL, NULL, "false", change_show_binary_state }, + { "show_match_filename", NULL, NULL, "false", change_show_match_filename }, + { "previous_location", previous_location_activated }, + { "next_location", next_location_activated }, + { "up", up_activated }, + { "home", home_activated } +}; + +static void +locations_icon_renderer_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GdkPixbuf *pixbuf; + gchar *icon_name; + + gtk_tree_model_get (tree_model, + iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME, &icon_name, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, &pixbuf, + -1); + + if (icon_name != NULL) + g_object_set (cell, "icon-name", icon_name, NULL); + else + g_object_set (cell, "pixbuf", pixbuf, NULL); + + g_clear_object (&pixbuf); + g_free (icon_name); +} + +static void +gedit_file_browser_widget_init (GeditFileBrowserWidget *obj) +{ + GtkBuilder *builder; + GdkDisplay *display; + GAction *action; + GError *error = NULL; + + obj->priv = gedit_file_browser_widget_get_instance_private (obj); + + obj->priv->filter_pattern_str = g_strdup (""); + obj->priv->bookmarks_hash = g_hash_table_new_full (g_file_hash, + (GEqualFunc)g_file_equal, + g_object_unref, + free_name_icon); + + display = gtk_widget_get_display (GTK_WIDGET (obj)); + obj->priv->busy_cursor = gdk_cursor_new_from_name (display, "progress"); + + builder = gtk_builder_new (); + if (!gtk_builder_add_from_resource (builder, + "/org/gnome/gedit/plugins/file-browser/ui/gedit-file-browser-menus.ui", + &error)) + { + g_warning ("loading menu builder file: %s", error->message); + g_error_free (error); + } + else + { + obj->priv->dir_menu = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "dir-menu"))); + obj->priv->bookmarks_menu = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "bookmarks-menu"))); + } + + g_object_unref (builder); + + obj->priv->action_group = g_simple_action_group_new (); + g_action_map_add_action_entries (G_ACTION_MAP (obj->priv->action_group), + browser_entries, + G_N_ELEMENTS (browser_entries), + obj); + + /* set initial sensitivity */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "previous_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "next_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + gtk_widget_insert_action_group (GTK_WIDGET (obj), + "browser", + G_ACTION_GROUP (obj->priv->action_group)); + + gtk_widget_init_template (GTK_WIDGET (obj)); + + g_signal_connect (obj->priv->previous_button, "button-press-event", + G_CALLBACK (on_location_button_press_event), obj); + g_signal_connect (obj->priv->next_button, "button-press-event", + G_CALLBACK (on_location_button_press_event), obj); + + /* locations popover */ + gtk_tree_selection_set_mode (obj->priv->locations_treeview_selection, GTK_SELECTION_SINGLE); + gtk_tree_view_column_set_cell_data_func (obj->priv->treeview_icon_column, + obj->priv->treeview_icon_renderer, + (GtkTreeCellDataFunc)locations_icon_renderer_cb, + obj, + NULL); + fill_locations_model (obj); + + g_signal_connect (obj->priv->locations_treeview_selection, "changed", + G_CALLBACK (on_locations_treeview_selection_changed), obj); + g_signal_connect (obj->priv->locations_treeview, "row-activated", + G_CALLBACK (on_locations_treeview_row_activated), obj); + + g_signal_connect (obj->priv->location_entry, "activate", + G_CALLBACK (on_location_entry_activate), obj); + g_signal_connect (obj->priv->location_entry, "focus-out-event", + G_CALLBACK (on_location_entry_focus_out_event), obj); + g_signal_connect (obj->priv->location_entry, "key-press-event", + G_CALLBACK (on_location_entry_key_press_event), obj); + + /* tree view */ + obj->priv->file_store = gedit_file_browser_store_new (NULL); + obj->priv->bookmarks_store = gedit_file_bookmarks_store_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); + + 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); + + /* filter */ + g_signal_connect_swapped (obj->priv->filter_entry, "activate", + G_CALLBACK (on_entry_filter_activate), + obj); + g_signal_connect_swapped (obj->priv->filter_entry, "focus_out_event", + G_CALLBACK (on_entry_filter_activate), + obj); +} + +/* Private */ + +static void +update_sensitivity (GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GAction *action; + gint mode; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + { + mode = gedit_file_browser_store_get_filter_mode (GEDIT_FILE_BROWSER_STORE (model)); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + + g_action_change_state (action, + g_variant_new_boolean (!(mode & + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN))); + + /* sensitivity */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "up"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "home"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_binary"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_match_filename"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + { + /* Set the filter toggle to normal up state, just for visual pleasure */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + + g_action_change_state (action, g_variant_new_boolean (FALSE)); + + /* sensitivity */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "up"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "home"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_binary"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_match_filename"); + g_simple_action_set_enabled (G_SIMPLE_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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return result; +} + +static gboolean +popup_menu (GeditFileBrowserWidget *obj, + GtkTreeView *treeview, + GdkEventButton *event, + GtkTreeModel *model) +{ + GtkWidget *menu; + GMenuModel *menu_model; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + menu_model = obj->priv->dir_menu; + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + menu_model = obj->priv->bookmarks_menu; + else + return FALSE; + + menu = gtk_menu_new_from_model (menu_model); + gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (obj), NULL); + + if (event != NULL) + { + GtkTreeSelection *selection; + selection = gtk_tree_view_get_selection (treeview); + + if (gtk_tree_selection_count_selected_rows (selection) <= 1) + { + GtkTreePath *path; + + if (gtk_tree_view_get_path_at_pos (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_at_pointer (GTK_MENU (menu), (GdkEvent *)event); + } + else + { + GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (treeview)); + GdkGravity rect_gravity = GDK_GRAVITY_EAST; + GdkGravity menu_gravity = GDK_GRAVITY_NORTH_WEST; + GdkRectangle rect; + + if (gedit_utils_menu_position_under_tree_view (treeview, &rect)) + { + if (gtk_widget_get_direction (GTK_WIDGET (treeview)) == GTK_TEXT_DIR_RTL) + { + rect_gravity = GDK_GRAVITY_WEST; + menu_gravity = GDK_GRAVITY_NORTH_EAST; + } + + gtk_menu_popup_at_rect (GTK_MENU (menu), window, &rect, rect_gravity, menu_gravity, NULL); + 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 = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeIter iter; + + 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) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + GList *rows = gtk_tree_selection_get_selected_rows (selection, &model); + GList *row; + GList *paths = NULL; + + for (row = rows; row; row = row->next) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + GtkTreeIter iter; + guint flags; + + 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return paths; +} + +static gboolean +delete_selected_files (GeditFileBrowserWidget *obj, + gboolean trash) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + gboolean confirm; + GeditFileBrowserStoreResult result; + GList *rows; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + if (!(rows = get_deletable_files (obj))) + 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return result == GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +static void +show_location_entry (GeditFileBrowserWidget *obj, + const gchar *location) +{ + g_warn_if_fail (location != NULL); + + gtk_entry_set_text (GTK_ENTRY (obj->priv->location_entry), location); + + gtk_widget_show (obj->priv->location_entry); + gtk_widget_grab_focus (obj->priv->location_entry); + + /* grab_focus() causes the entry's text to become + * selected so, unselect it and move the cursor to the end. + */ + gtk_editable_set_position (GTK_EDITABLE (obj->priv->location_entry), -1); +} + +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 *current = g_object_ref (file); + GFile *tmp; + + 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) +{ + Location *loc = (Location *) (item->data); + GtkWidget *result; + gchar *icon_name = NULL; + gchar *unescape = NULL; + + if (!get_from_bookmark_file (obj, loc->virtual_root, &unescape, &icon_name, NULL)) + unescape = gedit_file_browser_utils_file_basename (loc->virtual_root); + + 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 (icon_name); + 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; + + 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 */ + gedit_file_browser_widget_set_root_and_virtual_root (obj, + loc->root, + loc->virtual_root); + + obj->priv->changing_location = FALSE; +} + +static void +clear_next_locations (GeditFileBrowserWidget *obj) +{ + GAction *action; + 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); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); +} + +static void +update_filter_mode (GeditFileBrowserWidget *obj, + GSimpleAction *action, + GVariant *state, + GeditFileBrowserStoreFilterMode mode) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + { + gint now = gedit_file_browser_store_get_filter_mode (GEDIT_FILE_BROWSER_STORE (model)); + + if (g_variant_get_boolean(state)) + now &= ~mode; + else + now |= mode; + + gedit_file_browser_store_set_filter_mode (GEDIT_FILE_BROWSER_STORE (model), now); + + } + + g_simple_action_set_state (action, state); +} + +static void +set_filter_pattern_real (GeditFileBrowserWidget *obj, + gchar const *pattern, + gboolean update_entry) +{ + GtkTreeModel *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 == '\0') + return; + + if (pattern != NULL && strcmp (pattern, obj->priv->filter_pattern_str) == 0) + return; + + /* Free the old pattern */ + g_free (obj->priv->filter_pattern_str); + + if (pattern == NULL) + obj->priv->filter_pattern_str = g_strdup (""); + else + 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) + gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry), obj->priv->filter_pattern_str); + + 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 (void) +{ + GeditFileBrowserWidget *obj = g_object_new (GEDIT_TYPE_FILE_BROWSER_WIDGET, NULL); + + gedit_file_browser_widget_show_bookmarks (obj); + + return GTK_WIDGET (obj); +} + +void +gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget *obj) +{ + GtkTreePath *path; + GtkTreeIter iter; + + gtk_widget_set_sensitive (obj->priv->locations_button, FALSE); + gtk_widget_hide (obj->priv->locations_button_arrow); + locations_find_by_id (obj, BOOKMARKS_ID, &iter); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (obj->priv->locations_model), &iter); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (obj->priv->locations_cellview), path); + gtk_tree_path_free (path); + + 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) +{ + gtk_widget_set_sensitive (obj->priv->locations_button, TRUE); + gtk_widget_show (obj->priv->locations_button_arrow); + + 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, + GFile *root, + GFile *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, + GFile *root, + gboolean virtual_root) +{ + GFile *parent; + + if (!virtual_root) + { + gedit_file_browser_widget_set_root_and_virtual_root (obj, + root, + NULL); + return; + } + + if (!root) + return; + + parent = get_topmost_file (root); + gedit_file_browser_widget_set_root_and_virtual_root (obj, parent, root); + 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; +} + +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 = filter_func_new (obj, func, user_data, notify);; + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + 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; + + for (item = obj->priv->filter_funcs; item; item = item->next) + { + FilterFunc *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); + + filter_func_free (func); + break; + } + } +} + +void +gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget *obj, + gchar const *pattern) +{ + gboolean show; + GAction *action; + + /* if pattern is not null, reveal the entry */ + show = pattern != NULL && *pattern != '\0'; + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_match_filename"); + g_action_change_state (action, g_variant_new_boolean (show)); + + 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) && + !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; +} + +void +gedit_file_browser_widget_set_active_root_enabled (GeditFileBrowserWidget *widget, + gboolean enabled) +{ + GAction *action; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_WIDGET (widget)); + + action = g_action_map_lookup_action (G_ACTION_MAP (widget->priv->action_group), "set_active_root"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); +} + +static guint +gedit_file_browser_widget_get_num_selected_files_or_directories (GeditFileBrowserWidget *obj, + guint *files, + guint *dirs) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GList *rows, *row; + guint result = 0; + + 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) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + GeditFileBrowserStoreFlag flags; + GtkTreeIter iter; + + /* 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return result; +} + +typedef struct +{ + GeditFileBrowserWidget *widget; + GCancellable *cancellable; +} AsyncData; + +static AsyncData * +async_data_new (GeditFileBrowserWidget *widget) +{ + AsyncData *ret = g_slice_new (AsyncData); + + 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_slice_free (AsyncData, async); +} + +static void +set_busy (GeditFileBrowserWidget *obj, + gboolean busy) +{ + GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)); + + if (!GDK_IS_WINDOW (window)) + return; + + if (busy) + { + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (obj)); + GdkCursor *cursor= gdk_cursor_new_from_name (display, "progress"); + + gdk_window_set_cursor (window, cursor); + g_clear_object (&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; + + if (!mount) + { + gchar *name = g_volume_get_name (volume); + gchar *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); + + gedit_file_browser_widget_set_root (widget, root, FALSE); + + g_object_unref (root); +} + +static void +try_activate_drive (GeditFileBrowserWidget *widget, + GDrive *drive) +{ + GList *volumes = g_drive_get_volumes (drive); + GVolume *volume = G_VOLUME (volumes->data); + GMount *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_free_full (volumes, g_object_unref); +} + +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 *name = g_drive_get_name (drive); + gchar *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 = g_volume_get_mount (volume); + + activate_mount (async->widget, volume, mount); + + if (mount) + g_object_unref (mount); + } + else + { + gchar *name = g_volume_get_name (volume); + gchar *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 = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget)))); + AsyncData *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)); + } +} + +GeditMenuExtension * +gedit_file_browser_widget_extend_context_menu (GeditFileBrowserWidget *obj) +{ + guint i, n_items; + GMenuModel *section = NULL; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_WIDGET (obj), NULL); + + n_items = g_menu_model_get_n_items (obj->priv->dir_menu); + + for (i = 0; i < n_items && !section; i++) + { + gchar *id = NULL; + + if (g_menu_model_get_item_attribute (obj->priv->dir_menu, i, "id", "s", &id) && + strcmp (id, "extension-section") == 0) + { + section = g_menu_model_get_item_link (obj->priv->dir_menu, i, G_MENU_LINK_SECTION); + } + + g_free (id); + } + + return section != NULL ? gedit_menu_extension_new (G_MENU (section)) : NULL; +} + +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) +{ + GFile *location; + 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; + } + + if ((location = gedit_file_bookmarks_store_get_location (GEDIT_FILE_BOOKMARKS_STORE (model), iter))) + { + /* 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, location, FALSE); + } + else + { + gedit_file_browser_widget_set_root (obj, location, TRUE); + } + + g_object_unref (location); + } + else + { + g_warning ("No uri!"); + } +} + +static void +file_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + GFile *location; + gint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (!FILE_IS_DIR (flags) && !FILE_IS_DUMMY (flags)) + g_signal_emit (obj, signals[LOCATION_ACTIVATED], 0, location); + + if (location) + g_object_unref (location); +} + +static gboolean +directory_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gboolean result = FALSE; + GError *error = NULL; + GFile *location; + GeditFileBrowserStoreFlag flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (FILE_IS_DIR (flags) && location) + { + gchar *uri = g_file_get_uri (location); + result = TRUE; + + if (!gtk_show_uri_on_window (GTK_WINDOW (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); + g_object_unref (location); + } + + 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; + + 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)) + { + GFile *location; + GtkTreeIter root; + + gtk_tree_model_get (GTK_TREE_MODEL (model), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (gedit_file_browser_store_get_iter_root (model, &root)) + { + GAction *action; + + if (!obj->priv->changing_location) + { + Location *loc; + + /* Remove all items from obj->priv->current_location on */ + if (obj->priv->current_location) + clear_next_locations (obj); + + loc = g_slice_new (Location); + loc->root = gedit_file_browser_store_get_root (model); + loc->virtual_root = g_object_ref (location); + + 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); + + obj->priv->current_location = obj->priv->locations; + obj->priv->current_location_menu_item = create_goto_menu_item (obj, + obj->priv->current_location); + + g_object_ref_sink (obj->priv->current_location_menu_item); + } + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "up"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !virtual_root_is_root (obj, model)); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + obj->priv->current_location != NULL && + obj->priv->current_location->next != NULL); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + obj->priv->current_location != NULL && + obj->priv->current_location->prev != NULL); + } + + check_current_item (obj, TRUE); + + if (location) + g_object_unref (location); + } + 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) + { + GAction *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 = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } + + gtk_widget_hide (obj->priv->filter_entry_revealer); + + 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_show (obj->priv->filter_entry_revealer); + } + + 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 gboolean +on_location_button_press_event (GtkWidget *button, + GdkEventButton *event, + GeditFileBrowserWidget *obj) +{ + GtkWidget *menu; + + if (event->button != GDK_BUTTON_SECONDARY) + return FALSE; + + if (button == obj->priv->previous_button) + menu = obj->priv->location_previous_menu; + else + menu = obj->priv->location_next_menu; + + gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *)event); + + return TRUE; +} + +static void +on_locations_treeview_selection_changed (GtkTreeSelection *treeview_selection, + GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + GtkTreeModel *model = GTK_TREE_MODEL (priv->locations_model); + GtkTreePath *path; + GtkTreeIter iter; + + if (!gtk_tree_selection_get_selected (treeview_selection, &model, &iter)) + return; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (obj->priv->locations_model), &iter); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (obj->priv->locations_cellview), path); + gtk_tree_path_free (path); +} + +static void +on_location_entry_activate (GtkEntry *entry, + GeditFileBrowserWidget *obj) +{ + gchar *location = g_strdup (gtk_entry_get_text (entry)); + GFile *root; + gchar *cwd; + GFile *new_root; + + if (g_str_has_prefix (location, "~/")) + { + gchar *tmp = location; + + location = g_strdup_printf ("%s/%s", g_get_home_dir (), tmp + strlen ("~/")); + + g_free (tmp); + } + + root = gedit_file_browser_store_get_virtual_root (obj->priv->file_store); + cwd = g_file_get_path (root); + + if (cwd == NULL) + cwd = g_file_get_uri (root); + + new_root = g_file_new_for_commandline_arg_and_cwd (location, cwd); + + if (g_file_query_file_type (new_root, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY) + { + gchar *display_name = g_file_get_parse_name (new_root); + gchar *msg = g_strdup_printf (_("Error when loading “%s”: No such directory"), display_name); + + g_signal_emit (obj, signals[ERROR], 0, GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, msg); + + g_free (msg); + g_free (display_name); + } + else + { + gtk_widget_grab_focus (GTK_WIDGET (obj->priv->treeview)); + gtk_widget_hide (obj->priv->location_entry); + + gedit_file_browser_widget_set_root (obj, new_root, TRUE); + } + + g_object_unref (new_root); + g_free (cwd); + g_object_unref (root); + g_free (location); +} + +static gboolean +on_location_entry_focus_out_event (GtkWidget *entry, + GdkEvent *event, + GeditFileBrowserWidget *obj) +{ + gtk_widget_hide (entry); + return FALSE; +} + +static gboolean +on_location_entry_key_press_event (GtkWidget *entry, + GdkEventKey *event, + GeditFileBrowserWidget *obj) +{ + guint modifiers = gtk_accelerator_get_default_mod_mask (); + + if (event->keyval == GDK_KEY_Escape && + (event->state & modifiers) == 0) + { + gtk_widget_grab_focus (GTK_WIDGET (obj->priv->treeview)); + gtk_widget_hide (entry); + return TRUE; + } + + return FALSE; +} + +static gboolean +on_treeview_popup_menu (GeditFileBrowserView *treeview, + GeditFileBrowserWidget *obj) +{ + return popup_menu (obj, GTK_TREE_VIEW (treeview), 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 == GDK_BUTTON_SECONDARY) + return popup_menu (obj, + GTK_TREE_VIEW (treeview), + event, + gtk_tree_view_get_model (GTK_TREE_VIEW (treeview))); + + return FALSE; +} + +static gboolean +do_change_directory (GeditFileBrowserWidget *obj, + GdkEventKey *event) +{ + GAction *action = NULL; + + if ((event->state & + (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK & ~GDK_MOD1_MASK)) == + event->state && event->keyval == GDK_KEY_BackSpace) + { + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + } + else if (!((event->state & GDK_MOD1_MASK) && + (event->state & (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK)) == event->state)) + { + return FALSE; + } + + switch (event->keyval) + { + case GDK_KEY_Home: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "home"); + break; + case GDK_KEY_Left: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + break; + case GDK_KEY_Right: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location"); + break; + case GDK_KEY_Up: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "up"); + break; + default: + break; + } + + if (action != NULL) + { + g_action_activate (action, NULL); + return TRUE; + } + + return FALSE; +} + +static gboolean +on_treeview_key_press_event (GeditFileBrowserView *treeview, + GdkEventKey *event, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model; + guint modifiers; + + if (do_change_directory (obj, event)) + return TRUE; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + if (event->keyval == GDK_KEY_Delete || + event->keyval == GDK_KEY_KP_Delete) + { + + if ((event->state & modifiers) == GDK_SHIFT_MASK) + { + delete_selected_files (obj, FALSE); + return TRUE; + } + else if ((event->state & modifiers) == 0) + { + delete_selected_files (obj, TRUE); + return TRUE; + } + } + + if ((event->keyval == GDK_KEY_F2) && (event->state & modifiers) == 0) + { + rename_selected_file (obj); + return TRUE; + } + + if (event->keyval == GDK_KEY_l && + (event->state & modifiers) == GDK_CONTROL_MASK) + { + show_location_entry (obj, ""); + return TRUE; + } + + if (event->keyval == GDK_KEY_slash || + event->keyval == GDK_KEY_KP_Divide || +#ifdef G_OS_WIN32 + event->keyval == GDK_KEY_backslash || +#endif + event->keyval == GDK_KEY_asciitilde) + { + gchar location[2] = {'\0', '\0'}; + + location[0] = gdk_keyval_to_unicode (event->keyval); + + show_location_entry (obj, location); + return TRUE; + } + + return FALSE; +} + +static void +on_selection_changed (GtkTreeSelection *selection, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GAction *action; + guint selected = 0; + guint files = 0; + guint dirs = 0; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + selected = gedit_file_browser_widget_get_num_selected_files_or_directories (obj, &files, &dirs); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "move_to_trash"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "delete"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "open"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (selected > 0) && (selected == files)); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "rename"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected == 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "open_in_terminal"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected == 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "new_folder"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected <= 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "new_file"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected <= 1); +} + +static gboolean +on_entry_filter_activate (GeditFileBrowserWidget *obj) +{ + gchar const *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 = 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; + GFile *location; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + + if (!(location = gedit_file_bookmarks_store_get_location (obj->priv->bookmarks_store, &iter))) + return; + + g_hash_table_remove (obj->priv->bookmarks_hash, location); + + g_object_unref (location); +} + +static void +on_filter_mode_changed (GeditFileBrowserStore *model, + GParamSpec *param, + GeditFileBrowserWidget *obj) +{ + gint mode = gedit_file_browser_store_get_filter_mode (model); + GAction *action; + GVariant *variant; + gboolean active; + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_hidden"); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); + variant = g_action_get_state (action); + + if (active != g_variant_get_boolean (variant)) + g_action_change_state (action, g_variant_new_boolean (active)); + + g_variant_unref (variant); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_binary"); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); + variant = g_action_get_state (action); + + if (active != g_variant_get_boolean (variant)) + g_action_change_state (action, g_variant_new_boolean (active)); + + g_variant_unref (variant); +} + +static void +next_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gedit_file_browser_widget_history_forward (GEDIT_FILE_BROWSER_WIDGET (user_data)); +} + +static void +previous_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gedit_file_browser_widget_history_back (GEDIT_FILE_BROWSER_WIDGET (user_data)); +} + +static void +up_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->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 +home_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GFile *home_location; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + home_location = g_file_new_for_path (g_get_home_dir ()); + gedit_file_browser_widget_set_root (widget, home_location, TRUE); + + g_object_unref (home_location); +} + +static void +new_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeIter parent; + GtkTreeIter iter; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (widget, &parent)) + return; + + if (gedit_file_browser_store_new_directory (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) + gedit_file_browser_view_start_rename (widget->priv->treeview, &iter); +} + +static void +open_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->treeview)); + GList *rows; + GList *row; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + for (row = rows; row; row = row->next) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (model, &iter, path)) + file_open (widget, model, &iter); + + gtk_tree_path_free (path); + } + + g_list_free (rows); +} + +static void +new_file_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeIter parent; + GtkTreeIter iter; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (widget, &parent)) + return; + + if (gedit_file_browser_store_new_file (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) + gedit_file_browser_view_start_rename (widget->priv->treeview, &iter); +} + +static void +rename_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + rename_selected_file (widget); +} + +static void +delete_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + delete_selected_files (widget, FALSE); +} + +static void +move_to_trash_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + delete_selected_files (widget, TRUE); +} + +static void +refresh_view_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + gedit_file_browser_widget_refresh (widget); +} + +static void +view_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeIter iter; + GList *rows; + GList *row; + gboolean directory_opened = FALSE; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + for (row = rows; row; row = row->next) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + directory_opened |= directory_open (widget, model, &iter); + + gtk_tree_path_free (path); + } + + if (!directory_opened && gedit_file_browser_widget_get_selected_directory (widget, &iter)) + directory_open (widget, model, &iter); + + g_list_free (rows); +} + +static void +change_show_hidden_state (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + update_filter_mode (widget, + action, + state, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); +} + +static void +change_show_binary_state (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + update_filter_mode (widget, + action, + state, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); +} + +static void +change_show_match_filename (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + gboolean visible = g_variant_get_boolean (state); + + gtk_revealer_set_reveal_child (GTK_REVEALER (widget->priv->filter_entry_revealer), visible); + + if (visible) + gtk_widget_grab_focus (widget->priv->filter_entry); + else + /* clear the filter */ + set_filter_pattern_real (widget, NULL, TRUE); + + g_simple_action_set_state (action, state); +} + +static void +open_in_terminal_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeIter iter; + GFile *file; + + /* Get the current directory */ + if (!gedit_file_browser_widget_get_selected_directory (widget, &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->file_store), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &file, + -1); + + g_signal_emit (widget, signals[OPEN_IN_TERMINAL], 0, file); + + g_object_unref (file); +} + +static void +set_active_root_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + g_signal_emit (widget, signals[SET_ACTIVE_ROOT], 0); +} + +void +_gedit_file_browser_widget_register_type (GTypeModule *type_module) +{ + gedit_file_browser_widget_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-widget.h b/plugins/filebrowser/gedit-file-browser-widget.h new file mode 100644 index 0000000..8da8312 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.h @@ -0,0 +1,126 @@ +/* + * gedit-file-browser-widget.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_FILE_BROWSER_WIDGET_H +#define GEDIT_FILE_BROWSER_WIDGET_H + +#include <gtk/gtk.h> +#include <gedit/gedit-menu-extension.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 +{ + GtkBox parent; + + GeditFileBrowserWidgetPrivate *priv; +}; + +struct _GeditFileBrowserWidgetClass +{ + GtkBoxClass parent_class; + + /* Signals */ + void (* location_activated) (GeditFileBrowserWidget *widget, + GFile *location); + 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); + void (* open_in_terminal) (GeditFileBrowserWidget *widget, + GFile *location); + void (* set_active_root) (GeditFileBrowserWidget *widget); +}; + +GType gedit_file_browser_widget_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_file_browser_widget_new (void); + +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, + GFile *root, + gboolean virtual_root); +void gedit_file_browser_widget_set_root_and_virtual_root + (GeditFileBrowserWidget *obj, + GFile *root, + GFile *virtual_root); + +gboolean gedit_file_browser_widget_get_selected_directory + (GeditFileBrowserWidget *obj, + GtkTreeIter *iter); + +void gedit_file_browser_widget_set_active_root_enabled (GeditFileBrowserWidget *widget, + gboolean enabled); + +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); + +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); +GeditMenuExtension * + gedit_file_browser_widget_extend_context_menu (GeditFileBrowserWidget *obj); +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); + +void _gedit_file_browser_widget_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_WIDGET_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/meson.build b/plugins/filebrowser/meson.build new file mode 100644 index 0000000..ba66652 --- /dev/null +++ b/plugins/filebrowser/meson.build @@ -0,0 +1,127 @@ +libfilebrowser_public_h = files( + 'gedit-file-bookmarks-store.h', + 'gedit-file-browser-error.h', + 'gedit-file-browser-store.h', + 'gedit-file-browser-view.h', + 'gedit-file-browser-widget.h', + 'gedit-file-browser-utils.h', + 'gedit-file-browser-plugin.h', + 'gedit-file-browser-messages.h', +) + +libfilebrowser_sources = files( + 'gedit-file-bookmarks-store.c', + 'gedit-file-browser-messages.c', + 'gedit-file-browser-plugin.c', + 'gedit-file-browser-store.c', + 'gedit-file-browser-utils.c', + 'gedit-file-browser-view.c', + 'gedit-file-browser-widget.c', +) + +libfilebrowser_deps = [ + libgedit_dep, +] + +subdir('messages') + +libfilebrowser_register_enums = gnome.mkenums( + 'gedit-file-browser-enum-register', + sources: libfilebrowser_public_h, + c_template: 'gedit-file-browser-enum-register.c.template', +) + +libfilebrowser_type_enums = gnome.mkenums( + 'gedit-file-browser-enum-types', + depends : [libfilebrowser_register_enums], + sources: libfilebrowser_public_h, + h_template: 'gedit-file-browser-enum-types.h.template', + c_template: 'gedit-file-browser-enum-types-stage1.c.template', +) + +# cat won't work on Windows so this +# will need to be reimplemented as a script +cat = find_program('cat') + +# Combine the 2 C mkenums templates together before compiling +libfilebrowser_enums_c = custom_target('libfilebrowser_enums_c', + input: [libfilebrowser_type_enums.get(0), + libfilebrowser_register_enums], + output: 'gedit-file-browser-enum-types.c', + command: [cat, '@INPUT0@', '@INPUT1@'], + # redirects the command output since we can't use >> here + capture: true, +) + +libfilebrowser_sources += [ + libfilebrowser_enums_c, + libfilebrowser_type_enums.get(1), +] + +subdir('resources') + +libfilebrowser_sha = shared_module( + 'filebrowser', + sources: libfilebrowser_sources, + include_directories: root_include_dir, + dependencies: libfilebrowser_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +# FIXME: https://github.com/mesonbuild/meson/issues/1687 +custom_target( + 'org.gnome.gedit.plugins.filebrowser.enums.xml', + input : libfilebrowser_sources + libfilebrowser_public_h, + output: 'org.gnome.gedit.plugins.filebrowser.enums.xml', + capture: true, + command: [ + 'glib-mkenums', + '--comments', '<!-- @comment@ -->', + '--fhead', '<schemalist>', + '--vhead', ' <@type@ id="org.gnome.gedit.plugins.filebrowser.@EnumName@">', + '--vprod', ' <value nick="@valuenick@" value="@valuenum@"/>', + '--vtail', ' </@type@>', + '--ftail', '</schemalist>', + '@INPUT@' + ], + install: true, + install_dir: join_paths( + glibdir, + 'schemas', + ) +) + +filebrowser_gschema_file = files('org.gnome.gedit.plugins.filebrowser.gschema.xml') +install_data( + filebrowser_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-filebrowser-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + filebrowser_gschema_file, + ] + ) +endif + +custom_target( + 'filebrowser.plugin', + input: 'filebrowser.plugin.desktop.in', + output: 'filebrowser.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/filebrowser/messages.xml b/plugins/filebrowser/messages.xml new file mode 100644 index 0000000..e2b7137 --- /dev/null +++ b/plugins/filebrowser/messages.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<messages> + <message namespace="Gedit" name="FileBrowserMessageGetRoot"> + <include>gio/gio.h</include> + <property name="location" type="object" gtype="G_TYPE_FILE"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageSetRoot"> + <include>gio/gio.h</include> + <property name="location" type="object" gtype="G_TYPE_FILE"/> + <property name="virtual" type="string"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageSetEmblem"> + <property name="id" type="string"/> + <property name="emblem" type="string"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageSetMarkup"> + <property name="id" type="string"/> + <property name="markup" type="string"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageAddFilter"> + <property name="object_path" type="string"/> + <property name="method" type="string"/> + <property name="id" type="uint"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageId"> + <property name="id" type="uint"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageExtendContextMenu"> + <include system="yes">gedit/gedit-menu-extension.h</include> + <property name="extension" type="object" gtype="GEDIT_TYPE_MENU_EXTENSION"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageActivation"> + <property name="active" type="boolean"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageGetView"> + <include>plugins/filebrowser/gedit-file-browser-view.h</include> + <property name="view" type="object" gtype="GEDIT_TYPE_FILE_BROWSER_VIEW"/> + </message> + <message namespace="Gedit" name="FileBrowserMessageIdLocation"> + <include>gio/gio.h</include> + <property name="id" type="string"/> + <property name="name" type="string"/> + <property name="location" type="object" gtype="G_TYPE_FILE"/> + <property name="is-directory" type="boolean"/> + </message> +</messages> +<!-- vi:ex:ts=2:et --> diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-activation.c b/plugins/filebrowser/messages/gedit-file-browser-message-activation.c new file mode 100644 index 0000000..0138e0d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-activation.c @@ -0,0 +1,105 @@ + +/* + * gedit-file-browser-message-activation.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-activation.h" + +enum +{ + PROP_0, + + PROP_ACTIVE, +}; + +struct _GeditFileBrowserMessageActivationPrivate +{ + gboolean active; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageActivation, + gedit_file_browser_message_activation, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageActivation)) + +static void +gedit_file_browser_message_activation_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageActivation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION (obj); + + switch (prop_id) + { + case PROP_ACTIVE: + g_value_set_boolean (value, msg->priv->active); + break; + } +} + +static void +gedit_file_browser_message_activation_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageActivation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION (obj); + + switch (prop_id) + { + case PROP_ACTIVE: + msg->priv->active = g_value_get_boolean (value); + break; + } +} + +static void +gedit_file_browser_message_activation_class_init (GeditFileBrowserMessageActivationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = gedit_file_browser_message_activation_get_property; + object_class->set_property = gedit_file_browser_message_activation_set_property; + + g_object_class_install_property (object_class, + PROP_ACTIVE, + g_param_spec_boolean ("active", + "Active", + "Active", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_activation_init (GeditFileBrowserMessageActivation *message) +{ + message->priv = gedit_file_browser_message_activation_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-activation.h b/plugins/filebrowser/messages/gedit-file-browser-message-activation.h new file mode 100644 index 0000000..bd3b1ed --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-activation.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-activation.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION (gedit_file_browser_message_activation_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivation)) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivation const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivationClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ACTIVATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ACTIVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION)) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivationClass)) + +typedef struct _GeditFileBrowserMessageActivation GeditFileBrowserMessageActivation; +typedef struct _GeditFileBrowserMessageActivationClass GeditFileBrowserMessageActivationClass; +typedef struct _GeditFileBrowserMessageActivationPrivate GeditFileBrowserMessageActivationPrivate; + +struct _GeditFileBrowserMessageActivation +{ + GeditMessage parent; + + GeditFileBrowserMessageActivationPrivate *priv; +}; + +struct _GeditFileBrowserMessageActivationClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_activation_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c new file mode 100644 index 0000000..f66a32d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c @@ -0,0 +1,162 @@ + +/* + * gedit-file-browser-message-add-filter.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-add-filter.h" + +enum +{ + PROP_0, + + PROP_OBJECT_PATH, + PROP_METHOD, + PROP_ID, +}; + +struct _GeditFileBrowserMessageAddFilterPrivate +{ + gchar *object_path; + gchar *method; + guint id; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageAddFilter, + gedit_file_browser_message_add_filter, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageAddFilter)) + +static void +gedit_file_browser_message_add_filter_finalize (GObject *obj) +{ + GeditFileBrowserMessageAddFilter *msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj); + + g_free (msg->priv->object_path); + g_free (msg->priv->method); + + G_OBJECT_CLASS (gedit_file_browser_message_add_filter_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_add_filter_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageAddFilter *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, msg->priv->object_path); + break; + case PROP_METHOD: + g_value_set_string (value, msg->priv->method); + break; + case PROP_ID: + g_value_set_uint (value, msg->priv->id); + break; + } +} + +static void +gedit_file_browser_message_add_filter_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageAddFilter *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + { + g_free (msg->priv->object_path); + msg->priv->object_path = g_value_dup_string (value); + break; + } + case PROP_METHOD: + { + g_free (msg->priv->method); + msg->priv->method = g_value_dup_string (value); + break; + } + case PROP_ID: + msg->priv->id = g_value_get_uint (value); + break; + } +} + +static void +gedit_file_browser_message_add_filter_class_init (GeditFileBrowserMessageAddFilterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_add_filter_finalize; + + object_class->get_property = gedit_file_browser_message_add_filter_get_property; + object_class->set_property = gedit_file_browser_message_add_filter_set_property; + + g_object_class_install_property (object_class, + PROP_OBJECT_PATH, + g_param_spec_string ("object-path", + "Object Path", + "Object Path", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_METHOD, + g_param_spec_string ("method", + "Method", + "Method", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_uint ("id", + "Id", + "Id", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_add_filter_init (GeditFileBrowserMessageAddFilter *message) +{ + message->priv = gedit_file_browser_message_add_filter_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h new file mode 100644 index 0000000..4a5b35c --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-add-filter.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER (gedit_file_browser_message_add_filter_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilter)) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilter const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilterClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ADD_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ADD_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER)) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilterClass)) + +typedef struct _GeditFileBrowserMessageAddFilter GeditFileBrowserMessageAddFilter; +typedef struct _GeditFileBrowserMessageAddFilterClass GeditFileBrowserMessageAddFilterClass; +typedef struct _GeditFileBrowserMessageAddFilterPrivate GeditFileBrowserMessageAddFilterPrivate; + +struct _GeditFileBrowserMessageAddFilter +{ + GeditMessage parent; + + GeditFileBrowserMessageAddFilterPrivate *priv; +}; + +struct _GeditFileBrowserMessageAddFilterClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_add_filter_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c new file mode 100644 index 0000000..4bb8682 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c @@ -0,0 +1,128 @@ + +/* + * gedit-file-browser-message-extend-context-menu.c + * This file is part of gedit + * + * Copyright (C) 2014 - Paolo Borelli + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-extend-context-menu.h" +#include <gedit/gedit-menu-extension.h> + +enum +{ + PROP_0, + + PROP_EXTENSION, +}; + +struct _GeditFileBrowserMessageExtendContextMenuPrivate +{ + GeditMenuExtension *extension; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageExtendContextMenu, + gedit_file_browser_message_extend_context_menu, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageExtendContextMenu)) + +static void +gedit_file_browser_message_extend_context_menu_finalize (GObject *obj) +{ + GeditFileBrowserMessageExtendContextMenu *msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj); + + if (msg->priv->extension) + { + g_object_unref (msg->priv->extension); + } + + G_OBJECT_CLASS (gedit_file_browser_message_extend_context_menu_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_extend_context_menu_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageExtendContextMenu *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj); + + switch (prop_id) + { + case PROP_EXTENSION: + g_value_set_object (value, msg->priv->extension); + break; + } +} + +static void +gedit_file_browser_message_extend_context_menu_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageExtendContextMenu *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj); + + switch (prop_id) + { + case PROP_EXTENSION: + { + if (msg->priv->extension) + { + g_object_unref (msg->priv->extension); + } + msg->priv->extension = g_value_dup_object (value); + break; + } + } +} + +static void +gedit_file_browser_message_extend_context_menu_class_init (GeditFileBrowserMessageExtendContextMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_extend_context_menu_finalize; + + object_class->get_property = gedit_file_browser_message_extend_context_menu_get_property; + object_class->set_property = gedit_file_browser_message_extend_context_menu_set_property; + + g_object_class_install_property (object_class, + PROP_EXTENSION, + g_param_spec_object ("extension", + "Extension", + "Extension", + GEDIT_TYPE_MENU_EXTENSION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_extend_context_menu_init (GeditFileBrowserMessageExtendContextMenu *message) +{ + message->priv = gedit_file_browser_message_extend_context_menu_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h new file mode 100644 index 0000000..643485d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h @@ -0,0 +1,70 @@ + +/* + * gedit-file-browser-message-extend-context-menu.h + * This file is part of gedit + * + * Copyright (C) 2014 - Paolo Borelli + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (gedit_file_browser_message_extend_context_menu_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenu)) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenu const)) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenuClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU)) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenuClass)) + +typedef struct _GeditFileBrowserMessageExtendContextMenu GeditFileBrowserMessageExtendContextMenu; +typedef struct _GeditFileBrowserMessageExtendContextMenuClass GeditFileBrowserMessageExtendContextMenuClass; +typedef struct _GeditFileBrowserMessageExtendContextMenuPrivate GeditFileBrowserMessageExtendContextMenuPrivate; + +struct _GeditFileBrowserMessageExtendContextMenu +{ + GeditMessage parent; + + GeditFileBrowserMessageExtendContextMenuPrivate *priv; +}; + +struct _GeditFileBrowserMessageExtendContextMenuClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_extend_context_menu_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c new file mode 100644 index 0000000..b766ac1 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c @@ -0,0 +1,127 @@ + +/* + * gedit-file-browser-message-get-root.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-get-root.h" +#include "gio/gio.h" + +enum +{ + PROP_0, + + PROP_LOCATION, +}; + +struct _GeditFileBrowserMessageGetRootPrivate +{ + GFile *location; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageGetRoot, + gedit_file_browser_message_get_root, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageGetRoot)) + +static void +gedit_file_browser_message_get_root_finalize (GObject *obj) +{ + GeditFileBrowserMessageGetRoot *msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj); + + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + + G_OBJECT_CLASS (gedit_file_browser_message_get_root_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_get_root_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + g_value_set_object (value, msg->priv->location); + break; + } +} + +static void +gedit_file_browser_message_get_root_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + { + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + msg->priv->location = g_value_dup_object (value); + break; + } + } +} + +static void +gedit_file_browser_message_get_root_class_init (GeditFileBrowserMessageGetRootClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_get_root_finalize; + + object_class->get_property = gedit_file_browser_message_get_root_get_property; + object_class->set_property = gedit_file_browser_message_get_root_set_property; + + g_object_class_install_property (object_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location", + "Location", + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_get_root_init (GeditFileBrowserMessageGetRoot *message) +{ + message->priv = gedit_file_browser_message_get_root_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h new file mode 100644 index 0000000..8cc114d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-get-root.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT (gedit_file_browser_message_get_root_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRoot)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRoot const)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRootClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRootClass)) + +typedef struct _GeditFileBrowserMessageGetRoot GeditFileBrowserMessageGetRoot; +typedef struct _GeditFileBrowserMessageGetRootClass GeditFileBrowserMessageGetRootClass; +typedef struct _GeditFileBrowserMessageGetRootPrivate GeditFileBrowserMessageGetRootPrivate; + +struct _GeditFileBrowserMessageGetRoot +{ + GeditMessage parent; + + GeditFileBrowserMessageGetRootPrivate *priv; +}; + +struct _GeditFileBrowserMessageGetRootClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_get_root_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c new file mode 100644 index 0000000..17fe757 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c @@ -0,0 +1,127 @@ + +/* + * gedit-file-browser-message-get-view.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-get-view.h" +#include "plugins/filebrowser/gedit-file-browser-view.h" + +enum +{ + PROP_0, + + PROP_VIEW, +}; + +struct _GeditFileBrowserMessageGetViewPrivate +{ + GeditFileBrowserView *view; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageGetView, + gedit_file_browser_message_get_view, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageGetView)) + +static void +gedit_file_browser_message_get_view_finalize (GObject *obj) +{ + GeditFileBrowserMessageGetView *msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj); + + if (msg->priv->view) + { + g_object_unref (msg->priv->view); + } + + G_OBJECT_CLASS (gedit_file_browser_message_get_view_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_get_view_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetView *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj); + + switch (prop_id) + { + case PROP_VIEW: + g_value_set_object (value, msg->priv->view); + break; + } +} + +static void +gedit_file_browser_message_get_view_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetView *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj); + + switch (prop_id) + { + case PROP_VIEW: + { + if (msg->priv->view) + { + g_object_unref (msg->priv->view); + } + msg->priv->view = g_value_dup_object (value); + break; + } + } +} + +static void +gedit_file_browser_message_get_view_class_init (GeditFileBrowserMessageGetViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_get_view_finalize; + + object_class->get_property = gedit_file_browser_message_get_view_get_property; + object_class->set_property = gedit_file_browser_message_get_view_set_property; + + g_object_class_install_property (object_class, + PROP_VIEW, + g_param_spec_object ("view", + "View", + "View", + GEDIT_TYPE_FILE_BROWSER_VIEW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_get_view_init (GeditFileBrowserMessageGetView *message) +{ + message->priv = gedit_file_browser_message_get_view_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h new file mode 100644 index 0000000..452abbc --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-get-view.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW (gedit_file_browser_message_get_view_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetView)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetView const)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetViewClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetViewClass)) + +typedef struct _GeditFileBrowserMessageGetView GeditFileBrowserMessageGetView; +typedef struct _GeditFileBrowserMessageGetViewClass GeditFileBrowserMessageGetViewClass; +typedef struct _GeditFileBrowserMessageGetViewPrivate GeditFileBrowserMessageGetViewPrivate; + +struct _GeditFileBrowserMessageGetView +{ + GeditMessage parent; + + GeditFileBrowserMessageGetViewPrivate *priv; +}; + +struct _GeditFileBrowserMessageGetViewClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_get_view_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c new file mode 100644 index 0000000..36c33c5 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c @@ -0,0 +1,190 @@ + +/* + * gedit-file-browser-message-id-location.c + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-id-location.h" +#include "gio/gio.h" + +enum +{ + PROP_0, + + PROP_ID, + PROP_NAME, + PROP_LOCATION, + PROP_IS_DIRECTORY, +}; + +struct _GeditFileBrowserMessageIdLocationPrivate +{ + gchar *id; + gchar *name; + GFile *location; + gboolean is_directory; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageIdLocation, + gedit_file_browser_message_id_location, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageIdLocation)) + +static void +gedit_file_browser_message_id_location_finalize (GObject *obj) +{ + GeditFileBrowserMessageIdLocation *msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj); + + g_free (msg->priv->id); + g_free (msg->priv->name); + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + + G_OBJECT_CLASS (gedit_file_browser_message_id_location_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_id_location_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageIdLocation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, msg->priv->id); + break; + case PROP_NAME: + g_value_set_string (value, msg->priv->name); + break; + case PROP_LOCATION: + g_value_set_object (value, msg->priv->location); + break; + case PROP_IS_DIRECTORY: + g_value_set_boolean (value, msg->priv->is_directory); + break; + } +} + +static void +gedit_file_browser_message_id_location_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageIdLocation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj); + + switch (prop_id) + { + case PROP_ID: + { + g_free (msg->priv->id); + msg->priv->id = g_value_dup_string (value); + break; + } + case PROP_NAME: + { + g_free (msg->priv->name); + msg->priv->name = g_value_dup_string (value); + break; + } + case PROP_LOCATION: + { + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + msg->priv->location = g_value_dup_object (value); + break; + } + case PROP_IS_DIRECTORY: + msg->priv->is_directory = g_value_get_boolean (value); + break; + } +} + +static void +gedit_file_browser_message_id_location_class_init (GeditFileBrowserMessageIdLocationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_id_location_finalize; + + object_class->get_property = gedit_file_browser_message_id_location_get_property; + object_class->set_property = gedit_file_browser_message_id_location_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "Id", + "Id", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location", + "Location", + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_IS_DIRECTORY, + g_param_spec_boolean ("is-directory", + "Is Directory", + "Is Directory", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_id_location_init (GeditFileBrowserMessageIdLocation *message) +{ + message->priv = gedit_file_browser_message_id_location_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h new file mode 100644 index 0000000..0af4c77 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h @@ -0,0 +1,70 @@ + +/* + * gedit-file-browser-message-id-location.h + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION (gedit_file_browser_message_id_location_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocation)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocation const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocationClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocationClass)) + +typedef struct _GeditFileBrowserMessageIdLocation GeditFileBrowserMessageIdLocation; +typedef struct _GeditFileBrowserMessageIdLocationClass GeditFileBrowserMessageIdLocationClass; +typedef struct _GeditFileBrowserMessageIdLocationPrivate GeditFileBrowserMessageIdLocationPrivate; + +struct _GeditFileBrowserMessageIdLocation +{ + GeditMessage parent; + + GeditFileBrowserMessageIdLocationPrivate *priv; +}; + +struct _GeditFileBrowserMessageIdLocationClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_id_location_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id.c b/plugins/filebrowser/messages/gedit-file-browser-message-id.c new file mode 100644 index 0000000..fec490f --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id.c @@ -0,0 +1,107 @@ + +/* + * gedit-file-browser-message-id.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-id.h" + +enum +{ + PROP_0, + + PROP_ID, +}; + +struct _GeditFileBrowserMessageIdPrivate +{ + guint id; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageId, + gedit_file_browser_message_id, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageId)) + +static void +gedit_file_browser_message_id_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageId *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_uint (value, msg->priv->id); + break; + } +} + +static void +gedit_file_browser_message_id_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageId *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID (obj); + + switch (prop_id) + { + case PROP_ID: + msg->priv->id = g_value_get_uint (value); + break; + } +} + +static void +gedit_file_browser_message_id_class_init (GeditFileBrowserMessageIdClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = gedit_file_browser_message_id_get_property; + object_class->set_property = gedit_file_browser_message_id_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_uint ("id", + "Id", + "Id", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_id_init (GeditFileBrowserMessageId *message) +{ + message->priv = gedit_file_browser_message_id_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id.h b/plugins/filebrowser/messages/gedit-file-browser-message-id.h new file mode 100644 index 0000000..8351c3b --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-id.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ID_H +#define GEDIT_FILE_BROWSER_MESSAGE_ID_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID (gedit_file_browser_message_id_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageId)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageId const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageIdClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageIdClass)) + +typedef struct _GeditFileBrowserMessageId GeditFileBrowserMessageId; +typedef struct _GeditFileBrowserMessageIdClass GeditFileBrowserMessageIdClass; +typedef struct _GeditFileBrowserMessageIdPrivate GeditFileBrowserMessageIdPrivate; + +struct _GeditFileBrowserMessageId +{ + GeditMessage parent; + + GeditFileBrowserMessageIdPrivate *priv; +}; + +struct _GeditFileBrowserMessageIdClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_id_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ID_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c new file mode 100644 index 0000000..8e63042 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c @@ -0,0 +1,142 @@ + +/* + * gedit-file-browser-message-set-emblem.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-set-emblem.h" + +enum +{ + PROP_0, + + PROP_ID, + PROP_EMBLEM, +}; + +struct _GeditFileBrowserMessageSetEmblemPrivate +{ + gchar *id; + gchar *emblem; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetEmblem, + gedit_file_browser_message_set_emblem, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageSetEmblem)) + +static void +gedit_file_browser_message_set_emblem_finalize (GObject *obj) +{ + GeditFileBrowserMessageSetEmblem *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj); + + g_free (msg->priv->id); + g_free (msg->priv->emblem); + + G_OBJECT_CLASS (gedit_file_browser_message_set_emblem_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_set_emblem_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetEmblem *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, msg->priv->id); + break; + case PROP_EMBLEM: + g_value_set_string (value, msg->priv->emblem); + break; + } +} + +static void +gedit_file_browser_message_set_emblem_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetEmblem *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj); + + switch (prop_id) + { + case PROP_ID: + { + g_free (msg->priv->id); + msg->priv->id = g_value_dup_string (value); + break; + } + case PROP_EMBLEM: + { + g_free (msg->priv->emblem); + msg->priv->emblem = g_value_dup_string (value); + break; + } + } +} + +static void +gedit_file_browser_message_set_emblem_class_init (GeditFileBrowserMessageSetEmblemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_set_emblem_finalize; + + object_class->get_property = gedit_file_browser_message_set_emblem_get_property; + object_class->set_property = gedit_file_browser_message_set_emblem_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "Id", + "Id", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_EMBLEM, + g_param_spec_string ("emblem", + "Emblem", + "Emblem", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_set_emblem_init (GeditFileBrowserMessageSetEmblem *message) +{ + message->priv = gedit_file_browser_message_set_emblem_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h new file mode 100644 index 0000000..6daa795 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-set-emblem.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM (gedit_file_browser_message_set_emblem_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblem)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblem const)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblemClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_EMBLEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_EMBLEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblemClass)) + +typedef struct _GeditFileBrowserMessageSetEmblem GeditFileBrowserMessageSetEmblem; +typedef struct _GeditFileBrowserMessageSetEmblemClass GeditFileBrowserMessageSetEmblemClass; +typedef struct _GeditFileBrowserMessageSetEmblemPrivate GeditFileBrowserMessageSetEmblemPrivate; + +struct _GeditFileBrowserMessageSetEmblem +{ + GeditMessage parent; + + GeditFileBrowserMessageSetEmblemPrivate *priv; +}; + +struct _GeditFileBrowserMessageSetEmblemClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_set_emblem_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c new file mode 100644 index 0000000..3b14d16 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c @@ -0,0 +1,143 @@ + +/* + * gedit-file-browser-message-set-markup.c + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-set-markup.h" + +enum +{ + PROP_0, + + PROP_ID, + PROP_MARKUP, +}; + +struct _GeditFileBrowserMessageSetMarkupPrivate +{ + gchar *id; + gchar *markup; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetMarkup, + gedit_file_browser_message_set_markup, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageSetMarkup)) + +static void +gedit_file_browser_message_set_markup_finalize (GObject *obj) +{ + GeditFileBrowserMessageSetMarkup *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj); + + g_free (msg->priv->id); + g_free (msg->priv->markup); + + G_OBJECT_CLASS (gedit_file_browser_message_set_markup_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_set_markup_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetMarkup *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, msg->priv->id); + break; + case PROP_MARKUP: + g_value_set_string (value, msg->priv->markup); + break; + } +} + +static void +gedit_file_browser_message_set_markup_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetMarkup *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj); + + switch (prop_id) + { + case PROP_ID: + { + g_free (msg->priv->id); + msg->priv->id = g_value_dup_string (value); + break; + } + case PROP_MARKUP: + { + g_free (msg->priv->markup); + msg->priv->markup = g_value_dup_string (value); + break; + } + } +} + +static void +gedit_file_browser_message_set_markup_class_init (GeditFileBrowserMessageSetMarkupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_set_markup_finalize; + + object_class->get_property = gedit_file_browser_message_set_markup_get_property; + object_class->set_property = gedit_file_browser_message_set_markup_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "Id", + "Id", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_MARKUP, + g_param_spec_string ("markup", + "Markup", + "Markup", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_set_markup_init (GeditFileBrowserMessageSetMarkup *message) +{ + message->priv = gedit_file_browser_message_set_markup_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h new file mode 100644 index 0000000..ab9a1dc --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h @@ -0,0 +1,70 @@ + +/* + * gedit-file-browser-message-set-markup.h + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP (gedit_file_browser_message_set_markup_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkup)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkup const)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkupClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_MARKUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_MARKUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkupClass)) + +typedef struct _GeditFileBrowserMessageSetMarkup GeditFileBrowserMessageSetMarkup; +typedef struct _GeditFileBrowserMessageSetMarkupClass GeditFileBrowserMessageSetMarkupClass; +typedef struct _GeditFileBrowserMessageSetMarkupPrivate GeditFileBrowserMessageSetMarkupPrivate; + +struct _GeditFileBrowserMessageSetMarkup +{ + GeditMessage parent; + + GeditFileBrowserMessageSetMarkupPrivate *priv; +}; + +struct _GeditFileBrowserMessageSetMarkupClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_set_markup_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c new file mode 100644 index 0000000..cb3891b --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c @@ -0,0 +1,149 @@ + +/* + * gedit-file-browser-message-set-root.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "gedit-file-browser-message-set-root.h" +#include "gio/gio.h" + +enum +{ + PROP_0, + + PROP_LOCATION, + PROP_VIRTUAL, +}; + +struct _GeditFileBrowserMessageSetRootPrivate +{ + GFile *location; + gchar *virtual; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetRoot, + gedit_file_browser_message_set_root, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageSetRoot)) + +static void +gedit_file_browser_message_set_root_finalize (GObject *obj) +{ + GeditFileBrowserMessageSetRoot *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj); + + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + g_free (msg->priv->virtual); + + G_OBJECT_CLASS (gedit_file_browser_message_set_root_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_set_root_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + g_value_set_object (value, msg->priv->location); + break; + case PROP_VIRTUAL: + g_value_set_string (value, msg->priv->virtual); + break; + } +} + +static void +gedit_file_browser_message_set_root_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + { + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + msg->priv->location = g_value_dup_object (value); + break; + } + case PROP_VIRTUAL: + { + g_free (msg->priv->virtual); + msg->priv->virtual = g_value_dup_string (value); + break; + } + } +} + +static void +gedit_file_browser_message_set_root_class_init (GeditFileBrowserMessageSetRootClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_set_root_finalize; + + object_class->get_property = gedit_file_browser_message_set_root_get_property; + object_class->set_property = gedit_file_browser_message_set_root_set_property; + + g_object_class_install_property (object_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location", + "Location", + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_VIRTUAL, + g_param_spec_string ("virtual", + "Virtual", + "Virtual", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_set_root_init (GeditFileBrowserMessageSetRoot *message) +{ + message->priv = gedit_file_browser_message_set_root_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h new file mode 100644 index 0000000..8b61c50 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-set-root.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H + +#include <gedit/gedit-message.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT (gedit_file_browser_message_set_root_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRoot)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRoot const)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRootClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRootClass)) + +typedef struct _GeditFileBrowserMessageSetRoot GeditFileBrowserMessageSetRoot; +typedef struct _GeditFileBrowserMessageSetRootClass GeditFileBrowserMessageSetRootClass; +typedef struct _GeditFileBrowserMessageSetRootPrivate GeditFileBrowserMessageSetRootPrivate; + +struct _GeditFileBrowserMessageSetRoot +{ + GeditMessage parent; + + GeditFileBrowserMessageSetRootPrivate *priv; +}; + +struct _GeditFileBrowserMessageSetRootClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_set_root_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H */ diff --git a/plugins/filebrowser/messages/meson.build b/plugins/filebrowser/messages/meson.build new file mode 100644 index 0000000..ec4fb84 --- /dev/null +++ b/plugins/filebrowser/messages/meson.build @@ -0,0 +1,25 @@ +libfilebrowser_public_h += files( + 'gedit-file-browser-message-activation.h', + 'gedit-file-browser-message-add-filter.h', + 'gedit-file-browser-message-extend-context-menu.h', + 'gedit-file-browser-message-get-root.h', + 'gedit-file-browser-message-get-view.h', + 'gedit-file-browser-message-id.h', + 'gedit-file-browser-message-id-location.h', + 'gedit-file-browser-message-set-emblem.h', + 'gedit-file-browser-message-set-markup.h', + 'gedit-file-browser-message-set-root.h', +) + +libfilebrowser_sources += files( + 'gedit-file-browser-message-activation.c', + 'gedit-file-browser-message-add-filter.c', + 'gedit-file-browser-message-extend-context-menu.c', + 'gedit-file-browser-message-get-root.c', + 'gedit-file-browser-message-get-view.c', + 'gedit-file-browser-message-id.c', + 'gedit-file-browser-message-id-location.c', + 'gedit-file-browser-message-set-emblem.c', + 'gedit-file-browser-message-set-markup.c', + 'gedit-file-browser-message-set-root.c', +) diff --git a/plugins/filebrowser/messages/messages.h b/plugins/filebrowser/messages/messages.h new file mode 100644 index 0000000..4dc0f51 --- /dev/null +++ b/plugins/filebrowser/messages/messages.h @@ -0,0 +1,16 @@ +#ifndef GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H +#define GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H + +#include "gedit-file-browser-message-activation.h" +#include "gedit-file-browser-message-add-filter.h" +#include "gedit-file-browser-message-extend-context-menu.h" +#include "gedit-file-browser-message-get-root.h" +#include "gedit-file-browser-message-get-view.h" +#include "gedit-file-browser-message-id.h" +#include "gedit-file-browser-message-id-location.h" +#include "gedit-file-browser-message-set-emblem.h" +#include "gedit-file-browser-message-set-markup.h" +#include "gedit-file-browser-message-set-root.h" + +#endif /* GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H */ + diff --git a/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml b/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml new file mode 100644 index 0000000..9e9d856 --- /dev/null +++ b/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml @@ -0,0 +1,58 @@ +<schemalist gettext-domain="gedit"> + <schema id="org.gnome.gedit.plugins.filebrowser" path="/org/gnome/gedit/plugins/filebrowser/"> + <key name="tree-view" type="b"> + <default>true</default> + <summary>Open With Tree View</summary> + <description>Open the tree view when the file browser plugin gets loaded instead of the bookmarks view</description> + </key> + <key name="root" type="s"> + <default>''</default> + <summary>File Browser Root Directory</summary> + <description>The file browser root directory to use when loading the file browser plugin and onload/tree_view is TRUE.</description> + </key> + <key name="virtual-root" type="s"> + <default>''</default> + <summary>File Browser Virtual Root Directory</summary> + <description>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.</description> + </key> + <key name="enable-remote" type="b"> + <default>false</default> + <summary>Enable Restore of Remote Locations</summary> + <description>Sets whether to enable restoring of remote locations.</description> + </key> + <key name="open-at-first-doc" type="b"> + <default>true</default> + <summary>Set Location to First Document</summary> + <description>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 Nautilus, etc.)</description> + </key> + <key name="filter-mode" flags="org.gnome.gedit.plugins.filebrowser.GeditFileBrowserStoreFilterMode"> + <default>['hide-hidden', 'hide-binary']</default> + <summary>File Browser Filter Mode</summary> + <description>This value determines what files get filtered from the file browser. Valid values are: none (filter nothing), hide-hidden (filter hidden files) and hide-binary (filter binary files).</description> + </key> + <key name="filter-pattern" type="s"> + <default>''</default> + <summary>File Browser Filter Pattern</summary> + <description>The filter pattern to filter the file browser with. This filter works on top of the filter_mode.</description> + </key> + <key name="binary-patterns" type="as"> + <default>['*.la', '*.lo']</default> + <summary>File Browser Binary Patterns</summary> + <description>The supplemental patterns to use when filtering binary files.</description> + </key> + </schema> + + <enum id="org.gnome.gedit.plugins.filebrowser.nautilus.ClickPolicy"> + <value value="0" nick="single"/> + <value value="1" nick="double"/> + </enum> + + <schema id="org.gnome.gedit.plugins.filebrowser.nautilus" path="/org/gnome/gedit/plugins/filebrowser/nautilus/"> + <key name="click-policy" enum="org.gnome.gedit.plugins.filebrowser.nautilus.ClickPolicy"> + <default>'double'</default> + </key> + <key name="confirm-trash" type="b"> + <default>true</default> + </key> + </schema> +</schemalist> diff --git a/plugins/filebrowser/resources/gedit-file-browser.gresource.xml b/plugins/filebrowser/resources/gedit-file-browser.gresource.xml new file mode 100644 index 0000000..273bd26 --- /dev/null +++ b/plugins/filebrowser/resources/gedit-file-browser.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/gedit/plugins/file-browser"> + <file preprocess="xml-stripblanks">ui/gedit-file-browser-menus.ui</file> + <file preprocess="xml-stripblanks">ui/gedit-file-browser-widget.ui</file> + </gresource> +</gresources> diff --git a/plugins/filebrowser/resources/meson.build b/plugins/filebrowser/resources/meson.build new file mode 100644 index 0000000..da2b577 --- /dev/null +++ b/plugins/filebrowser/resources/meson.build @@ -0,0 +1,8 @@ +libfilebrowser_res = gnome.compile_resources( + 'gedit-file-browser-resources', + 'gedit-file-browser.gresource.xml', +) + +libfilebrowser_sources += [ + libfilebrowser_res.get(0), +] diff --git a/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui b/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui new file mode 100644 index 0000000..9134268 --- /dev/null +++ b/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <menu id="dir-menu"> + <section> + <item> + <attribute name="label" translatable="yes">_Open</attribute> + <attribute name="action">browser.open</attribute> + </item> + </section> + <section> + <item> + <attribute name="label" translatable="yes">_Set Root to Active Document</attribute> + <attribute name="action">browser.set_active_root</attribute> + </item> + </section> + <section> + <item> + <attribute name="label" translatable="yes">_New Folder</attribute> + <attribute name="action">browser.new_folder</attribute> + </item> + <item> + <attribute name="label" translatable="yes">New F_ile</attribute> + <attribute name="action">browser.new_file</attribute> + </item> + </section> + <section> + <item> + <attribute name="label" translatable="yes">_Rename…</attribute> + <attribute name="action">browser.rename</attribute> + </item> + <item> + <attribute name="label" translatable="yes">_Move to Trash</attribute> + <attribute name="action">browser.move_to_trash</attribute> + </item> + <item> + <attribute name="label" translatable="yes">_Delete</attribute> + <attribute name="action">browser.delete</attribute> + </item> + </section> + <section> + <item> + <attribute name="label" translatable="yes">Re_fresh View</attribute> + <attribute name="action">browser.refresh_view</attribute> + </item> + <item> + <attribute name="label" translatable="yes">_View Folder</attribute> + <attribute name="action">browser.view_folder</attribute> + </item> + <item> + <attribute name="label" translatable="yes">_Open in Terminal</attribute> + <attribute name="action">browser.open_in_terminal</attribute> + </item> + </section> + <submenu> + <attribute name="label" translatable="yes">_Filter</attribute> + <section> + <item> + <attribute name="label" translatable="yes">Show _Hidden</attribute> + <attribute name="action">browser.show_hidden</attribute> + </item> + <item> + <attribute name="label" translatable="yes">Show _Binary</attribute> + <attribute name="action">browser.show_binary</attribute> + </item> + <item> + <attribute name="label" translatable="yes">Match Filename</attribute> + <attribute name="action">browser.show_match_filename</attribute> + </item> + </section> + </submenu> + <section> + <attribute name="id">extension-section</attribute> + </section> + </menu> + <menu id="bookmarks-menu"> + <section> + <item> + <attribute name="label" translatable="yes">_Set Root to Active Document</attribute> + <attribute name="action">browser.set_active_root</attribute> + </item> + </section> + </menu> +</interface> diff --git a/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui b/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui new file mode 100644 index 0000000..e4fb300 --- /dev/null +++ b/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui @@ -0,0 +1,275 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.6 --> + <object class="GtkListStore" id="locations_model"> + <columns> + <!-- column-name icon --> + <column type="GdkPixbuf"/> + <!-- column-name icon name --> + <column type="gchararray"/> + <!-- column-name name --> + <column type="gchararray"/> + <!-- column-name file --> + <column type="GFile"/> + <!-- column-name id --> + <column type="guint"/> + </columns> + </object> + <object class="GtkPopover" id="locations_popover"> + <property name="can_focus">True</property> + <property name="visible">True</property> + <property name="width-request">300</property> + <property name="height-request">300</property> + <child> + <object class="GtkScrolledWindow" id="locations_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <property name="margin">6</property> + <property name="hscrollbar_policy">never</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GtkTreeView" id="locations_treeview"> + <property name="visible">True</property> + <property name="headers_visible">False</property> + <property name="can_focus">True</property> + <property name="model">locations_model</property> + <property name="activate-on-single-click">True</property> + <child> + <object class="GtkTreeViewColumn" id="treeview_icon_column"> + <child> + <object class="GtkCellRendererPixbuf" id="treeview_icon_renderer"/> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeview_name_column"> + <child> + <object class="GtkCellRendererText" id="treeview_name_renderer"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="locations_treeview_selection"/> + </child> + </object> + </child> + </object> + </child> + </object> + <object class="GtkMenu" id="location_previous_menu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <object class="GtkMenu" id="location_next_menu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <template class="GeditFileBrowserWidget" parent="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox" id="toolbar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin">3</property> + <property name="spacing">3</property> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButton" id="previous_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="action_name">browser.previous_location</property> + <property name="image">previous_image</property> + <style> + <class name="small-button"/> + </style> + </object> + </child> + <child> + <object class="GtkButton" id="next_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="action_name">browser.next_location</property> + <property name="image">next_image</property> + <style> + <class name="small-button"/> + </style> + </object> + </child> + <style> + <class name="linked"/> + </style> + </object> + </child> + <child> + <object class="GtkButton" id="button3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="action_name">browser.up</property> + <property name="image">up_image</property> + <style> + <class name="small-button"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="locations_button"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="use_popover">True</property> + <property name="popover">locations_popover</property> + <style> + <class name="text-button"/> + <class name="image-button"/> + <class name="small-button"/> + </style> + <child> + <object class="GtkBox" id="locations_button_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="has_focus">False</property> + <property name="is_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkCellView" id="locations_cellview"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="model">locations_model</property> + <child> + <object class="GtkCellRendererPixbuf" id="cellview_icon_renderer"/> + </child> + <child> + <object class="GtkCellRendererText" id="cellview_name_renderer"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkImage" id="locations_button_arrow"> + <property name="visible">True</property> + <property name="valign">baseline</property> + <property name="icon_name">pan-down-symbolic</property> + </object> + <packing> + <property name="pack-type">GTK_PACK_END</property> + </packing> + </child> + </object> + </child> + <child internal-child="accessible"> + <object class="AtkObject" id="locations_button_a11y"> + <property name="accessible-name" translatable="yes">History</property> + <property name="accessible-description" translatable="yes">Open history menu</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkSearchEntry" id="location_entry"> + <property name="visible">False</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + <property name="primary_icon_name">folder-symbolic</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GeditFileBrowserView" id="treeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection1"/> + </child> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkRevealer" id="filter_entry_revealer"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="reveal_child">False</property> + <property name="valign">start</property> + <child> + <object class="GtkEntry" id="filter_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + <property name="placeholder_text">Match Filename</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </template> + <object class="GtkImage" id="previous_image"> + <property name="visible">True</property> + <property name="icon_name">go-previous-symbolic</property> + <property name="icon-size">2</property> + </object> + <object class="GtkImage" id="next_image"> + <property name="visible">True</property> + <property name="icon_name">go-next-symbolic</property> + <property name="icon-size">2</property> + </object> + <object class="GtkImage" id="up_image"> + <property name="visible">True</property> + <property name="icon_name">go-up-symbolic</property> + <property name="icon-size">2</property> + </object> +</interface> diff --git a/plugins/generate-list.sh b/plugins/generate-list.sh new file mode 100755 index 0000000..c77cab0 --- /dev/null +++ b/plugins/generate-list.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2020 Sébastien Wilmet <swilmet@gnome.org> +# SPDX-License-Identifier: GPL-3.0-or-later + +# This script generates a Markdown file with the names and descriptions of all +# official gedit plugins. + +write_list_for_plugins_dir() { + plugins_dir=$1 + + for plugin_desktop_file in `find "$plugins_dir" -name '*.plugin.desktop*'` + do + name=`grep -P '^Name=' "$plugin_desktop_file" | cut -d'=' -f2` + echo -n "- **$name** - " + + desc=`grep -P '^Description=' "$plugin_desktop_file" | cut -d'=' -f2` + echo "*$desc*" + done | sort +} + +write_content() { + echo 'gedit plugins' + echo '=============' + echo + echo 'Core plugins' + echo '------------' + echo + echo 'Plugins that are distributed with gedit itself.' + echo + + write_list_for_plugins_dir '.' + + echo + echo 'gedit-plugins package' + echo '---------------------' + echo + echo 'The gedit-plugins package contains useful plugins that are (most' + echo 'of the time) too specific to be distributed with gedit itself.' + echo + + write_list_for_plugins_dir '../../gedit-plugins/plugins' +} + +write_content > list-of-gedit-plugins.md diff --git a/plugins/list-of-gedit-plugins.md b/plugins/list-of-gedit-plugins.md new file mode 100644 index 0000000..3afea6c --- /dev/null +++ b/plugins/list-of-gedit-plugins.md @@ -0,0 +1,45 @@ +gedit plugins +============= + +Core plugins +------------ + +Plugins that are distributed with gedit itself. + +- **Document Statistics** - *Report the number of words, lines and characters in a document.* +- **External Tools** - *Execute external commands and shell scripts.* +- **File Browser Panel** - *Easy file access from the side panel.* +- **Insert Date/Time** - *Inserts current date and time at the cursor position.* +- **Modelines** - *Emacs, Kate and Vim-style modelines support for gedit.* +- **Python Console** - *Interactive Python console standing in the bottom panel.* +- **Quick Highlight** - *Highlights every occurrences of selected text.* +- **Quick Open** - *Quickly open files.* +- **Snippets** - *Insert often-used pieces of text in a fast way.* +- **Sort** - *Sorts a document or selected text.* +- **Spell Checker** - *Checks the spelling of the current document.* + +gedit-plugins package +--------------------- + +The gedit-plugins package contains useful plugins that are (most +of the time) too specific to be distributed with gedit itself. + +- **Bookmarks** - *Easy document navigation with bookmarks* +- **Bracket Completion** - *Automatically adds closing brackets.* +- **Character Map** - *Insert special characters just by clicking on them.* +- **Code Comment** - *Comment out or uncomment a selected block of code.* +- **Color Picker** - *Pick a color from a dialog and insert its hexadecimal representation.* +- **Color Scheme Editor** - *Source code color scheme editor* +- **Commander** - *Command line interface for advanced editing* +- **Draw Spaces** - *Draw spaces and tabs* +- **Embedded Terminal** - *Embed a terminal in the bottom pane.* +- **Find in Files** - *Find text in all files of a folder.* +- **Git** - *Highlight lines that have been changed since the last commit* +- **Join/Split Lines** - *Join several lines or split long ones* +- **Multi Edit** - *Edit document in multiple places at once* +- **Session Saver** - *Save and restore your working sessions* +- **Smart Spaces** - *Forget you’re not using tabulations.* +- **SyncTeX** - *Synchronize between LaTeX and PDF with gedit and evince.* +- **Text Size** - *Easily increase and decrease the text size* +- **Translate** - *Translates text into different languages* +- **Word Completion** - *Word completion using the completion framework* diff --git a/plugins/meson.build b/plugins/meson.build new file mode 100644 index 0000000..50bc5d3 --- /dev/null +++ b/plugins/meson.build @@ -0,0 +1,31 @@ +# Keep the autotools convention for shared module suffix because GModule +# depends on it: https://gitlab.gnome.org/GNOME/glib/issues/520 +module_suffix = [] +if host_machine.system() == 'darwin' + module_suffix = 'so' +endif + +msgfmt_plugin_cmd = [ + find_program('msgfmt'), + '--desktop', + '--keyword=Name', + '--keyword=Description', + '--template=@INPUT@', + '-d', join_paths(srcdir, 'po'), + '--output=@OUTPUT@' +] + +subdir('docinfo') +subdir('filebrowser') +subdir('modelines') +subdir('pythonconsole') +subdir('quickhighlight') +subdir('quickopen') +subdir('snippets') +subdir('sort') +subdir('spell') +subdir('time') + +if get_option('plugin_externaltools') + subdir('externaltools') +endif diff --git a/plugins/modelines/gedit-modeline-plugin.c b/plugins/modelines/gedit-modeline-plugin.c new file mode 100644 index 0000000..376d996 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.c @@ -0,0 +1,225 @@ +/* + * gedit-modeline-plugin.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2010 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#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-view-activatable.h> +#include <gedit/gedit-view.h> + +struct _GeditModelinePluginPrivate +{ + GeditView *view; + + gulong document_loaded_handler_id; + gulong document_saved_handler_id; +}; + +enum +{ + PROP_0, + PROP_VIEW +}; + +static void gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditModelinePlugin, + gedit_modeline_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_VIEW_ACTIVATABLE, + gedit_view_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditModelinePlugin)) + +static void +gedit_modeline_plugin_constructed (GObject *object) +{ + gchar *data_dir; + + data_dir = peas_extension_base_get_data_dir (PEAS_EXTENSION_BASE (object)); + + modeline_parser_init (data_dir); + + g_free (data_dir); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->constructed (object); +} + +static void +gedit_modeline_plugin_init (GeditModelinePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin initializing"); + + plugin->priv = gedit_modeline_plugin_get_instance_private (plugin); + +} + +static void +gedit_modeline_plugin_dispose (GObject *object) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin disposing"); + + g_clear_object (&plugin->priv->view); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->dispose (object); +} + +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 +gedit_modeline_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + plugin->priv->view = GEDIT_VIEW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_modeline_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + g_value_set_object (value, plugin->priv->view); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_document_loaded_or_saved (GeditDocument *document, + GtkSourceView *view) +{ + modeline_parser_apply_modeline (view); +} + +static void +gedit_modeline_plugin_activate (GeditViewActivatable *activatable) +{ + GeditModelinePlugin *plugin; + GtkTextBuffer *doc; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_MODELINE_PLUGIN (activatable); + + modeline_parser_apply_modeline (GTK_SOURCE_VIEW (plugin->priv->view)); + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + plugin->priv->document_loaded_handler_id = + g_signal_connect (doc, "loaded", + G_CALLBACK (on_document_loaded_or_saved), + plugin->priv->view); + plugin->priv->document_saved_handler_id = + g_signal_connect (doc, "saved", + G_CALLBACK (on_document_loaded_or_saved), + plugin->priv->view); +} + +static void +gedit_modeline_plugin_deactivate (GeditViewActivatable *activatable) +{ + GeditModelinePlugin *plugin; + GtkTextBuffer *doc; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_MODELINE_PLUGIN (activatable); + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + g_signal_handler_disconnect (doc, plugin->priv->document_loaded_handler_id); + g_signal_handler_disconnect (doc, plugin->priv->document_saved_handler_id); +} + +static void +gedit_modeline_plugin_class_init (GeditModelinePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gedit_modeline_plugin_constructed; + object_class->dispose = gedit_modeline_plugin_dispose; + object_class->finalize = gedit_modeline_plugin_finalize; + object_class->set_property = gedit_modeline_plugin_set_property; + object_class->get_property = gedit_modeline_plugin_get_property; + + g_object_class_override_property (object_class, PROP_VIEW, "view"); +} + +static void +gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface) +{ + iface->activate = gedit_modeline_plugin_activate; + iface->deactivate = gedit_modeline_plugin_deactivate; +} + +static void +gedit_modeline_plugin_class_finalize (GeditModelinePluginClass *klass) +{ +} + + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_modeline_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_VIEW_ACTIVATABLE, + GEDIT_TYPE_MODELINE_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/gedit-modeline-plugin.h b/plugins/modelines/gedit-modeline-plugin.h new file mode 100644 index 0000000..d3557e4 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.h @@ -0,0 +1,60 @@ +/* + * gedit-modeline-plugin.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_MODELINE_PLUGIN_H +#define GEDIT_MODELINE_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.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)) + +typedef struct _GeditModelinePlugin GeditModelinePlugin; +typedef struct _GeditModelinePluginPrivate GeditModelinePluginPrivate; +typedef struct _GeditModelinePluginClass GeditModelinePluginClass; + +struct _GeditModelinePlugin { + PeasExtensionBase parent; + + /*< private >*/ + GeditModelinePluginPrivate *priv; +}; + +struct _GeditModelinePluginClass { + PeasExtensionBaseClass parent_class; +}; + +GType gedit_modeline_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_MODELINE_PLUGIN_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/language-mappings b/plugins/modelines/language-mappings new file mode 100644 index 0000000..47d0029 --- /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/meson.build b/plugins/modelines/meson.build new file mode 100644 index 0000000..3ff0e84 --- /dev/null +++ b/plugins/modelines/meson.build @@ -0,0 +1,42 @@ +libmodelines_sources = files( + 'gedit-modeline-plugin.c', + 'modeline-parser.c', +) + +libmodelines_deps = [ + libgedit_dep, +] + +libmodelines_sha = shared_module( + 'modelines', + sources: libmodelines_sources, + include_directories: root_include_dir, + dependencies: libmodelines_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'modelines.plugin', + input: 'modelines.plugin.desktop.in', + output: 'modelines.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) + +install_data( + 'language-mappings', + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'modelines', + ) +) diff --git a/plugins/modelines/modeline-parser.c b/plugins/modelines/modeline-parser.c new file mode 100644 index 0000000..989e111 --- /dev/null +++ b/plugins/modelines/modeline-parser.c @@ -0,0 +1,899 @@ +/* + * modeline-parser.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <gtksourceview/gtksource.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-document.h> +#include <gedit/gedit-settings.h> +#include <gedit/gedit-utils.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 = NULL; +static GHashTable *emacs_languages = NULL; +static GHashTable *kate_languages = NULL; + +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) +{ + if (modelines_data_dir == NULL) + { + modelines_data_dir = g_strdup (data_dir); + } +} + +void +modeline_parser_shutdown () +{ + if (vim_languages != NULL) + g_hash_table_unref (vim_languages); + + if (emacs_languages != NULL) + g_hash_table_unref (emacs_languages); + + if (kate_languages != NULL) + g_hash_table_unref (kate_languages); + + vim_languages = NULL; + emacs_languages = NULL; + kate_languages = NULL; + + g_free (modelines_data_dir); + modelines_data_dir = NULL; +} + +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++) + { + /* steal the name string */ + 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 = NULL; + + name = g_ascii_strdown (language_name, -1); + + if (mapping != NULL) + { + language_id = g_hash_table_lookup (mapping, name); + + if (language_id != NULL) + { + g_free (name); + return g_strdup (language_id); + } + } + + /* 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 || + strcmp (key->str, "tw") == 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 || + strcmp (key->str, "c-basic-offset") == 0 || + strcmp (key->str, "js-indent-level") == 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 *line, + gint line_number, + gint line_count, + ModelineOptions *options) +{ + gchar *s = line; + + /* look for the beginning of a modeline */ + while (s != NULL && *s != '\0') + { + if (s > line && !g_ascii_isspace (*(s - 1))) + { + s++; + 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); + } + else + { + s++; + } + } +} + +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; + GSettings *settings; + + 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) + { + if (g_ascii_strcasecmp (options.language_id, "text") == 0) + { + gedit_document_set_language (GEDIT_DOCUMENT (buffer), + NULL); + } + else + { + GtkSourceLanguageManager *manager; + GtkSourceLanguage *language; + + manager = gtk_source_language_manager_get_default (); + + language = gtk_source_language_manager_get_language + (manager, options.language_id); + if (language != NULL) + { + gedit_document_set_language (GEDIT_DOCUMENT (buffer), + language); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, + "Unknown language `%s'", + options.language_id); + } + } + } + + ModelineOptions *previous = g_object_get_data (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY); + + settings = g_settings_new ("org.gnome.gedit.preferences.editor"); + + /* 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)) + { + gboolean insert_spaces; + + insert_spaces = g_settings_get_boolean (settings, GEDIT_SETTINGS_INSERT_SPACES); + + gtk_source_view_set_insert_spaces_instead_of_tabs (view, 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)) + { + guint tab_width; + + g_settings_get (settings, GEDIT_SETTINGS_TABS_SIZE, "u", &tab_width); + + gtk_source_view_set_tab_width (view, tab_width); + } + + 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)) + { + GtkWrapMode mode; + + mode = g_settings_get_enum (settings, + GEDIT_SETTINGS_WRAP_MODE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), 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)) + { + guint right_margin_pos; + + g_settings_get (settings, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION, "u", + &right_margin_pos); + gtk_source_view_set_right_margin_position (view, + right_margin_pos); + } + + 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)) + { + gboolean display_right_margin; + + display_right_margin = g_settings_get_boolean (settings, + GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN); + gtk_source_view_set_show_right_margin (view, display_right_margin); + } + + if (previous) + { + g_free (previous->language_id); + *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_object_unref (settings); + g_free (options.language_id); +} + +/* vi:ts=8 */ diff --git a/plugins/modelines/modeline-parser.h b/plugins/modelines/modeline-parser.h new file mode 100644 index 0000000..0fbf9a1 --- /dev/null +++ b/plugins/modelines/modeline-parser.h @@ -0,0 +1,36 @@ +/* + * modelie-parser.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MODELINE_PARSER_H +#define MODELINE_PARSER_H + +#include <glib.h> +#include <gtksourceview/gtksource.h> + +G_BEGIN_DECLS + +void modeline_parser_init (const gchar *data_dir); +void modeline_parser_shutdown (void); +void modeline_parser_apply_modeline (GtkSourceView *view); + +G_END_DECLS + +#endif /* MODELINE_PARSER_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/modelines.plugin.desktop.in b/plugins/modelines/modelines.plugin.desktop.in new file mode 100644 index 0000000..d334c55 --- /dev/null +++ b/plugins/modelines/modelines.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=modelines +IAge=3 +Name=Modelines +Description=Emacs, Kate and Vim-style modelines support for gedit. +Authors=Steve Frécinaux <steve@istique.net> +Copyright=Copyright © 2005 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/pythonconsole/meson.build b/plugins/pythonconsole/meson.build new file mode 100644 index 0000000..1aecada --- /dev/null +++ b/plugins/pythonconsole/meson.build @@ -0,0 +1,31 @@ +subdir('pythonconsole') + +pythonconsole_gschema_file = files('org.gnome.gedit.plugins.pythonconsole.gschema.xml') +install_data( + pythonconsole_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-pythonconsole-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + pythonconsole_gschema_file, + ] + ) +endif + +custom_target( + 'pythonconsole.plugin', + input: 'pythonconsole.plugin.desktop.in', + output: 'pythonconsole.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml b/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml new file mode 100644 index 0000000..0a29031 --- /dev/null +++ b/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml @@ -0,0 +1,30 @@ +<schemalist gettext-domain="gedit"> + <schema id="org.gnome.gedit.plugins.pythonconsole" path="/org/gnome/gedit/plugins/pythonconsole/"> + <key name="command-color" type="s"> + <default>'#314e6c'</default> + <summary>Command Color Text</summary> + <description>The command color text</description> + </key> + <key name="error-color" type="s"> + <default>'#990000'</default> + <summary>Error Color Text</summary> + <description>The error color text</description> + </key> + <key name="use-system-font" type="b"> + <default>true</default> + <summary>Whether to use the system font</summary> + <description> + If true, the terminal will use the desktop-global standard + font if it’s monospace (and the most similar font it can + come up with otherwise). + </description> + </key> + <key name="font" type="s"> + <default>'Monospace 10'</default> + <summary>Font</summary> + <description> + A Pango font name. Examples are “Sans 12” or “Monospace Bold 14”. + </description> + </key> + </schema> +</schemalist> diff --git a/plugins/pythonconsole/pythonconsole.plugin.desktop.in b/plugins/pythonconsole/pythonconsole.plugin.desktop.in new file mode 100644 index 0000000..2b4456c --- /dev/null +++ b/plugins/pythonconsole/pythonconsole.plugin.desktop.in @@ -0,0 +1,12 @@ +[Plugin] +Loader=python3 +Module=pythonconsole +IAge=3 +Name=Python Console +Description=Interactive Python console standing in the bottom panel. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +Icon=text-x-script +Authors=Steve Frécinaux <steve@istique.net> +Copyright=Copyright © 2006 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py new file mode 100644 index 0000000..6405596 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/__init__.py @@ -0,0 +1,75 @@ +# -*- 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, see <http://www.gnu.org/licenses/>. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge <james@daa.com.au> +# Copyright (C), 2005 Adam Hooper <adamh@densi.com> +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import gi +gi.require_version('Gedit', '3.0') +gi.require_version('Peas', '1.0') +gi.require_version('PeasGtk', '1.0') +gi.require_version('Gtk', '3.0') + +from gi.repository import GObject, Gtk, Gedit, Peas, PeasGtk +from .console import PythonConsole +from .config import PythonConsoleConfigWidget + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class PythonConsolePlugin(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable): + __gtype_name__ = "PythonConsolePlugin" + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + self._console = PythonConsole(namespace = {'__builtins__' : __builtins__, + 'gedit' : Gedit, + 'window' : self.window}) + self._console.eval('print("You can access the main window through ' \ + '\'window\' :\\n%s" % window)', False) + bottom = self.window.get_bottom_panel() + self._console.show_all() + bottom.add_titled(self._console, "GeditPythonConsolePanel", _('Python Console')) + + def do_deactivate(self): + self._console.stop() + bottom = self.window.get_bottom_panel() + bottom.remove(self._console) + + def do_update_state(self): + pass + + def do_create_configure_widget(self): + config_widget = PythonConsoleConfigWidget(self.plugin_info.get_data_dir()) + + return config_widget.configure_widget() + +# ex:et:ts=4: diff --git a/plugins/pythonconsole/pythonconsole/config.py b/plugins/pythonconsole/pythonconsole/config.py new file mode 100644 index 0000000..8c609af --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.py @@ -0,0 +1,74 @@ +# -*- 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, see <http://www.gnu.org/licenses/>. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge <james@daa.com.au> +# Copyright (C), 2005 Adam Hooper <adamh@densi.com> +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import os +from gi.repository import Gio, Gtk, Gdk + +__all__ = ('PythonConsoleConfigWidget') + +class PythonConsoleConfigWidget(object): + + CONSOLE_KEY_BASE = 'org.gnome.gedit.plugins.pythonconsole' + CONSOLE_KEY_COMMAND_COLOR = 'command-color' + CONSOLE_KEY_ERROR_COLOR = 'error-color' + + def __init__(self, datadir): + object.__init__(self) + + self._ui_path = os.path.join(datadir, 'ui', 'config.ui') + self._settings = Gio.Settings.new(self.CONSOLE_KEY_BASE) + self._ui = Gtk.Builder() + + def configure_widget(self): + self._ui.add_from_file(self._ui_path) + + self.set_colorbutton_color(self._ui.get_object('colorbutton-command'), + self._settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR)) + self.set_colorbutton_color(self._ui.get_object('colorbutton-error'), + self._settings.get_string(self.CONSOLE_KEY_ERROR_COLOR)) + + self._ui.connect_signals(self) + + widget = self._ui.get_object('grid') + + return widget + + @staticmethod + def set_colorbutton_color(colorbutton, value): + rgba = Gdk.RGBA() + parsed = rgba.parse(value) + + if parsed: + colorbutton.set_rgba(rgba) + + def on_colorbutton_command_color_set(self, colorbutton): + self._settings.set_string(self.CONSOLE_KEY_COMMAND_COLOR, + colorbutton.get_color().to_string()) + + def on_colorbutton_error_color_set(self, colorbutton): + self._settings.set_string(self.CONSOLE_KEY_ERROR_COLOR, + 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 100644 index 0000000..573be34 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.ui @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkGrid" id="grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">12</property> + <property name="margin_end">12</property> + <property name="margin_top">12</property> + <property name="margin_bottom">12</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label-command"> + <property name="visible">True</property> + <property name="can_focus">False</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> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-error"> + <property name="visible">True</property> + <property name="can_focus">False</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="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="colorbutton-command"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="hexpand">True</property> + <property name="rgba">#31314e4e6c6c</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="colorbutton-error"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="hexpand">True</property> + <property name="rgba">#999900000000</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> +</interface> diff --git a/plugins/pythonconsole/pythonconsole/console.py b/plugins/pythonconsole/pythonconsole/console.py new file mode 100644 index 0000000..b81e8a7 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/console.py @@ -0,0 +1,415 @@ +# -*- 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, see <http://www.gnu.org/licenses/>. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge <james@daa.com.au> +# Copyright (C), 2005 Adam Hooper <adamh@densi.com> +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import string +import sys +import re +import traceback + +from gi.repository import GLib, Gio, Gtk, Gdk, Pango + +__all__ = ('PythonConsole', 'OutFile') + +class PythonConsole(Gtk.ScrolledWindow): + + __gsignals__ = { + 'grab-focus' : 'override', + } + + DEFAULT_FONT = "Monospace 10" + + CONSOLE_KEY_BASE = 'org.gnome.gedit.plugins.pythonconsole' + SETTINGS_INTERFACE_DIR = "org.gnome.desktop.interface" + SETTINGS_PROFILE_DIR = "org.gnome.GnomeTerminal.profiles.Default" + + CONSOLE_KEY_COMMAND_COLOR = 'command-color' + CONSOLE_KEY_ERROR_COLOR = 'error-color' + + def __init__(self, namespace = {}): + Gtk.ScrolledWindow.__init__(self) + + self._settings = Gio.Settings.new(self.CONSOLE_KEY_BASE) + self._settings.connect("changed", self.on_color_settings_changed) + + self._interface_settings = Gio.Settings.new(self.SETTINGS_INTERFACE_DIR) + self._interface_settings.connect("changed", self.on_settings_changed) + + self._profile_settings = self.get_profile_settings() + self._profile_settings.connect("changed", self.on_settings_changed) + + self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.set_shadow_type(Gtk.ShadowType.NONE) + self.view = Gtk.TextView() + self.reconfigure() + self.view.set_editable(True) + self.view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) + self.add(self.view) + self.view.show() + + buf = self.view.get_buffer() + self.normal = buf.create_tag("normal") + self.error = buf.create_tag("error") + self.command = buf.create_tag("command") + + # Load the default settings + self.on_color_settings_changed(self._settings, None) + + self.__spaces_pattern = re.compile(r'^\s+') + self.namespace = namespace + + self.block_command = False + + # Init first line + buf.create_mark("input-line", buf.get_end_iter(), True) + buf.insert(buf.get_end_iter(), ">>> ") + buf.create_mark("input", buf.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) + buf.connect("mark-set", self.__mark_set_cb) + + def get_profile_settings(self): + #FIXME return either the gnome-terminal settings or the gedit one + return Gio.Settings.new(self.CONSOLE_KEY_BASE) + + def do_grab_focus(self): + self.view.grab_focus() + + def reconfigure(self): + # Font + font_desc = None + system_font = self._interface_settings.get_string("monospace-font-name") + + if self._profile_settings.get_boolean("use-system-font"): + font_name = system_font + else: + font_name = self._profile_settings.get_string("font") + + try: + font_desc = Pango.FontDescription(font_name) + except: + if font_name != self.DEFAULT_FONT: + if font_name != system_font: + try: + font_desc = Pango.FontDescription(system_font) + except: + pass + + if font_desc is None: + try: + font_desc = Pango.FontDescription(self.DEFAULT_FONT) + except: + pass + + if font_desc != None: + self.view.modify_font(font_desc) + + def on_settings_changed(self, settings, key): + self.reconfigure() + + def on_color_settings_changed(self, settings, key): + self.error.set_property("foreground", settings.get_string(self.CONSOLE_KEY_ERROR_COLOR)) + self.command.set_property("foreground", settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR)) + + 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 == Gdk.KEY_D and event_state == Gdk.ModifierType.CONTROL_MASK: + self.destroy() + + elif event.keyval == Gdk.KEY_Return and event_state == Gdk.ModifierType.CONTROL_MASK: + # Get the command + buf = view.get_buffer() + inp_mark = buf.get_mark("input") + inp = buf.get_iter_at_mark(inp_mark) + cur = buf.get_end_iter() + line = buf.get_text(inp, cur, False) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Prepare the new line + cur = buf.get_end_iter() + buf.insert(cur, "\n... ") + cur = buf.get_end_iter() + buf.move_mark(inp_mark, cur) + + # Keep indentation of precendent line + spaces = re.match(self.__spaces_pattern, line) + if spaces is not None: + buf.insert(cur, line[spaces.start() : spaces.end()]) + cur = buf.get_end_iter() + + buf.place_cursor(cur) + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_Return: + # Get the marks + buf = view.get_buffer() + lin_mark = buf.get_mark("input-line") + inp_mark = buf.get_mark("input") + + # Get the command line + inp = buf.get_iter_at_mark(inp_mark) + cur = buf.get_end_iter() + line = buf.get_text(inp, cur, False) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Make the line blue + lin = buf.get_iter_at_mark(lin_mark) + buf.apply_tag(self.command, lin, cur) + buf.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 = buf.get_end_iter() + buf.move_mark(lin_mark, cur) + buf.insert(cur, com_mark) + cur = buf.get_end_iter() + buf.move_mark(inp_mark, cur) + buf.place_cursor(cur) + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_KP_Down or event.keyval == Gdk.KEY_Down: + # Next entry from history + view.stop_emission_by_name("key_press_event") + self.history_down() + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_KP_Up or event.keyval == Gdk.KEY_Up: + # Previous entry from history + view.stop_emission_by_name("key_press_event") + self.history_up() + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_KP_Left or event.keyval == Gdk.KEY_Left or \ + event.keyval == Gdk.KEY_BackSpace: + buf = view.get_buffer() + inp = buf.get_iter_at_mark(buf.get_mark("input")) + cur = buf.get_iter_at_mark(buf.get_insert()) + if inp.compare(cur) == 0: + if not event_state: + buf.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 == Gdk.KEY_KP_Home or event.keyval == Gdk.KEY_Home) and \ + event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): + # Go to the begin of the command instead of the begin of the line + buf = view.get_buffer() + it = buf.get_iter_at_mark(buf.get_mark("input")) + ins = buf.get_iter_at_mark(buf.get_insert()) + + while it.get_char().isspace(): + it.forward_char() + + if it.equal(ins): + it = buf.get_iter_at_mark(buf.get_mark("input")) + + if event_state & Gdk.ModifierType.SHIFT_MASK: + buf.move_mark_by_name("insert", it) + else: + buf.place_cursor(it) + return True + + elif (event.keyval == Gdk.KEY_KP_End or event.keyval == Gdk.KEY_End) and \ + event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): + + buf = view.get_buffer() + it = buf.get_end_iter() + ins = buf.get_iter_at_mark(buf.get_insert()) + + it.backward_char() + + while it.get_char().isspace(): + it.backward_char() + + it.forward_char() + + if it.equal(ins): + it = buf.get_end_iter() + + if event_state & Gdk.ModifierType.SHIFT_MASK: + buf.move_mark_by_name("insert", it) + else: + buf.place_cursor(it) + return True + + def __mark_set_cb(self, buf, it, name): + input = buf.get_iter_at_mark(buf.get_mark("input")) + pos = buf.get_iter_at_mark(buf.get_insert()) + self.view.set_editable(pos.compare(input) != -1) + + def get_command_line(self): + buf = self.view.get_buffer() + inp = buf.get_iter_at_mark(buf.get_mark("input")) + cur = buf.get_end_iter() + return buf.get_text(inp, cur, False) + + def set_command_line(self, command): + buf = self.view.get_buffer() + mark = buf.get_mark("input") + inp = buf.get_iter_at_mark(mark) + cur = buf.get_end_iter() + buf.delete(inp, cur) + buf.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): + i = self.view.get_buffer().get_end_iter() + self.view.scroll_to_iter(i, 0.0, False, 0.5, 0.5) + return False + + def write(self, text, tag = None): + buf = self.view.get_buffer() + if tag is None: + buf.insert(buf.get_end_iter(), text) + else: + buf.insert_with_tags(buf.get_end_iter(), text, tag) + + GLib.idle_add(self.scroll_to_end) + + def eval(self, command, display_command = False): + buf = self.view.get_buffer() + lin = buf.get_mark("input-line") + buf.delete(buf.get_iter_at_mark(lin), + buf.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 = buf.get_end_iter() + buf.move_mark_by_name("input-line", cur) + buf.insert(cur, ">>> ") + cur = buf.get_end_iter() + buf.move_mark_by_name("input", cur) + self.view.scroll_to_iter(buf.get_end_iter(), 0.0, False, 0.5, 0.5) + + def __run(self, command): + sys.stdout, self.stdout = self.stdout, sys.stdout + sys.stderr, self.stderr = self.stderr, sys.stderr + + try: + try: + r = eval(command, self.namespace, self.namespace) + if r is not None: + print(r) + except SyntaxError: + exec(command, 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/pythonconsole/pythonconsole/meson.build b/plugins/pythonconsole/pythonconsole/meson.build new file mode 100644 index 0000000..6bae388 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/meson.build @@ -0,0 +1,24 @@ +pythonconsole_sources = files( + '__init__.py', + 'config.py', + 'console.py', +) + +install_data( + pythonconsole_sources, + install_dir: join_paths( + pkglibdir, + 'plugins', + 'pythonconsole', + ) +) + +install_data( + 'config.ui', + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'pythonconsole', + 'ui', + ) +) diff --git a/plugins/quickhighlight/gedit-quick-highlight-plugin.c b/plugins/quickhighlight/gedit-quick-highlight-plugin.c new file mode 100644 index 0000000..7b4289d --- /dev/null +++ b/plugins/quickhighlight/gedit-quick-highlight-plugin.c @@ -0,0 +1,483 @@ +/* + * gedit-quick-highlight-plugin.c + * + * Copyright (C) 2018 Martin Blanchard + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + * + */ + +#include "config.h" + +#include <glib/gi18n.h> + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-document.h> +#include <gedit/gedit-view-activatable.h> +#include <gedit/gedit-view.h> + +#include "gedit-quick-highlight-plugin.h" + +struct _GeditQuickHighlightPluginPrivate +{ + GeditView *view; + + GeditDocument *buffer; + GtkTextMark *insert_mark; + + GtkSourceSearchContext *search_context; + GtkSourceStyle *style; + + gulong buffer_handler_id; + gulong mark_set_handler_id; + gulong delete_range_handler_id; + gulong style_scheme_handler_id; + + guint queued_highlight; +}; + +enum +{ + PROP_0, + PROP_VIEW +}; + +static void gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditQuickHighlightPlugin, + gedit_quick_highlight_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_VIEW_ACTIVATABLE, + gedit_view_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditQuickHighlightPlugin)) + +static void gedit_quick_highlight_plugin_notify_buffer_cb (GObject *object, GParamSpec *pspec, gpointer user_data); +static void gedit_quick_highlight_plugin_mark_set_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, GtkTextMark *mark, gpointer user_data); +static void gedit_quick_highlight_plugin_delete_range_cb (GtkTextBuffer *textbuffer, GtkTextIter *start, GtkTextIter *end, gpointer user_data); +static void gedit_quick_highlight_plugin_notify_style_scheme_cb (GObject *object, GParamSpec *pspec, gpointer user_data); + +static void +gedit_quick_highlight_plugin_load_style (GeditQuickHighlightPlugin *plugin) +{ + GtkSourceStyleScheme *style_scheme; + GtkSourceStyle *style = NULL; + + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if (plugin->priv->buffer == NULL) + { + return; + } + + gedit_debug (DEBUG_PLUGINS); + + g_clear_object (&plugin->priv->style); + + style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (plugin->priv->buffer)); + + if (style_scheme != NULL) + { + style = gtk_source_style_scheme_get_style (style_scheme, "quick-highlight-match"); + + if (style != NULL) + { + plugin->priv->style = gtk_source_style_copy (style); + } + } +} + +static gboolean +gedit_quick_highlight_plugin_highlight_worker (gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + GtkSourceSearchSettings *search_settings; + GtkTextIter start, end; + g_autofree gchar *text = NULL; + + g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + plugin->priv->queued_highlight = 0; + + if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (plugin->priv->buffer), &start, &end)) + { + g_clear_object (&plugin->priv->search_context); + return G_SOURCE_REMOVE; + } + + if (gtk_text_iter_get_line (&start) != gtk_text_iter_get_line (&end)) + { + g_clear_object (&plugin->priv->search_context); + return G_SOURCE_REMOVE; + } + + if (plugin->priv->search_context == NULL) + { + search_settings = + g_object_new (GTK_SOURCE_TYPE_SEARCH_SETTINGS, + "at-word-boundaries", FALSE, + "case-sensitive", TRUE, + "regex-enabled", FALSE, + NULL); + + plugin->priv->search_context = + g_object_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT, + "buffer", plugin->priv->buffer, + "highlight", FALSE, + "match-style", plugin->priv->style, + "settings", search_settings, + NULL); + + g_object_unref (search_settings); + } + else + { + search_settings = + gtk_source_search_context_get_settings (plugin->priv->search_context); + } + + text = gtk_text_iter_get_slice (&start, &end); + + gtk_source_search_settings_set_search_text (search_settings, text); + + gtk_source_search_context_set_highlight (plugin->priv->search_context, TRUE); + + return G_SOURCE_REMOVE; +} + +static void +gedit_quick_highlight_plugin_queue_update (GeditQuickHighlightPlugin *plugin) +{ + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if (plugin->priv->queued_highlight != 0) + { + return; + } + + plugin->priv->queued_highlight = + gdk_threads_add_idle_full (G_PRIORITY_LOW, + gedit_quick_highlight_plugin_highlight_worker, + g_object_ref (plugin), + g_object_unref); +} + +static void +gedit_quick_highlight_plugin_notify_weak_buffer_cb (gpointer data, + GObject *where_the_object_was) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (data); + + g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + plugin->priv->style_scheme_handler_id = 0; + plugin->priv->buffer = NULL; +} + +static void +gedit_quick_highlight_plugin_unref_weak_buffer (GeditQuickHighlightPlugin *plugin) +{ + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if (plugin->priv->buffer == NULL) + { + return; + } + + if (plugin->priv->delete_range_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->buffer, + plugin->priv->delete_range_handler_id); + plugin->priv->delete_range_handler_id = 0; + } + + if (plugin->priv->mark_set_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->buffer, + plugin->priv->mark_set_handler_id); + plugin->priv->mark_set_handler_id = 0; + } + + if (plugin->priv->style_scheme_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->buffer, + plugin->priv->style_scheme_handler_id); + plugin->priv->style_scheme_handler_id = 0; + } + + g_object_weak_unref (G_OBJECT (plugin->priv->buffer), + gedit_quick_highlight_plugin_notify_weak_buffer_cb, + plugin); + + plugin->priv->buffer = NULL; +} + +static void +gedit_quick_highlight_plugin_set_buffer (GeditQuickHighlightPlugin *plugin, + GeditDocument *buffer) +{ + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + g_return_if_fail (GEDIT_IS_DOCUMENT (buffer)); + + if (plugin->priv->buffer == buffer) + { + return; + } + + gedit_debug (DEBUG_PLUGINS); + + gedit_quick_highlight_plugin_unref_weak_buffer (plugin); + + plugin->priv->buffer = buffer; + + if (plugin->priv->buffer != NULL) + { + g_object_weak_ref (G_OBJECT (plugin->priv->buffer), + gedit_quick_highlight_plugin_notify_weak_buffer_cb, + plugin); + + plugin->priv->style_scheme_handler_id = + g_signal_connect (plugin->priv->buffer, + "notify::style-scheme", + G_CALLBACK (gedit_quick_highlight_plugin_notify_style_scheme_cb), + plugin); + + plugin->priv->mark_set_handler_id = + g_signal_connect (plugin->priv->buffer, + "mark-set", + G_CALLBACK (gedit_quick_highlight_plugin_mark_set_cb), + plugin); + + plugin->priv->delete_range_handler_id = + g_signal_connect (plugin->priv->buffer, + "delete-range", + G_CALLBACK (gedit_quick_highlight_plugin_delete_range_cb), + plugin); + + plugin->priv->insert_mark = + gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (plugin->priv->buffer)); + + gedit_quick_highlight_plugin_load_style (plugin); + + gedit_quick_highlight_plugin_queue_update (plugin); + } +} + +static void +gedit_quick_highlight_plugin_mark_set_cb (GtkTextBuffer *textbuffer, + GtkTextIter *location, + GtkTextMark *mark, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + + g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if G_LIKELY (mark != plugin->priv->insert_mark) + { + return; + } + + gedit_quick_highlight_plugin_queue_update (plugin); +} + +static void +gedit_quick_highlight_plugin_delete_range_cb (GtkTextBuffer *textbuffer, + GtkTextIter *start, + GtkTextIter *end, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + + g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + gedit_quick_highlight_plugin_queue_update (plugin); +} + +static void +gedit_quick_highlight_plugin_notify_style_scheme_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + + g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + gedit_quick_highlight_plugin_load_style (plugin); +} + +static void +gedit_quick_highlight_plugin_dispose (GObject *object) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + g_clear_object (&plugin->priv->search_context); + + gedit_quick_highlight_plugin_unref_weak_buffer (plugin); + + g_clear_object (&plugin->priv->view); + + G_OBJECT_CLASS (gedit_quick_highlight_plugin_parent_class)->dispose (object); +} + +static void +gedit_quick_highlight_plugin_finalize (GObject *object) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + g_clear_object (&plugin->priv->style); + + G_OBJECT_CLASS (gedit_quick_highlight_plugin_parent_class)->finalize (object); +} + +static void +gedit_quick_highlight_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + g_value_set_object (value, plugin->priv->view); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_quick_highlight_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + plugin->priv->view = GEDIT_VIEW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_quick_highlight_plugin_class_init (GeditQuickHighlightPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_quick_highlight_plugin_dispose; + object_class->finalize = gedit_quick_highlight_plugin_finalize; + object_class->set_property = gedit_quick_highlight_plugin_set_property; + object_class->get_property = gedit_quick_highlight_plugin_get_property; + + g_object_class_override_property (object_class, PROP_VIEW, "view"); +} + +static void +gedit_quick_highlight_plugin_class_finalize (GeditQuickHighlightPluginClass *klass) +{ +} + +static void +gedit_quick_highlight_plugin_init (GeditQuickHighlightPlugin *plugin) +{ + plugin->priv = gedit_quick_highlight_plugin_get_instance_private (plugin); +} + +static void +gedit_quick_highlight_plugin_activate (GeditViewActivatable *activatable) +{ + GeditQuickHighlightPlugin *plugin; + GtkTextBuffer *buffer; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (activatable); + + plugin->priv->buffer_handler_id = + g_signal_connect (plugin->priv->view, + "notify::buffer", + G_CALLBACK (gedit_quick_highlight_plugin_notify_buffer_cb), + plugin); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + gedit_quick_highlight_plugin_set_buffer (plugin, GEDIT_DOCUMENT (buffer)); +} + +static void +gedit_quick_highlight_plugin_deactivate (GeditViewActivatable *activatable) +{ + GeditQuickHighlightPlugin *plugin; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (activatable); + + g_clear_object (&plugin->priv->style); + g_clear_object (&plugin->priv->search_context); + + gedit_quick_highlight_plugin_unref_weak_buffer (plugin); + + if (plugin->priv->view != NULL && plugin->priv->buffer_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->view, + plugin->priv->buffer_handler_id); + plugin->priv->buffer_handler_id = 0; + } +} + +static void +gedit_quick_highlight_plugin_notify_buffer_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + GtkTextBuffer *buffer; + + g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + gedit_quick_highlight_plugin_set_buffer (plugin, GEDIT_DOCUMENT (buffer)); +} + +static void +gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface) +{ + iface->activate = gedit_quick_highlight_plugin_activate; + iface->deactivate = gedit_quick_highlight_plugin_deactivate; +} + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_quick_highlight_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_VIEW_ACTIVATABLE, + GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/quickhighlight/gedit-quick-highlight-plugin.h b/plugins/quickhighlight/gedit-quick-highlight-plugin.h new file mode 100644 index 0000000..3e668d0 --- /dev/null +++ b/plugins/quickhighlight/gedit-quick-highlight-plugin.h @@ -0,0 +1,63 @@ +/* + * gedit-quick-highlight-plugin.h + * + * Copyright (C) 2018 Martin Blanchard + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef GEDIT_QUICK_HIGHLIGHT_PLUGIN_H +#define GEDIT_QUICK_HIGHLIGHT_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN (gedit_quick_highlight_plugin_get_type ()) +#define GEDIT_QUICK_HIGHLIGHT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPlugin)) +#define GEDIT_QUICK_HIGHLIGHT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPluginClass)) +#define GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN)) +#define GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN)) +#define GEDIT_QUICK_HIGHLIGHT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPluginClass)) + +typedef struct _GeditQuickHighlightPlugin GeditQuickHighlightPlugin; +typedef struct _GeditQuickHighlightPluginPrivate GeditQuickHighlightPluginPrivate; +typedef struct _GeditQuickHighlightPluginClass GeditQuickHighlightPluginClass; + +struct _GeditQuickHighlightPlugin +{ + PeasExtensionBase parent_instance; + + /* < private > */ + GeditQuickHighlightPluginPrivate *priv; +}; + +struct _GeditQuickHighlightPluginClass +{ + PeasExtensionBaseClass parent_class; +}; + +GType gedit_quick_highlight_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_QUICK_HIGHLIGHT_PLUGIN_H */ + +/* ex:set ts=8 noet: */ diff --git a/plugins/quickhighlight/meson.build b/plugins/quickhighlight/meson.build new file mode 100644 index 0000000..18fb417 --- /dev/null +++ b/plugins/quickhighlight/meson.build @@ -0,0 +1,32 @@ +libquickhighlight_sources = files( + 'gedit-quick-highlight-plugin.c', +) + +libquickhighlight_deps = [ + libgedit_dep, +] + +libquickhighlight_sha = shared_module( + 'quickhighlight', + sources: libquickhighlight_sources, + include_directories: root_include_dir, + dependencies: libquickhighlight_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'quickhighlight.plugin', + input: 'quickhighlight.plugin.desktop.in', + output: 'quickhighlight.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/quickhighlight/quickhighlight.plugin.desktop.in b/plugins/quickhighlight/quickhighlight.plugin.desktop.in new file mode 100644 index 0000000..abb1baf --- /dev/null +++ b/plugins/quickhighlight/quickhighlight.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=quickhighlight +IAge=3 +Name=Quick Highlight +Description=Highlights every occurrences of selected text. +Authors=Martin Blanchard <tchaik@gmx.com> +Copyright=Copyright © 2016 Martin Blanchard +Website=http://www.gedit.org diff --git a/plugins/quickopen/meson.build b/plugins/quickopen/meson.build new file mode 100644 index 0000000..c5a2680 --- /dev/null +++ b/plugins/quickopen/meson.build @@ -0,0 +1,19 @@ +install_subdir( + 'quickopen', + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) + +custom_target( + 'quickopen.plugin', + input: 'quickopen.plugin.desktop.in', + output: 'quickopen.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/quickopen/quickopen.plugin.desktop.in b/plugins/quickopen/quickopen.plugin.desktop.in new file mode 100644 index 0000000..80fa03b --- /dev/null +++ b/plugins/quickopen/quickopen.plugin.desktop.in @@ -0,0 +1,12 @@ +[Plugin] +Loader=python3 +Module=quickopen +IAge=3 +Name=Quick Open +Description=Quickly open files. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +Icon=document-open +Authors=Jesse van den Kieboom <jessevdk@gnome.org> +Copyright=Copyright © 2009 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py new file mode 100644 index 0000000..3f708a3 --- /dev/null +++ b/plugins/quickopen/quickopen/__init__.py @@ -0,0 +1,193 @@ +# -*- 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, see <http://www.gnu.org/licenses/>. + +import os + +import gi +gi.require_version('Gedit', '3.0') +gi.require_version('Gtk', '3.0') +from gi.repository import GObject, Gio, GLib, Gtk, Gedit + +from .popup import Popup +from .virtualdirs import RecentDocumentsDirectory +from .virtualdirs import CurrentDocumentsDirectory + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class QuickOpenAppActivatable(GObject.Object, Gedit.AppActivatable): + app = GObject.Property(type=Gedit.App) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + self.app.add_accelerator("<Primary><Alt>O", "win.quickopen", None) + + self.menu_ext = self.extend_menu("file-section") + item = Gio.MenuItem.new(_("Quick Open…"), "win.quickopen") + self.menu_ext.prepend_menu_item(item) + + def do_deactivate(self): + self.app.remove_accelerator("win.quickopen", None) + + +class QuickOpenPlugin(GObject.Object, Gedit.WindowActivatable): + __gtype_name__ = "QuickOpenPlugin" + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + self._popup_size = (450, 300) + self._popup = None + + action = Gio.SimpleAction(name="quickopen") + action.connect('activate', self.on_quick_open_activate) + self.window.add_action(action) + + def do_deactivate(self): + self.window.remove_action("quickopen") + + def get_popup_size(self): + return self._popup_size + + def set_popup_size(self, size): + self._popup_size = size + + 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.get_file().is_local(): + gfile = doc.get_file().get_location() + paths.append(gfile.get_parent()) + + # File browser root directory + bus = self.window.get_message_bus() + + if bus.is_registered('/plugins/filebrowser', 'get_root'): + msg = bus.send_sync('/plugins/filebrowser', 'get_root') + + if msg: + gfile = msg.props.location + + if gfile and gfile.is_native(): + paths.append(gfile) + + # Recent documents + paths.append(RecentDocumentsDirectory()) + + # Local bookmarks + for path in self._local_bookmarks(): + paths.append(path) + + # Desktop directory + desktopdir = self._desktop_dir() + + if desktopdir: + paths.append(Gio.file_new_for_path(desktopdir)) + + # Home directory + paths.append(Gio.file_new_for_path(os.path.expanduser('~'))) + + self._popup = Popup(self.window, paths, self.on_activated) + self.window.get_group().add_window(self._popup) + + self._popup.set_default_size(*self.get_popup_size()) + self._popup.set_transient_for(self.window) + self._popup.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) + self._popup.connect('destroy', self.on_popup_destroy) + + def _local_bookmarks(self): + filename = os.path.expanduser('~/.config/gtk-3.0/bookmarks') + + if not os.path.isfile(filename): + return [] + + paths = [] + + for line in open(filename, 'r', encoding='utf-8'): + uri = line.strip().split(" ")[0] + f = Gio.file_new_for_uri(uri) + + if f.is_native(): + try: + info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, + Gio.FileQueryInfoFlags.NONE, + None) + + if info and info.get_file_type() == Gio.FileType.DIRECTORY: + paths.append(f) + except: + 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 open(config, 'r', encoding='utf-8'): + line = line.strip() + + if line.startswith('XDG_DESKTOP_DIR'): + parts = line.split('=', 1) + desktopdir = parts[1].strip('"').strip("'") + desktopdir = os.path.expandvars(desktopdir) + break + + if not desktopdir: + desktopdir = os.path.expanduser('~/Desktop') + + return desktopdir + + # Callbacks + def on_quick_open_activate(self, action, parameter, user_data=None): + if not self._popup: + self._create_popup() + + self._popup.show() + + def on_popup_destroy(self, popup, user_data=None): + self.set_popup_size(popup.get_final_size()) + + self._popup = None + + def on_activated(self, gfile, user_data=None): + Gedit.commands_load_location(self.window, gfile, None, -1, -1) + return True + +# ex:ts=4:et: diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py new file mode 100644 index 0000000..50d957e --- /dev/null +++ b/plugins/quickopen/quickopen/popup.py @@ -0,0 +1,617 @@ +# -*- 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, see <http://www.gnu.org/licenses/>. + +import os +import platform +import functools +import fnmatch + +from gi.repository import GLib, Gio, GObject, Pango, Gtk, Gdk, Gedit +import xml.sax.saxutils +from .virtualdirs import VirtualDirectory + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class Popup(Gtk.Dialog): + __gtype_name__ = "QuickOpenPopup" + + def __init__(self, window, paths, handler): + Gtk.Dialog.__init__(self, + title=_('Quick Open'), + transient_for=window, + modal=True, + destroy_with_parent=True) + + self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) + self._open_button = self.add_button(_("_Open"), + Gtk.ResponseType.ACCEPT) + + self._handler = handler + self._build_ui() + + self._size = (0, 0) + self._dirs = [] + self._cache = {} + self._theme = None + self._cursor = None + self._shift_start = None + + self._busy_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) + + accel_group = Gtk.AccelGroup() + accel_group.connect(Gdk.KEY_l, + Gdk.ModifierType.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()) + + self.connect('show', self.on_show) + + def get_final_size(self): + return self._size + + def _build_ui(self): + self.set_border_width(5) + vbox = self.get_content_area() + vbox.set_spacing(2) + action_area = self.get_action_area() + action_area.set_border_width(5) + action_area.set_spacing(6) + + self._entry = Gtk.SearchEntry() + self._entry.set_placeholder_text(_('Type to search…')) + + self._entry.connect('changed', self.on_changed) + self._entry.connect('key-press-event', self.on_key_press_event) + + sw = Gtk.ScrolledWindow() + sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + sw.set_shadow_type(Gtk.ShadowType.OUT) + + tv = Gtk.TreeView() + tv.set_headers_visible(False) + + self._store = Gtk.ListStore(Gio.Icon, + str, + GObject.Object, + Gio.FileType) + tv.set_model(self._store) + + self._treeview = tv + tv.connect('row-activated', self.on_row_activated) + + column = Gtk.TreeViewColumn() + + renderer = Gtk.CellRendererPixbuf() + column.pack_start(renderer, False) + column.add_attribute(renderer, "gicon", 0) + + renderer = Gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, "markup", 1) + + column.set_cell_data_func(renderer, self.on_cell_data_cb, None) + + tv.append_column(column) + sw.add(tv) + + selection = tv.get_selection() + selection.connect('changed', self.on_selection_changed) + selection.set_mode(Gtk.SelectionMode.MULTIPLE) + + vbox.pack_start(self._entry, False, False, 0) + vbox.pack_start(sw, True, True, 0) + + lbl = Gtk.Label() + lbl.set_halign(Gtk.Align.START) + lbl.set_ellipsize(Pango.EllipsizeMode.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, user_data): + path = model.get_path(piter) + + if self._cursor and path == self._cursor.get_path(): + style = self._treeview.get_style() + bg = style.bg[Gtk.StateType.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 _is_text(self, entry): + content_type = entry.get_content_type() + + if content_type is None or Gio.content_type_is_unknown(content_type): + return True + + if platform.system() != 'Windows': + if Gio.content_type_is_a(content_type, 'text/plain'): + return True + else: + if Gio.content_type_is_a(content_type, 'text'): + return True + + # This covers a rare case in which on Windows the PerceivedType + # is not set to "text" but the Content Type is set to text/plain + if Gio.content_type_get_mime_type(content_type) == 'text/plain': + return True + + return False + + def _list_dir(self, gfile): + entries = [] + + try: + ret = gfile.enumerate_children("standard::*", + Gio.FileQueryInfoFlags.NONE, + None) + except GLib.Error as e: + pass + + if isinstance(ret, Gio.FileEnumerator): + while True: + entry = ret.next_file(None) + + if not entry: + break + + if not entry.get_is_backup(): + entries.append((gfile.get_child(entry.get_name()), entry)) + else: + entries = ret + + children = [] + + for entry in entries: + file_type = entry[1].get_file_type() + + if file_type == Gio.FileType.REGULAR: + if not self._is_text(entry[1]): + continue + + children.append((entry[0], + entry[1].get_name(), + file_type, + entry[1].get_icon())) + + return children + + def _compare_entries(self, a, b, lpart): + if lpart in a: + if lpart in b: + if a.index(lpart) < b.index(lpart): + return -1 + elif a.index(lpart) > b.index(lpart): + return 1 + else: + return 0 + 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 d in self._cache: + entries = self._cache[d] + else: + entries = self._list_dir(d) + entries.sort(key=lambda x: x[1].lower()) + self._cache[d] = entries + + 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.FileType.DIRECTORY: + if len(parts) > 1: + newdirs.append(entry[0]) + else: + found.append(entry) + elif entry[2] == Gio.FileType.REGULAR and \ + (not lpart or len(parts) == 1): + found.append(entry) + + found.sort(key=functools.cmp_to_key(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, + Gio.FileQueryInfoFlags.NONE, + None) + + 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): + uri = item[2].get_uri() + + if uri not in self._stored_items: + self._store.append(item) + self._stored_items.add(uri) + + def _clear_store(self): + self._store.clear() + self._stored_items = set() + + def _show_virtuals(self): + for d in self._dirs: + if isinstance(d, VirtualDirectory): + for entry in d.enumerate_children("standard::*", 0, None): + self._append_to_store((entry[1].get_icon(), + xml.sax.saxutils.escape(entry[1].get_name()), + entry[0], + entry[1].get_file_type())) + + def _set_busy(self, busy): + if busy: + self.get_window().set_cursor(self._busy_cursor) + else: + self.get_window().set_cursor(None) + Gdk.flush() + + 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._set_busy(True) + 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: + path = self._store.get_path(piter) + self._treeview.get_selection().select_path(path) + + self._set_busy(False) + + # FIXME: override doesn't work anymore for some reason, if we override + # the widget is not realized + def on_show(self, data=None): + # 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.new(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.get_indices()[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 = Gio.file_new_for_uri(uri) + + if Gedit.utils_is_valid_location(gfile) or \ + (os.path.isabs(uri) and gfile.query_exists()): + return gfile + else: + return None + + 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.FileType.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: + # We destroy the popup in an idle callback to work around a crash that happens with + # GTK_IM_MODULE=xim. See https://bugzilla.gnome.org/show_bug.cgi?id=737711 . + GLib.idle_add(self.destroy) + + if not rows: + gfile = self._direct_file() + + if gfile and self._handler(gfile): + GLib.idle_add(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 = { + Gdk.KEY_Down: 1, + Gdk.KEY_Up: -1, + Gdk.KEY_Page_Down: 5, + Gdk.KEY_Page_Up: -5 + } + + if event.keyval == Gdk.KEY_Escape: + self.destroy() + return True + elif event.keyval in move_mapping: + return self._move_selection(move_mapping[event.keyval], event.state & Gdk.ModifierType.CONTROL_MASK, event.state & Gdk.ModifierType.SHIFT_MASK) + elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter, Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]: + return self._activate() + elif event.keyval == Gdk.KEY_space and event.state & Gdk.ModifierType.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.ResponseType.ACCEPT or not self._activate(): + self.destroy() + + def do_configure_event(self, event): + if self.get_realized(): + alloc = self.get_allocation() + self._size = (alloc.width, alloc.height) + + return Gtk.Dialog.do_configure_event(self, event) + + 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 is not None) + self._info_label.set_markup(fname or '') + + def on_focus_entry(self, group, accel, keyval, modifier): + self._entry.grab_focus() + +# ex:ts=4:et: diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py new file mode 100644 index 0000000..6792f9b --- /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, see <http://www.gnu.org/licenses/>. + +from gi.repository import Gio, Gtk + + +class VirtualDirectory(object): + 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, flags, callback): + return self._children + + def append(self, child): + if not child.is_native(): + return + + try: + info = child.query_info("standard::*", + Gio.FileQueryInfoFlags.NONE, + None) + + if info: + self._children.append((child, info)) + except Exception as e: + pass + + +class RecentDocumentsDirectory(VirtualDirectory): + def __init__(self, maxitems=200): + VirtualDirectory.__init__(self, 'recent') + + self._maxitems = maxitems + self.fill() + + def fill(self): + manager = Gtk.RecentManager.get_default() + + items = manager.get_items() + items.sort(key=lambda a: a.get_visited(), reverse=True) + + added = 0 + + for item in items: + if item.has_group('gedit'): + self.append(Gio.file_new_for_uri(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_file().get_location() + + if location: + self.append(location) + +# ex:ts=4:et: diff --git a/plugins/snippets/data/c.xml b/plugins/snippets/data/c.xml new file mode 100644 index 0000000..f46ceed --- /dev/null +++ b/plugins/snippets/data/c.xml @@ -0,0 +1,281 @@ +<?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, see <http://www.gnu.org/licenses/>. + */ + +$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 ${2}. If not, see <http://www.gnu.org/licenses/>. + */ + +$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() +> + +typedef struct _$<[1]: return camel_str >Private +{ +} $<[1]: return camel_str >Private; + +G_DEFINE_TYPE_WITH_PRIVATE ($<[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; +} + +static void +$<[1]: return low_str >_init ($<[1]: return camel_str> *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() +> +G_DEFINE_INTERFACE ($<[1]: return camel_str >, $<[1]: return low_str >, ${2:G_TYPE_OBJECT}) + +/* 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); +}]]></text> + <tag>ginterface</tag> + <description>GObject interface</description> + </snippet> + <snippet> + <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() +camel_str = '' + +for t in components: + camel_str += t.capitalize() +> + +struct _$<[1]: return camel_str > +{ +}; + +G_DEFINE_BOXED_TYPE ($<[1]: return camel_str >, $<[1]: return low_str >, $<[1]: return low_str >_${2:copy}, $<[1]: return low_str >_${3:free}) + +$<[1]: return camel_str > * +$<[1]: return low_str >_${2:copy} ($<[1]: return camel_str > *${4:boxed_name}) +{ + +} + +void +$<[1]: return low_str >_${3:free} ($<[1]: return camel_str > *${4:boxed_name}) +{ + +}]]></text> + <tag>gboxed</tag> + <description>GBoxed template</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/chdr.xml b/plugins/snippets/data/chdr.xml new file mode 100644 index 0000000..2ce94d7 --- /dev/null +++ b/plugins/snippets/data/chdr.xml @@ -0,0 +1,258 @@ +<?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, see <http://www.gnu.org/licenses/>. + */ + +$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 ${2}. If not, see <http://www.gnu.org/licenses/>. + */ + +$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 +$< +global camel_str, module, name, type_str +components = $1.split('_') +module = components[0].upper() +name = '_'.join(components[1:]).upper() +type_str = '_'.join([components[0], 'TYPE'] + components[1:]).upper() +camel_str = '' + +for t in components: + camel_str += t.capitalize() +> +#include <${2:glib-object.h}> + +G_BEGIN_DECLS + +#define $<[1]: return type_str > ($<[1]: return $1.lower() >_get_type ()) +G_DECLARE_DERIVABLE_TYPE ($<[1]: return camel_str >, $<[1]: return $1.lower() >, $<[1]: return module >, $<[1]: return name >, ${3:GObject}) + +struct _$<[1]: return camel_str >Class +{ + $3Class parent_class; +}; + +$<[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> + <snippet> + <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)', '((' + camel_str + ' *)obj)'], \ +['#define ' + $1 + '_CONST(obj)', '((' + camel_str + ' const *)obj)'], \ +] + +return align(items) > + +$<[1]: +items = [ \ +['typedef struct _' + camel_str, camel_str + ';'], \ +] + +return align(items) > + +GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST; +$<[1]: return camel_str > *$< return $1.lower()>_${3:copy} ($<[1]: return camel_str > *${4:boxed_name}); +void $< return $1.lower()>_${5:free} ($<[1]: return camel_str > *${4:boxed_name}); + +$0 +G_END_DECLS + +#endif /* $1_H */]]></text> + <tag>gboxed</tag> + <description>GBoxed template</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/cpp.xml b/plugins/snippets/data/cpp.xml new file mode 100644 index 0000000..1d4c31c --- /dev/null +++ b/plugins/snippets/data/cpp.xml @@ -0,0 +1,180 @@ +<?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, see <http://www.gnu.org/licenses/>. + */ + + $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 ${2}. If not, see <http://www.gnu.org/licenses/>. + */ + + $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 100644 index 0000000..babca91 --- /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 100644 index 0000000..d2a07de --- /dev/null +++ b/plugins/snippets/data/docbook.xml @@ -0,0 +1,2645 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + DocBook 4.5 snippets according to DocBook: The Definitive Guide (v2.0.17) + Copyright (C) 2012, 2013 Jaromir Hradilek + + Home Page: https://github.com/jhradilek/gedit-snippets + Last Change: 16 February 2013 +--> +<snippets language="docbook"> + <!-- Core DocBook Snippets: --> + <snippet id="abbrev"> + <text><![CDATA[<abbrev>${1}</abbrev>]]></text> + <tag>abbrev</tag> + <description>abbrev</description> + </snippet> + <snippet id="abstract"> + <text><![CDATA[<abstract> + ${1} +</abstract>]]></text> + <tag>abstract</tag> + <description>abstract</description> + </snippet> + <snippet id="accel"> + <text><![CDATA[<accel>${1}</accel>]]></text> + <tag>accel</tag> + <description>accel</description> + </snippet> + <snippet id="ackno"> + <text><![CDATA[<ackno> + ${1} +</ackno>]]></text> + <tag>ackno</tag> + <description>ackno</description> + </snippet> + <snippet id="acronym"> + <text><![CDATA[<acronym>${1}</acronym>]]></text> + <tag>acronym</tag> + <description>acronym</description> + </snippet> + <snippet id="action"> + <text><![CDATA[<action>${1}</action>]]></text> + <tag>action</tag> + <description>action</description> + </snippet> + <snippet id="address"> + <text><![CDATA[<address> + ${1} +</address>]]></text> + <tag>address</tag> + <description>address</description> + </snippet> + <snippet id="affiliation"> + <text><![CDATA[<affiliation> + ${1} +</affiliation>]]></text> + <tag>affiliation</tag> + <description>affiliation</description> + </snippet> + <snippet id="alt"> + <text><![CDATA[<alt>${1}</alt>]]></text> + <tag>alt</tag> + <description>alt</description> + </snippet> + <snippet id="anchor"> + <text><![CDATA[<anchor id="${1}" />]]></text> + <tag>anchor</tag> + <description>anchor</description> + </snippet> + <snippet id="answer"> + <text><![CDATA[<answer> + ${1} +</answer>]]></text> + <tag>answer</tag> + <description>answer</description> + </snippet> + <snippet id="appendix"> + <text><![CDATA[<appendix id="${1}"> + ${2} +</appendix>]]></text> + <tag>appendix</tag> + <description>appendix</description> + </snippet> + <snippet id="appendixinfo"> + <text><![CDATA[<appendixinfo> + ${1} +</appendixinfo>]]></text> + <tag>appendixinfo</tag> + <description>appendixinfo</description> + </snippet> + <snippet id="application"> + <text><![CDATA[<application>${1}</application>]]></text> + <tag>application</tag> + <description>application</description> + </snippet> + <snippet id="area"> + <text><![CDATA[<area id="${1}" coords="${2}" />]]></text> + <tag>area</tag> + <description>area</description> + </snippet> + <snippet id="areaset"> + <text><![CDATA[<areaset id="${1}" coords="${2}"> + ${3} +</areaset>]]></text> + <tag>areaset</tag> + <description>areaset</description> + </snippet> + <snippet id="areaspec"> + <text><![CDATA[<areaspec units="${1}"> + ${2} +</areaspec>]]></text> + <tag>areaspec</tag> + <description>areaspec</description> + </snippet> + <snippet id="arg"> + <text><![CDATA[<arg>${1}</arg>]]></text> + <tag>arg</tag> + <description>arg</description> + </snippet> + <snippet id="article"> + <text><![CDATA[<article id="${1}"> + ${2} +</article>]]></text> + <tag>article</tag> + <description>article</description> + </snippet> + <snippet id="articleinfo"> + <text><![CDATA[<articleinfo> + ${1} +</articleinfo>]]></text> + <tag>articleinfo</tag> + <description>articleinfo</description> + </snippet> + <snippet id="artpagenums"> + <text><![CDATA[<artpagenums>${1}</artpagenums>]]></text> + <tag>artpagenums</tag> + <description>artpagenums</description> + </snippet> + <snippet id="attribution"> + <text><![CDATA[<attribution>${1}</attribution>]]></text> + <tag>attribution</tag> + <description>attribution</description> + </snippet> + <snippet id="audiodata"> + <text><![CDATA[<audiodata fileref="${1}" />]]></text> + <tag>audiodata</tag> + <description>audiodata</description> + </snippet> + <snippet id="audioobject"> + <text><![CDATA[<audioobject> + ${1} +</audioobject>]]></text> + <tag>audioobject</tag> + <description>audioobject</description> + </snippet> + <snippet id="author"> + <text><![CDATA[<author> + ${1} +</author>]]></text> + <tag>author</tag> + <description>author</description> + </snippet> + <snippet id="authorblurb"> + <text><![CDATA[<authorblurb> + ${1} +</authorblurb>]]></text> + <tag>authorblurb</tag> + <description>authorblurb</description> + </snippet> + <snippet id="authorgroup"> + <text><![CDATA[<authorgroup> + ${1} +</authorgroup>]]></text> + <tag>authorgroup</tag> + <description>authorgroup</description> + </snippet> + <snippet id="authorinitials"> + <text><![CDATA[<authorinitials>${1}</authorinitials>]]></text> + <tag>authorinitials</tag> + <description>authorinitials</description> + </snippet> + <snippet id="beginpage"> + <text><![CDATA[<beginpage pagenum="${1}" />]]></text> + <tag>beginpage</tag> + <description>beginpage</description> + </snippet> + <snippet id="bibliocoverage"> + <text><![CDATA[<bibliocoverage> + ${1} +</bibliocoverage>]]></text> + <tag>bibliocoverage</tag> + <description>bibliocoverage</description> + </snippet> + <snippet id="bibliodiv"> + <text><![CDATA[<bibliodiv> + ${1} +</bibliodiv>]]></text> + <tag>bibliodiv</tag> + <description>bibliodiv</description> + </snippet> + <snippet id="biblioentry"> + <text><![CDATA[<biblioentry> + ${1} +</biblioentry>]]></text> + <tag>biblioentry</tag> + <description>biblioentry</description> + </snippet> + <snippet id="bibliography"> + <text><![CDATA[<bibliography> + ${1} +</bibliography>]]></text> + <tag>bibliography</tag> + <description>bibliography</description> + </snippet> + <snippet id="bibliographyinfo"> + <text><![CDATA[<bibliographyinfo> + ${1} +</bibliographyinfo>]]></text> + <tag>bibliographyinfo</tag> + <description>bibliographyinfo</description> + </snippet> + <snippet id="biblioid"> + <text><![CDATA[<biblioid class="${1:isbn}">${2}</biblioid>]]></text> + <tag>biblioid</tag> + <description>biblioid</description> + </snippet> + <snippet id="bibliolist"> + <text><![CDATA[<bibliolist> + ${1} +</bibliolist>]]></text> + <tag>bibliolist</tag> + <description>bibliolist</description> + </snippet> + <snippet id="bibliomisc"> + <text><![CDATA[<bibliomisc> + ${1} +</bibliomisc>]]></text> + <tag>bibliomisc</tag> + <description>bibliomisc</description> + </snippet> + <snippet id="bibliomixed"> + <text><![CDATA[<bibliomixed> + ${1} +</bibliomixed>]]></text> + <tag>bibliomixed</tag> + <description>bibliomixed</description> + </snippet> + <snippet id="bibliomset"> + <text><![CDATA[<bibliomset relation="${1}"> + ${2} +</bibliomset>]]></text> + <tag>bibliomset</tag> + <description>bibliomset</description> + </snippet> + <snippet id="biblioref"> + <text><![CDATA[<biblioref linkend="${1}" />]]></text> + <tag>biblioref</tag> + <description>biblioref</description> + </snippet> + <snippet id="bibliorelation"> + <text><![CDATA[<bibliorelation type="${1}" class="${2}">${3}</bibliorelation>]]></text> + <tag>bibliorelation</tag> + <description>bibliorelation</description> + </snippet> + <snippet id="biblioset"> + <text><![CDATA[<biblioset relation="${1}"> + ${2} +</biblioset>]]></text> + <tag>biblioset</tag> + <description>biblioset</description> + </snippet> + <snippet id="bibliosource"> + <text><![CDATA[<bibliosource class="${1:isbn}">${2}</bibliosource>]]></text> + <tag>bibliosource</tag> + <description>bibliosource</description> + </snippet> + <snippet id="blockinfo"> + <text><![CDATA[<blockinfo> + ${1} +</blockinfo>]]></text> + <tag>blockinfo</tag> + <description>blockinfo</description> + </snippet> + <snippet id="blockquote"> + <text><![CDATA[<blockquote> + ${1} +</blockquote>]]></text> + <tag>blockquote</tag> + <description>blockquote</description> + </snippet> + <snippet id="book"> + <text><![CDATA[<book id="${1}"> + ${2} +</book>]]></text> + <tag>book</tag> + <description>book</description> + </snippet> + <snippet id="bookinfo"> + <text><![CDATA[<bookinfo> + ${1} +</bookinfo>]]></text> + <tag>bookinfo</tag> + <description>bookinfo</description> + </snippet> + <snippet id="bridgehead"> + <text><![CDATA[<bridgehead id="${1}">${2}</bridgehead>]]></text> + <tag>bridgehead</tag> + <description>bridgehead</description> + </snippet> + <snippet id="callout"> + <text><![CDATA[<callout arearefs="${1}"> + ${2} +</callout>]]></text> + <tag>callout</tag> + <description>callout</description> + </snippet> + <snippet id="calloutlist"> + <text><![CDATA[<calloutlist> + ${1} +</calloutlist>]]></text> + <tag>calloutlist</tag> + <description>calloutlist</description> + </snippet> + <snippet id="caption"> + <text><![CDATA[<caption> + ${1} +</caption>]]></text> + <tag>caption</tag> + <description>caption</description> + </snippet> + <snippet id="caution"> + <text><![CDATA[<caution> + ${1} +</caution>]]></text> + <tag>caution</tag> + <description>caution</description> + </snippet> + <snippet id="chapter"> + <text><![CDATA[<chapter id="${1}"> + ${2} +</chapter>]]></text> + <tag>chapter</tag> + <description>chapter</description> + </snippet> + <snippet id="chapterinfo"> + <text><![CDATA[<chapterinfo> + ${1} +</chapterinfo>]]></text> + <tag>chapterinfo</tag> + <description>chapterinfo</description> + </snippet> + <snippet id="citation"> + <text><![CDATA[<citation>${1}</citation>]]></text> + <tag>citation</tag> + <description>citation</description> + </snippet> + <snippet id="citebiblioid"> + <text><![CDATA[<citebiblioid class="${1:isbn}">${2}</citebiblioid>]]></text> + <tag>citebiblioid</tag> + <description>citebiblioid</description> + </snippet> + <snippet id="citerefentry"> + <text><![CDATA[<citerefentry>${1}</citerefentry>]]></text> + <tag>citerefentry</tag> + <description>citerefentry</description> + </snippet> + <snippet id="citetitle"> + <text><![CDATA[<citetitle pubwork="${1:book}">${2}</citetitle>]]></text> + <tag>citetitle</tag> + <description>citetitle</description> + </snippet> + <snippet id="city"> + <text><![CDATA[<city>${1}</city>]]></text> + <tag>city</tag> + <description>city</description> + </snippet> + <snippet id="classname"> + <text><![CDATA[<classname>${1}</classname>]]></text> + <tag>classname</tag> + <description>classname</description> + </snippet> + <snippet id="classsynopsis"> + <text><![CDATA[<classsynopsis class="${1}" language="${2}"> + ${3} +</classsynopsis>]]></text> + <tag>classsynopsis</tag> + <description>classsynopsis</description> + </snippet> + <snippet id="classsynopsisinfo"> + <text><![CDATA[<classsynopsisinfo> + ${1} +</classsynopsisinfo>]]></text> + <tag>classsynopsisinfo</tag> + <description>classsynopsisinfo</description> + </snippet> + <snippet id="cmdsynopsis"> + <text><![CDATA[<cmdsynopsis> + ${1} +</cmdsynopsis>]]></text> + <tag>cmdsynopsis</tag> + <description>cmdsynopsis</description> + </snippet> + <snippet id="co"> + <text><![CDATA[<co label="${1}" linkends="${2}" />]]></text> + <tag>co</tag> + <description>co</description> + </snippet> + <snippet id="code"> + <text><![CDATA[<code language="${1}">${2}</code>]]></text> + <tag>code</tag> + <description>code</description> + </snippet> + <snippet id="col"> + <text><![CDATA[<col> + ${1} +</col>]]></text> + <tag>col</tag> + <description>col</description> + </snippet> + <snippet id="colgroup"> + <text><![CDATA[<colgroup> + ${1} +</colgroup>]]></text> + <tag>colgroup</tag> + <description>colgroup</description> + </snippet> + <snippet id="collab"> + <text><![CDATA[<collab> + ${1} +</collab>]]></text> + <tag>collab</tag> + <description>collab</description> + </snippet> + <snippet id="collabname"> + <text><![CDATA[<collabname>${1}</collabname>]]></text> + <tag>collabname</tag> + <description>collabname</description> + </snippet> + <snippet id="colophon"> + <text><![CDATA[<colophon> + ${1} +</colophon>]]></text> + <tag>colophon</tag> + <description>colophon</description> + </snippet> + <snippet id="colspec"> + <text><![CDATA[<colspec colname="${1}" colnum="${2}" colwidth="${3}" />]]></text> + <tag>colspec</tag> + <description>colspec</description> + </snippet> + <snippet id="command"> + <text><![CDATA[<command>${1}</command>]]></text> + <tag>command</tag> + <description>command</description> + </snippet> + <snippet id="computeroutput"> + <text><![CDATA[<computeroutput>${1}</computeroutput>]]></text> + <tag>computeroutput</tag> + <description>computeroutput</description> + </snippet> + <snippet id="confdates"> + <text><![CDATA[<confdates>${1}</confdates>]]></text> + <tag>confdates</tag> + <description>confdates</description> + </snippet> + <snippet id="confgroup"> + <text><![CDATA[<confgroup> + ${1} +</confgroup>]]></text> + <tag>confgroup</tag> + <description>confgroup</description> + </snippet> + <snippet id="confnum"> + <text><![CDATA[<confnum>${1}</confnum>]]></text> + <tag>confnum</tag> + <description>confnum</description> + </snippet> + <snippet id="confsponsor"> + <text><![CDATA[<confsponsor>${1}</confsponsor>]]></text> + <tag>confsponsor</tag> + <description>confsponsor</description> + </snippet> + <snippet id="conftitle"> + <text><![CDATA[<conftitle>${1}</conftitle>]]></text> + <tag>conftitle</tag> + <description>conftitle</description> + </snippet> + <snippet id="constant"> + <text><![CDATA[<constant>${1}</constant>]]></text> + <tag>constant</tag> + <description>constant</description> + </snippet> + <snippet id="constructorsynopsis"> + <text><![CDATA[<constructorsynopsis language="${1}"> + ${2} +</constructorsynopsis>]]></text> + <tag>constructorsynopsis</tag> + <description>constructorsynopsis</description> + </snippet> + <snippet id="contractnum"> + <text><![CDATA[<contractnum>${1}</contractnum>]]></text> + <tag>contractnum</tag> + <description>contractnum</description> + </snippet> + <snippet id="contractsponsor"> + <text><![CDATA[<contractsponsor>${1}</contractsponsor>]]></text> + <tag>contractsponsor</tag> + <description>contractsponsor</description> + </snippet> + <snippet id="contrib"> + <text><![CDATA[<contrib>${1}</contrib>]]></text> + <tag>contrib</tag> + <description>contrib</description> + </snippet> + <snippet id="copyright"> + <text><![CDATA[<copyright> + ${1} +</copyright>]]></text> + <tag>copyright</tag> + <description>copyright</description> + </snippet> + <snippet id="coref"> + <text><![CDATA[<coref label="${1}" linkend="${1}" />]]></text> + <tag>coref</tag> + <description>coref</description> + </snippet> + <snippet id="corpauthor"> + <text><![CDATA[<corpauthor>${1}</corpauthor>]]></text> + <tag>corpauthor</tag> + <description>corpauthor</description> + </snippet> + <snippet id="corpcredit"> + <text><![CDATA[<corpcredit>${1}</corpcredit>]]></text> + <tag>corpcredit</tag> + <description>corpcredit</description> + </snippet> + <snippet id="corpname"> + <text><![CDATA[<corpname>${1}</corpname>]]></text> + <tag>corpname</tag> + <description>corpname</description> + </snippet> + <snippet id="country"> + <text><![CDATA[<country>${1}</country>]]></text> + <tag>country</tag> + <description>country</description> + </snippet> + <snippet id="database"> + <text><![CDATA[<database class="${1}">${2}</database>]]></text> + <tag>database</tag> + <description>database</description> + </snippet> + <snippet id="date"> + <text><![CDATA[<date>${1}</date>]]></text> + <tag>date</tag> + <description>date</description> + </snippet> + <snippet id="dedication"> + <text><![CDATA[<dedication> + ${1} +</dedication>]]></text> + <tag>dedication</tag> + <description>dedication</description> + </snippet> + <snippet id="destructorsynopsis"> + <text><![CDATA[<destructorsynopsis language="${1}"> + ${2} +</destructorsynopsis>]]></text> + <tag>destructorsynopsis</tag> + <description>destructorsynopsis</description> + </snippet> + <snippet id="edition"> + <text><![CDATA[<edition>${1}</edition>]]></text> + <tag>edition</tag> + <description>edition</description> + </snippet> + <snippet id="editor"> + <text><![CDATA[<editor> + ${1} +</editor>]]></text> + <tag>editor</tag> + <description>editor</description> + </snippet> + <snippet id="email"> + <text><![CDATA[<email>${1}</email>]]></text> + <tag>email</tag> + <description>email</description> + </snippet> + <snippet id="emphasis"> + <text><![CDATA[<emphasis>${1}</emphasis>]]></text> + <tag>emphasis</tag> + <description>emphasis</description> + </snippet> + <snippet id="entry"> + <text><![CDATA[<entry>${1}</entry>]]></text> + <tag>entry</tag> + <description>entry</description> + </snippet> + <snippet id="entrytbl"> + <text><![CDATA[<entrytbl cols="${1}"> + ${2} +</entrytbl>]]></text> + <tag>entrytbl</tag> + <description>entrytbl</description> + </snippet> + <snippet id="envar"> + <text><![CDATA[<envar>${1}</envar>]]></text> + <tag>envar</tag> + <description>envar</description> + </snippet> + <snippet id="epigraph"> + <text><![CDATA[<epigraph> + ${1} +</epigraph>]]></text> + <tag>epigraph</tag> + <description>epigraph</description> + </snippet> + <snippet id="equation"> + <text><![CDATA[<equation> + ${1} +</equation>]]></text> + <tag>equation</tag> + <description>equation</description> + </snippet> + <snippet id="errorcode"> + <text><![CDATA[<errorcode>${1}</errorcode>]]></text> + <tag>errorcode</tag> + <description>errorcode</description> + </snippet> + <snippet id="errorname"> + <text><![CDATA[<errorname>${1}</errorname>]]></text> + <tag>errorname</tag> + <description>errorname</description> + </snippet> + <snippet id="errortext"> + <text><![CDATA[<errortext>${1}</errortext>]]></text> + <tag>errortext</tag> + <description>errortext</description> + </snippet> + <snippet id="errortype"> + <text><![CDATA[<errortype>${1}</errortype>]]></text> + <tag>errortype</tag> + <description>errortype</description> + </snippet> + <snippet id="example"> + <text><![CDATA[<example id="${1}"> + ${2} +</example>]]></text> + <tag>example</tag> + <description>example</description> + </snippet> + <snippet id="exceptionname"> + <text><![CDATA[<exceptionname>${1}</exceptionname>]]></text> + <tag>exceptionname</tag> + <description>exceptionname</description> + </snippet> + <snippet id="fax"> + <text><![CDATA[<fax>${1}</fax>]]></text> + <tag>fax</tag> + <description>fax</description> + </snippet> + <snippet id="fieldsynopsis"> + <text><![CDATA[<fieldsynopsis language="${1}"> + ${2} +</fieldsynopsis>]]></text> + <tag>fieldsynopsis</tag> + <description>fieldsynopsis</description> + </snippet> + <snippet id="figure"> + <text><![CDATA[<figure id="${1}"> + ${2} +</figure>]]></text> + <tag>figure</tag> + <description>figure</description> + </snippet> + <snippet id="filename"> + <text><![CDATA[<filename>${1}</filename>]]></text> + <tag>filename</tag> + <description>filename</description> + </snippet> + <snippet id="firstname"> + <text><![CDATA[<firstname>${1}</firstname>]]></text> + <tag>firstname</tag> + <description>firstname</description> + </snippet> + <snippet id="firstterm"> + <text><![CDATA[<firstterm>${1}</firstterm>]]></text> + <tag>firstterm</tag> + <description>firstterm</description> + </snippet> + <snippet id="footnote"> + <text><![CDATA[<footnote> + ${1} +</footnote>]]></text> + <tag>footnote</tag> + <description>footnote</description> + </snippet> + <snippet id="footnoteref"> + <text><![CDATA[<footnoteref linkend="${1}" />]]></text> + <tag>footnoteref</tag> + <description>footnoteref</description> + </snippet> + <snippet id="foreignphrase"> + <text><![CDATA[<foreignphrase>${1}</foreignphrase>]]></text> + <tag>foreignphrase</tag> + <description>foreignphrase</description> + </snippet> + <snippet id="formalpara"> + <text><![CDATA[<formalpara> + ${1} +</formalpara>]]></text> + <tag>formalpara</tag> + <description>formalpara</description> + </snippet> + <snippet id="funcdef"> + <text><![CDATA[<funcdef>${1}</funcdef>]]></text> + <tag>funcdef</tag> + <description>funcdef</description> + </snippet> + <snippet id="funcparams"> + <text><![CDATA[<funcparams>${1}</funcparams>]]></text> + <tag>funcparams</tag> + <description>funcparams</description> + </snippet> + <snippet id="funcprototype"> + <text><![CDATA[<funcprototype> + ${1} +</funcprototype>]]></text> + <tag>funcprototype</tag> + <description>funcprototype</description> + </snippet> + <snippet id="funcsynopsis"> + <text><![CDATA[<funcsynopsis> + ${1} +</funcsynopsis>]]></text> + <tag>funcsynopsis</tag> + <description>funcsynopsis</description> + </snippet> + <snippet id="funcsynopsisinfo"> + <text><![CDATA[<funcsynopsisinfo> + ${1} +</funcsynopsisinfo>]]></text> + <tag>funcsynopsisinfo</tag> + <description>funcsynopsisinfo</description> + </snippet> + <snippet id="function"> + <text><![CDATA[<function>${1}</function>]]></text> + <tag>function</tag> + <description>function</description> + </snippet> + <snippet id="glossary"> + <text><![CDATA[<glossary id="${1}"> + ${2} +</glossary>]]></text> + <tag>glossary</tag> + <description>glossary</description> + </snippet> + <snippet id="glossaryinfo"> + <text><![CDATA[<glossaryinfo> + ${1} +</glossaryinfo>]]></text> + <tag>glossaryinfo</tag> + <description>glossaryinfo</description> + </snippet> + <snippet id="glossdef"> + <text><![CDATA[<glossdef> + ${1} +</glossdef>]]></text> + <tag>glossdef</tag> + <description>glossdef</description> + </snippet> + <snippet id="glossdiv"> + <text><![CDATA[<glossdiv> + ${1} +</glossdiv>]]></text> + <tag>glossdiv</tag> + <description>glossdiv</description> + </snippet> + <snippet id="glossentry"> + <text><![CDATA[<glossentry> + ${1} +</glossentry>]]></text> + <tag>glossentry</tag> + <description>glossentry</description> + </snippet> + <snippet id="glosslist"> + <text><![CDATA[<glosslist> + ${1} +</glosslist>]]></text> + <tag>glosslist</tag> + <description>glosslist</description> + </snippet> + <snippet id="glosssee"> + <text><![CDATA[<glosssee otherterm="${1}">${2}</glosssee>]]></text> + <tag>glosssee</tag> + <description>glosssee</description> + </snippet> + <snippet id="glossseealso"> + <text><![CDATA[<glossseealso otherterm="${1}">${2}</glossseealso>]]></text> + <tag>glossseealso</tag> + <description>glossseealso</description> + </snippet> + <snippet id="glossterm"> + <text><![CDATA[<glossterm>${1}</glossterm>]]></text> + <tag>glossterm</tag> + <description>glossterm</description> + </snippet> + <snippet id="graphic"> + <text><![CDATA[<graphic fileref="${1}" />]]></text> + <tag>graphic</tag> + <description>graphic</description> + </snippet> + <snippet id="graphicco"> + <text><![CDATA[<graphicco> + ${1} +</graphicco>]]></text> + <tag>graphicco</tag> + <description>graphicco</description> + </snippet> + <snippet id="group"> + <text><![CDATA[<group> + ${1} +</group>]]></text> + <tag>group</tag> + <description>group</description> + </snippet> + <snippet id="guibutton"> + <text><![CDATA[<guibutton>${1}</guibutton>]]></text> + <tag>guibutton</tag> + <description>guibutton</description> + </snippet> + <snippet id="guiicon"> + <text><![CDATA[<guiicon>${1}</guiicon>]]></text> + <tag>guiicon</tag> + <description>guiicon</description> + </snippet> + <snippet id="guilabel"> + <text><![CDATA[<guilabel>${1}</guilabel>]]></text> + <tag>guilabel</tag> + <description>guilabel</description> + </snippet> + <snippet id="guimenu"> + <text><![CDATA[<guimenu>${1}</guimenu>]]></text> + <tag>guimenu</tag> + <description>guimenu</description> + </snippet> + <snippet id="guimenuitem"> + <text><![CDATA[<guimenuitem>${1}</guimenuitem>]]></text> + <tag>guimenuitem</tag> + <description>guimenuitem</description> + </snippet> + <snippet id="guisubmenu"> + <text><![CDATA[<guisubmenu>${1}</guisubmenu>]]></text> + <tag>guisubmenu</tag> + <description>guisubmenu</description> + </snippet> + <snippet id="hardware"> + <text><![CDATA[<hardware>${1}</hardware>]]></text> + <tag>hardware</tag> + <description>hardware</description> + </snippet> + <snippet id="highlights"> + <text><![CDATA[<highlights> + ${1} +</highlights>]]></text> + <tag>highlights</tag> + <description>highlights</description> + </snippet> + <snippet id="holder"> + <text><![CDATA[<holder>${1}</holder>]]></text> + <tag>holder</tag> + <description>holder</description> + </snippet> + <snippet id="honorific"> + <text><![CDATA[<honorific>${1}</honorific>]]></text> + <tag>honorific</tag> + <description>honorific</description> + </snippet> + <snippet id="imagedata"> + <text><![CDATA[<imagedata fileref="${1}" format="${2:PNG}" scalefit="${3:0}" />]]></text> + <tag>imagedata</tag> + <description>imagedata</description> + </snippet> + <snippet id="imageobject"> + <text><![CDATA[<imageobject> + ${1} +</imageobject>]]></text> + <tag>imageobject</tag> + <description>imageobject</description> + </snippet> + <snippet id="imageobjectco"> + <text><![CDATA[<imageobjectco> + ${1} +</imageobjectco>]]></text> + <tag>imageobjectco</tag> + <description>imageobjectco</description> + </snippet> + <snippet id="important"> + <text><![CDATA[<important> + ${1} +</important>]]></text> + <tag>important</tag> + <description>important</description> + </snippet> + <snippet id="index"> + <text><![CDATA[<index> + ${1} +</index>]]></text> + <tag>index</tag> + <description>index</description> + </snippet> + <snippet id="indexdiv"> + <text><![CDATA[<indexdiv> + ${1} +</indexdiv>]]></text> + <tag>indexdiv</tag> + <description>indexdiv</description> + </snippet> + <snippet id="indexentry"> + <text><![CDATA[<indexentry> + ${1} +</indexentry>]]></text> + <tag>indexentry</tag> + <description>indexentry</description> + </snippet> + <snippet id="indexinfo"> + <text><![CDATA[<indexinfo> + ${1} +</indexinfo>]]></text> + <tag>indexinfo</tag> + <description>indexinfo</description> + </snippet> + <snippet id="indexterm"> + <text><![CDATA[<indexterm> + ${1} +</indexterm>]]></text> + <tag>indexterm</tag> + <description>indexterm</description> + </snippet> + <snippet id="informalequation"> + <text><![CDATA[<informalequation> + ${1} +</informalequation>]]></text> + <tag>informalequation</tag> + <description>informalequation</description> + </snippet> + <snippet id="informalexample"> + <text><![CDATA[<informalexample> + ${1} +</informalexample>]]></text> + <tag>informalexample</tag> + <description>informalexample</description> + </snippet> + <snippet id="informalfigure"> + <text><![CDATA[<informalfigure> + ${1} +</informalfigure>]]></text> + <tag>informalfigure</tag> + <description>informalfigure</description> + </snippet> + <snippet id="informaltable"> + <text><![CDATA[<informaltable> + ${1} +</informaltable>]]></text> + <tag>informaltable</tag> + <description>informaltable</description> + </snippet> + <snippet id="initializer"> + <text><![CDATA[<initializer>${1}</initializer>]]></text> + <tag>initializer</tag> + <description>initializer</description> + </snippet> + <snippet id="inlineequation"> + <text><![CDATA[<inlineequation> + ${1} +</inlineequation>]]></text> + <tag>inlineequation</tag> + <description>inlineequation</description> + </snippet> + <snippet id="inlinegraphic"> + <text><![CDATA[<inlinegraphic fileref="${1}" format="${2:PNG}" scalefit="${3:0}" />]]></text> + <tag>inlinegraphic</tag> + <description>inlinegraphic</description> + </snippet> + <snippet id="inlinemediaobject"> + <text><![CDATA[<inlinemediaobject> + ${1} +</inlinemediaobject>]]></text> + <tag>inlinemediaobject</tag> + <description>inlinemediaobject</description> + </snippet> + <snippet id="interface"> + <text><![CDATA[<interface>${1}</interface>]]></text> + <tag>interface</tag> + <description>interface</description> + </snippet> + <snippet id="interfacename"> + <text><![CDATA[<interfacename>${1}</interfacename>]]></text> + <tag>interfacename</tag> + <description>interfacename</description> + </snippet> + <snippet id="invpartnumber"> + <text><![CDATA[<invpartnumber>${1}</invpartnumber>]]></text> + <tag>invpartnumber</tag> + <description>invpartnumber</description> + </snippet> + <snippet id="isbn"> + <text><![CDATA[<isbn>${1}</isbn>]]></text> + <tag>isbn</tag> + <description>isbn</description> + </snippet> + <snippet id="issn"> + <text><![CDATA[<issn>${1}</issn>]]></text> + <tag>issn</tag> + <description>issn</description> + </snippet> + <snippet id="issuenum"> + <text><![CDATA[<issuenum>${1}</issuenum>]]></text> + <tag>issuenum</tag> + <description>issuenum</description> + </snippet> + <snippet id="itemizedlist"> + <text><![CDATA[<itemizedlist> + ${1} +</itemizedlist>]]></text> + <tag>itemizedlist</tag> + <description>itemizedlist</description> + </snippet> + <snippet id="itermset"> + <text><![CDATA[<itermset> + ${1} +</itermset>]]></text> + <tag>itermset</tag> + <description>itermset</description> + </snippet> + <snippet id="jobtitle"> + <text><![CDATA[<jobtitle>${1}</jobtitle>]]></text> + <tag>jobtitle</tag> + <description>jobtitle</description> + </snippet> + <snippet id="keycap"> + <text><![CDATA[<keycap>${1}</keycap>]]></text> + <tag>keycap</tag> + <description>keycap</description> + </snippet> + <snippet id="keycode"> + <text><![CDATA[<keycode>${1}</keycode>]]></text> + <tag>keycode</tag> + <description>keycode</description> + </snippet> + <snippet id="keycombo"> + <text><![CDATA[<keycombo>${1}</keycombo>]]></text> + <tag>keycombo</tag> + <description>keycombo</description> + </snippet> + <snippet id="keysym"> + <text><![CDATA[<keysym>${1}</keysym>]]></text> + <tag>keysym</tag> + <description>keysym</description> + </snippet> + <snippet id="keyword"> + <text><![CDATA[<keyword>${1}</keyword>]]></text> + <tag>keyword</tag> + <description>keyword</description> + </snippet> + <snippet id="keywordset"> + <text><![CDATA[<keywordset> + ${1} +</keywordset>]]></text> + <tag>keywordset</tag> + <description>keywordset</description> + </snippet> + <snippet id="label"> + <text><![CDATA[<label>${1}</label>]]></text> + <tag>label</tag> + <description>label</description> + </snippet> + <snippet id="legalnotice"> + <text><![CDATA[<legalnotice> + ${1} +</legalnotice>]]></text> + <tag>legalnotice</tag> + <description>legalnotice</description> + </snippet> + <snippet id="lineage"> + <text><![CDATA[<lineage>${1}</lineage>]]></text> + <tag>lineage</tag> + <description>lineage</description> + </snippet> + <snippet id="lineannotation"> + <text><![CDATA[<lineannotation>${1}</lineannotation>]]></text> + <tag>lineannotation</tag> + <description>lineannotation</description> + </snippet> + <snippet id="link"> + <text><![CDATA[<link linkend="${1}">${2}</link>]]></text> + <tag>link</tag> + <description>link</description> + </snippet> + <snippet id="listitem"> + <text><![CDATA[<listitem> + ${1} +</listitem>]]></text> + <tag>listitem</tag> + <description>listitem</description> + </snippet> + <snippet id="literal"> + <text><![CDATA[<literal>${1}</literal>]]></text> + <tag>literal</tag> + <description>literal</description> + </snippet> + <snippet id="literallayout"> + <text><![CDATA[<literallayout>${1}</literallayout>]]></text> + <tag>literallayout</tag> + <description>literallayout</description> + </snippet> + <snippet id="lot"> + <text><![CDATA[<lot> + ${1} +</lot>]]></text> + <tag>lot</tag> + <description>lot</description> + </snippet> + <snippet id="lotentry"> + <text><![CDATA[<lotentry linkend="${1}"> + ${2} +</lotentry>]]></text> + <tag>lotentry</tag> + <description>lotentry</description> + </snippet> + <snippet id="manvolnum"> + <text><![CDATA[<manvolnum>${1}</manvolnum>]]></text> + <tag>manvolnum</tag> + <description>manvolnum</description> + </snippet> + <snippet id="markup"> + <text><![CDATA[<markup>${1}</markup>]]></text> + <tag>markup</tag> + <description>markup</description> + </snippet> + <snippet id="mathphrase"> + <text><![CDATA[<mathphrase>${1}</mathphrase>]]></text> + <tag>mathphrase</tag> + <description>mathphrase</description> + </snippet> + <snippet id="medialabel"> + <text><![CDATA[<medialabel>${1}</medialabel>]]></text> + <tag>medialabel</tag> + <description>medialabel</description> + </snippet> + <snippet id="mediaobject"> + <text><![CDATA[<mediaobject> + ${1} +</mediaobject>]]></text> + <tag>mediaobject</tag> + <description>mediaobject</description> + </snippet> + <snippet id="mediaobjectco"> + <text><![CDATA[<mediaobjectco> + ${1} +</mediaobjectco>]]></text> + <tag>mediaobjectco</tag> + <description>mediaobjectco</description> + </snippet> + <snippet id="member"> + <text><![CDATA[<member> + ${1} +</member>]]></text> + <tag>member</tag> + <description>member</description> + </snippet> + <snippet id="menuchoice"> + <text><![CDATA[<menuchoice>${1}</menuchoice>]]></text> + <tag>menuchoice</tag> + <description>menuchoice</description> + </snippet> + <snippet id="methodname"> + <text><![CDATA[<methodname>${1}</methodname>]]></text> + <tag>methodname</tag> + <description>methodname</description> + </snippet> + <snippet id="methodparam"> + <text><![CDATA[<methodparam>${1}</methodparam>]]></text> + <tag>methodparam</tag> + <description>methodparam</description> + </snippet> + <snippet id="methodsynopsis"> + <text><![CDATA[<methodsynopsis language="${1}"> + ${2} +</methodsynopsis>]]></text> + <tag>methodsynopsis</tag> + <description>methodsynopsis</description> + </snippet> + <snippet id="modespec"> + <text><![CDATA[<modespec application="${1}">${2}</modespec>]]></text> + <tag>modespec</tag> + <description>modespec</description> + </snippet> + <snippet id="modifier"> + <text><![CDATA[<modifier>${1}</modifier>]]></text> + <tag>modifier</tag> + <description>modifier</description> + </snippet> + <snippet id="mousebutton"> + <text><![CDATA[<mousebutton>${1}</mousebutton>]]></text> + <tag>mousebutton</tag> + <description>mousebutton</description> + </snippet> + <snippet id="msg"> + <text><![CDATA[<msg> + ${1} +</msg>]]></text> + <tag>msg</tag> + <description>msg</description> + </snippet> + <snippet id="msgaud"> + <text><![CDATA[<msgaud>${1}</msgaud>]]></text> + <tag>msgaud</tag> + <description>msgaud</description> + </snippet> + <snippet id="msgentry"> + <text><![CDATA[<msgentry> + ${1} +</msgentry>]]></text> + <tag>msgentry</tag> + <description>msgentry</description> + </snippet> + <snippet id="msgexplan"> + <text><![CDATA[<msgexplan> + ${1} +</msgexplan>]]></text> + <tag>msgexplan</tag> + <description>msgexplan</description> + </snippet> + <snippet id="msginfo"> + <text><![CDATA[<msginfo> + ${1} +</msginfo>]]></text> + <tag>msginfo</tag> + <description>msginfo</description> + </snippet> + <snippet id="msglevel"> + <text><![CDATA[<msglevel>${1}</msglevel>]]></text> + <tag>msglevel</tag> + <description>msglevel</description> + </snippet> + <snippet id="msgmain"> + <text><![CDATA[<msgmain> + ${1} +</msgmain>]]></text> + <tag>msgmain</tag> + <description>msgmain</description> + </snippet> + <snippet id="msgorig"> + <text><![CDATA[<msgorig>${1}</msgorig>]]></text> + <tag>msgorig</tag> + <description>msgorig</description> + </snippet> + <snippet id="msgrel"> + <text><![CDATA[<msgrel> + ${1} +</msgrel>]]></text> + <tag>msgrel</tag> + <description>msgrel</description> + </snippet> + <snippet id="msgset"> + <text><![CDATA[<msgset> + ${1} +</msgset>]]></text> + <tag>msgset</tag> + <description>msgset</description> + </snippet> + <snippet id="msgsub"> + <text><![CDATA[<msgsub> + ${1} +</msgsub>]]></text> + <tag>msgsub</tag> + <description>msgsub</description> + </snippet> + <snippet id="msgtext"> + <text><![CDATA[<msgtext> + ${1} +</msgtext>]]></text> + <tag>msgtext</tag> + <description>msgtext</description> + </snippet> + <snippet id="note"> + <text><![CDATA[<note> + ${1} +</note>]]></text> + <tag>note</tag> + <description>note</description> + </snippet> + <snippet id="objectinfo"> + <text><![CDATA[<objectinfo> + ${1} +</objectinfo>]]></text> + <tag>objectinfo</tag> + <description>objectinfo</description> + </snippet> + <snippet id="olink"> + <text><![CDATA[<olink targetdocent="${1}">${2}</olink>]]></text> + <tag>olink</tag> + <description>olink</description> + </snippet> + <snippet id="ooclass"> + <text><![CDATA[<ooclass>${1}</ooclass>]]></text> + <tag>ooclass</tag> + <description>ooclass</description> + </snippet> + <snippet id="ooexception"> + <text><![CDATA[<ooexception>${1}</ooexception>]]></text> + <tag>ooexception</tag> + <description>ooexception</description> + </snippet> + <snippet id="oointerface"> + <text><![CDATA[<oointerface>${1}</oointerface>]]></text> + <tag>oointerface</tag> + <description>oointerface</description> + </snippet> + <snippet id="option"> + <text><![CDATA[<option>${1}</option>]]></text> + <tag>option</tag> + <description>option</description> + </snippet> + <snippet id="optional"> + <text><![CDATA[<optional>${1}</optional>]]></text> + <tag>optional</tag> + <description>optional</description> + </snippet> + <snippet id="orderedlist"> + <text><![CDATA[<orderedlist> + ${1} +</orderedlist>]]></text> + <tag>orderedlist</tag> + <description>orderedlist</description> + </snippet> + <snippet id="orgdiv"> + <text><![CDATA[<orgdiv>${1}</orgdiv>]]></text> + <tag>orgdiv</tag> + <description>orgdiv</description> + </snippet> + <snippet id="orgname"> + <text><![CDATA[<orgname>${1}</orgname>]]></text> + <tag>orgname</tag> + <description>orgname</description> + </snippet> + <snippet id="otheraddr"> + <text><![CDATA[<otheraddr>${1}</otheraddr>]]></text> + <tag>otheraddr</tag> + <description>otheraddr</description> + </snippet> + <snippet id="othercredit"> + <text><![CDATA[<othercredit> + ${1} +</othercredit>]]></text> + <tag>othercredit</tag> + <description>othercredit</description> + </snippet> + <snippet id="othername"> + <text><![CDATA[<othername>${1}</othername>]]></text> + <tag>othername</tag> + <description>othername</description> + </snippet> + <snippet id="package"> + <text><![CDATA[<package>${1}</package>]]></text> + <tag>package</tag> + <description>package</description> + </snippet> + <snippet id="pagenums"> + <text><![CDATA[<pagenums>${1}</pagenums>]]></text> + <tag>pagenums</tag> + <description>pagenums</description> + </snippet> + <snippet id="para"> + <text><![CDATA[<para> + ${1} +</para>]]></text> + <tag>para</tag> + <description>para</description> + </snippet> + <snippet id="paramdef"> + <text><![CDATA[<paramdef>${1}</paramdef>]]></text> + <tag>paramdef</tag> + <description>paramdef</description> + </snippet> + <snippet id="parameter"> + <text><![CDATA[<parameter class="${1:function}">${2}</parameter>]]></text> + <tag>parameter</tag> + <description>parameter</description> + </snippet> + <snippet id="part"> + <text><![CDATA[<part id="${1}"> + ${2} +</part>]]></text> + <tag>part</tag> + <description>part</description> + </snippet> + <snippet id="partinfo"> + <text><![CDATA[<partinfo> + ${1} +</partinfo>]]></text> + <tag>partinfo</tag> + <description>partinfo</description> + </snippet> + <snippet id="partintro"> + <text><![CDATA[<partintro> + ${1} +</partintro>]]></text> + <tag>partintro</tag> + <description>partintro</description> + </snippet> + <snippet id="personblurb"> + <text><![CDATA[<personblurb> + ${1} +</personblurb>]]></text> + <tag>personblurb</tag> + <description>personblurb</description> + </snippet> + <snippet id="personname"> + <text><![CDATA[<personname>${1}</personname>]]></text> + <tag>personname</tag> + <description>personname</description> + </snippet> + <snippet id="phone"> + <text><![CDATA[<phone>${1}</phone>]]></text> + <tag>phone</tag> + <description>phone</description> + </snippet> + <snippet id="phrase"> + <text><![CDATA[<phrase>${1}</phrase>]]></text> + <tag>phrase</tag> + <description>phrase</description> + </snippet> + <snippet id="pob"> + <text><![CDATA[<pob>${1}</pob>]]></text> + <tag>pob</tag> + <description>pob</description> + </snippet> + <snippet id="postcode"> + <text><![CDATA[<postcode>${1}</postcode>]]></text> + <tag>postcode</tag> + <description>postcode</description> + </snippet> + <snippet id="preface"> + <text><![CDATA[<preface id="${1}"> + ${2} +</preface>]]></text> + <tag>preface</tag> + <description>preface</description> + </snippet> + <snippet id="prefaceinfo"> + <text><![CDATA[<prefaceinfo> + ${1} +</prefaceinfo>]]></text> + <tag>prefaceinfo</tag> + <description>prefaceinfo</description> + </snippet> + <snippet id="primary"> + <text><![CDATA[<primary>${1}</primary>]]></text> + <tag>primary</tag> + <description>primary</description> + </snippet> + <snippet id="primaryie"> + <text><![CDATA[<primaryie>${1}</primaryie>]]></text> + <tag>primaryie</tag> + <description>primaryie</description> + </snippet> + <snippet id="printhistory"> + <text><![CDATA[<printhistory> + ${1} +</printhistory>]]></text> + <tag>printhistory</tag> + <description>printhistory</description> + </snippet> + <snippet id="procedure"> + <text><![CDATA[<procedure> + ${1} +</procedure>]]></text> + <tag>procedure</tag> + <description>procedure</description> + </snippet> + <snippet id="productname"> + <text><![CDATA[<productname class="${1:trade}">${2}</productname>]]></text> + <tag>productname</tag> + <description>productname</description> + </snippet> + <snippet id="productnumber"> + <text><![CDATA[<productnumber>${1}</productnumber>]]></text> + <tag>productnumber</tag> + <description>productnumber</description> + </snippet> + <snippet id="programlisting"> + <text><![CDATA[<programlisting language="${1}">${2}</programlisting>]]></text> + <tag>programlisting</tag> + <description>programlisting</description> + </snippet> + <snippet id="programlistingco"> + <text><![CDATA[<programlistingco> + ${1} +</programlistingco>]]></text> + <tag>programlistingco</tag> + <description>programlistingco</description> + </snippet> + <snippet id="prompt"> + <text><![CDATA[<prompt>${1}</prompt>]]></text> + <tag>prompt</tag> + <description>prompt</description> + </snippet> + <snippet id="property"> + <text><![CDATA[<property>${1}</property>]]></text> + <tag>property</tag> + <description>property</description> + </snippet> + <snippet id="pubdate"> + <text><![CDATA[<pubdate>${1}</pubdate>]]></text> + <tag>pubdate</tag> + <description>pubdate</description> + </snippet> + <snippet id="publisher"> + <text><![CDATA[<publisher> + ${1} +</publisher>]]></text> + <tag>publisher</tag> + <description>publisher</description> + </snippet> + <snippet id="publishername"> + <text><![CDATA[<publishername>${1}</publishername>]]></text> + <tag>publishername</tag> + <description>publishername</description> + </snippet> + <snippet id="pubsnumber"> + <text><![CDATA[<pubsnumber>${1}</pubsnumber>]]></text> + <tag>pubsnumber</tag> + <description>pubsnumber</description> + </snippet> + <snippet id="qandadiv"> + <text><![CDATA[<qandadiv> + ${1} +</qandadiv>]]></text> + <tag>qandadiv</tag> + <description>qandadiv</description> + </snippet> + <snippet id="qandaentry"> + <text><![CDATA[<qandaentry> + ${1} +</qandaentry>]]></text> + <tag>qandaentry</tag> + <description>qandaentry</description> + </snippet> + <snippet id="qandaset"> + <text><![CDATA[<qandaset> + ${1} +</qandaset>]]></text> + <tag>qandaset</tag> + <description>qandaset</description> + </snippet> + <snippet id="question"> + <text><![CDATA[<question> + ${1} +</question>]]></text> + <tag>question</tag> + <description>question</description> + </snippet> + <snippet id="quote"> + <text><![CDATA[<quote>${1}</quote>]]></text> + <tag>quote</tag> + <description>quote</description> + </snippet> + <snippet id="refclass"> + <text><![CDATA[<refclass>${1}</refclass>]]></text> + <tag>refclass</tag> + <description>refclass</description> + </snippet> + <snippet id="refdescriptor"> + <text><![CDATA[<refdescriptor>${1}</refdescriptor>]]></text> + <tag>refdescriptor</tag> + <description>refdescriptor</description> + </snippet> + <snippet id="refentry"> + <text><![CDATA[<refentry> + ${1} +</refentry>]]></text> + <tag>refentry</tag> + <description>refentry</description> + </snippet> + <snippet id="refentryinfo"> + <text><![CDATA[<refentryinfo> + ${1} +</refentryinfo>]]></text> + <tag>refentryinfo</tag> + <description>refentryinfo</description> + </snippet> + <snippet id="refentrytitle"> + <text><![CDATA[<refentrytitle>${1}</refentrytitle>]]></text> + <tag>refentrytitle</tag> + <description>refentrytitle</description> + </snippet> + <snippet id="reference"> + <text><![CDATA[<reference> + ${1} +</reference>]]></text> + <tag>reference</tag> + <description>reference</description> + </snippet> + <snippet id="referenceinfo"> + <text><![CDATA[<referenceinfo> + ${1} +</referenceinfo>]]></text> + <tag>referenceinfo</tag> + <description>referenceinfo</description> + </snippet> + <snippet id="refmeta"> + <text><![CDATA[<refmeta> + ${1} +</refmeta>]]></text> + <tag>refmeta</tag> + <description>refmeta</description> + </snippet> + <snippet id="refmiscinfo"> + <text><![CDATA[<refmiscinfo> + ${1} +</refmiscinfo>]]></text> + <tag>refmiscinfo</tag> + <description>refmiscinfo</description> + </snippet> + <snippet id="refname"> + <text><![CDATA[<refname>${1}</refname>]]></text> + <tag>refname</tag> + <description>refname</description> + </snippet> + <snippet id="refnamediv"> + <text><![CDATA[<refnamediv> + ${1} +</refnamediv>]]></text> + <tag>refnamediv</tag> + <description>refnamediv</description> + </snippet> + <snippet id="refpurpose"> + <text><![CDATA[<refpurpose>${1}</refpurpose>]]></text> + <tag>refpurpose</tag> + <description>refpurpose</description> + </snippet> + <snippet id="refsect1"> + <text><![CDATA[<refsect1> + ${1} +</refsect1>]]></text> + <tag>refsect1</tag> + <description>refsect1</description> + </snippet> + <snippet id="refsect1info"> + <text><![CDATA[<refsect1info> + ${1} +</refsect1info>]]></text> + <tag>refsect1info</tag> + <description>refsect1info</description> + </snippet> + <snippet id="refsect2"> + <text><![CDATA[<refsect2> + ${1} +</refsect2>]]></text> + <tag>refsect2</tag> + <description>refsect2</description> + </snippet> + <snippet id="refsect2info"> + <text><![CDATA[<refsect2info> + ${1} +</refsect2info>]]></text> + <tag>refsect2info</tag> + <description>refsect2info</description> + </snippet> + <snippet id="refsect3"> + <text><![CDATA[<refsect3> + ${1} +</refsect3>]]></text> + <tag>refsect3</tag> + <description>refsect3</description> + </snippet> + <snippet id="refsect3info"> + <text><![CDATA[<refsect3info> + ${1} +</refsect3info>]]></text> + <tag>refsect3info</tag> + <description>refsect3info</description> + </snippet> + <snippet id="refsection"> + <text><![CDATA[<refsection> + ${1} +</refsection>]]></text> + <tag>refsection</tag> + <description>refsection</description> + </snippet> + <snippet id="refsectioninfo"> + <text><![CDATA[<refsectioninfo> + ${1} +</refsectioninfo>]]></text> + <tag>refsectioninfo</tag> + <description>refsectioninfo</description> + </snippet> + <snippet id="refsynopsisdiv"> + <text><![CDATA[<refsynopsisdiv> + ${1} +</refsynopsisdiv>]]></text> + <tag>refsynopsisdiv</tag> + <description>refsynopsisdiv</description> + </snippet> + <snippet id="refsynopsisdivinfo"> + <text><![CDATA[<refsynopsisdivinfo> + ${1} +</refsynopsisdivinfo>]]></text> + <tag>refsynopsisdivinfo</tag> + <description>refsynopsisdivinfo</description> + </snippet> + <snippet id="releaseinfo"> + <text><![CDATA[<releaseinfo>${1}</releaseinfo>]]></text> + <tag>releaseinfo</tag> + <description>releaseinfo</description> + </snippet> + <snippet id="remark"> + <text><![CDATA[<remark>${1}</remark>]]></text> + <tag>remark</tag> + <description>remark</description> + </snippet> + <snippet id="replaceable"> + <text><![CDATA[<replaceable>${1}</replaceable>]]></text> + <tag>replaceable</tag> + <description>replaceable</description> + </snippet> + <snippet id="returnvalue"> + <text><![CDATA[<returnvalue>${1}</returnvalue>]]></text> + <tag>returnvalue</tag> + <description>returnvalue</description> + </snippet> + <snippet id="revdescription"> + <text><![CDATA[<revdescription> + ${1} +</revdescription>]]></text> + <tag>revdescription</tag> + <description>revdescription</description> + </snippet> + <snippet id="revhistory"> + <text><![CDATA[<revhistory> + ${1} +</revhistory>]]></text> + <tag>revhistory</tag> + <description>revhistory</description> + </snippet> + <snippet id="revision"> + <text><![CDATA[<revision> + ${1} +</revision>]]></text> + <tag>revision</tag> + <description>revision</description> + </snippet> + <snippet id="revnumber"> + <text><![CDATA[<revnumber>${1}</revnumber>]]></text> + <tag>revnumber</tag> + <description>revnumber</description> + </snippet> + <snippet id="revremark"> + <text><![CDATA[<revremark>${1}</revremark>]]></text> + <tag>revremark</tag> + <description>revremark</description> + </snippet> + <snippet id="row"> + <text><![CDATA[<row> + ${1} +</row>]]></text> + <tag>row</tag> + <description>row</description> + </snippet> + <snippet id="sbr"> + <text><![CDATA[<sbr />]]></text> + <tag>sbr</tag> + <description>sbr</description> + </snippet> + <snippet id="screen"> + <text><![CDATA[<screen>${1}</screen>]]></text> + <tag>screen</tag> + <description>screen</description> + </snippet> + <snippet id="screenco"> + <text><![CDATA[<screenco> + ${1} +</screenco>]]></text> + <tag>screenco</tag> + <description>screenco</description> + </snippet> + <snippet id="screeninfo"> + <text><![CDATA[<screeninfo>${1}</screeninfo>]]></text> + <tag>screeninfo</tag> + <description>screeninfo</description> + </snippet> + <snippet id="screenshot"> + <text><![CDATA[<screenshot> + ${1} +</screenshot>]]></text> + <tag>screenshot</tag> + <description>screenshot</description> + </snippet> + <snippet id="secondary"> + <text><![CDATA[<secondary>${1}</secondary>]]></text> + <tag>secondary</tag> + <description>secondary</description> + </snippet> + <snippet id="secondaryie"> + <text><![CDATA[<secondaryie>${1}</secondaryie>]]></text> + <tag>secondaryie</tag> + <description>secondaryie</description> + </snippet> + <snippet id="sect1"> + <text><![CDATA[<sect1 id="${1}"> + ${2} +</sect1>]]></text> + <tag>sect1</tag> + <description>sect1</description> + </snippet> + <snippet id="sect1info"> + <text><![CDATA[<sect1info> + ${1} +</sect1info>]]></text> + <tag>sect1info</tag> + <description>sect1info</description> + </snippet> + <snippet id="sect2"> + <text><![CDATA[<sect2 id="${1}"> + ${2} +</sect2>]]></text> + <tag>sect2</tag> + <description>sect2</description> + </snippet> + <snippet id="sect2info"> + <text><![CDATA[<sect2info> + ${1} +</sect2info>]]></text> + <tag>sect2info</tag> + <description>sect2info</description> + </snippet> + <snippet id="sect3"> + <text><![CDATA[<sect3 id="${1}"> + ${2} +</sect3>]]></text> + <tag>sect3</tag> + <description>sect3</description> + </snippet> + <snippet id="sect3info"> + <text><![CDATA[<sect3info> + ${1} +</sect3info>]]></text> + <tag>sect3info</tag> + <description>sect3info</description> + </snippet> + <snippet id="sect4"> + <text><![CDATA[<sect4 id="${1}"> + ${2} +</sect4>]]></text> + <tag>sect4</tag> + <description>sect4</description> + </snippet> + <snippet id="sect4info"> + <text><![CDATA[<sect4info> + ${1} +</sect4info>]]></text> + <tag>sect4info</tag> + <description>sect4info</description> + </snippet> + <snippet id="sect5"> + <text><![CDATA[<sect5 id="${1}"> + ${2} +</sect5>]]></text> + <tag>sect5</tag> + <description>sect5</description> + </snippet> + <snippet id="sect5info"> + <text><![CDATA[<sect5info> + ${1} +</sect5info>]]></text> + <tag>sect5info</tag> + <description>sect5info</description> + </snippet> + <snippet id="section"> + <text><![CDATA[<section id="${1}"> + ${2} +</section>]]></text> + <tag>section</tag> + <description>section</description> + </snippet> + <snippet id="sectioninfo"> + <text><![CDATA[<sectioninfo> + ${1} +</sectioninfo>]]></text> + <tag>sectioninfo</tag> + <description>sectioninfo</description> + </snippet> + <snippet id="see"> + <text><![CDATA[<see>${1}</see>]]></text> + <tag>see</tag> + <description>see</description> + </snippet> + <snippet id="seealso"> + <text><![CDATA[<seealso>${1}</seealso>]]></text> + <tag>seealso</tag> + <description>seealso</description> + </snippet> + <snippet id="seealsoie"> + <text><![CDATA[<seealsoie>${1}</seealsoie>]]></text> + <tag>seealsoie</tag> + <description>seealsoie</description> + </snippet> + <snippet id="seeie"> + <text><![CDATA[<seeie>${1}</seeie>]]></text> + <tag>seeie</tag> + <description>seeie</description> + </snippet> + <snippet id="seg"> + <text><![CDATA[<seg>${1}</seg>]]></text> + <tag>seg</tag> + <description>seg</description> + </snippet> + <snippet id="seglistitem"> + <text><![CDATA[<seglistitem> + ${1} +</seglistitem>]]></text> + <tag>seglistitem</tag> + <description>seglistitem</description> + </snippet> + <snippet id="segmentedlist"> + <text><![CDATA[<segmentedlist> + ${1} +</segmentedlist>]]></text> + <tag>segmentedlist</tag> + <description>segmentedlist</description> + </snippet> + <snippet id="segtitle"> + <text><![CDATA[<segtitle>${1}</segtitle>]]></text> + <tag>segtitle</tag> + <description>segtitle</description> + </snippet> + <snippet id="seriesvolnums"> + <text><![CDATA[<seriesvolnums>${1}</seriesvolnums>]]></text> + <tag>seriesvolnums</tag> + <description>seriesvolnums</description> + </snippet> + <snippet id="set"> + <text><![CDATA[<set> + ${1} +</set>]]></text> + <tag>set</tag> + <description>set</description> + </snippet> + <snippet id="setindex"> + <text><![CDATA[<setindex> + ${1} +</setindex>]]></text> + <tag>setindex</tag> + <description>setindex</description> + </snippet> + <snippet id="setindexinfo"> + <text><![CDATA[<setindexinfo> + ${1} +</setindexinfo>]]></text> + <tag>setindexinfo</tag> + <description>setindexinfo</description> + </snippet> + <snippet id="setinfo"> + <text><![CDATA[<setinfo> + ${1} +</setinfo>]]></text> + <tag>setinfo</tag> + <description>setinfo</description> + </snippet> + <snippet id="sgmltag"> + <text><![CDATA[<sgmltag>${1}</sgmltag>]]></text> + <tag>sgmltag</tag> + <description>sgmltag</description> + </snippet> + <snippet id="shortaffil"> + <text><![CDATA[<shortaffil>${1}</shortaffil>]]></text> + <tag>shortaffil</tag> + <description>shortaffil</description> + </snippet> + <snippet id="shortcut"> + <text><![CDATA[<shortcut>${1}</shortcut>]]></text> + <tag>shortcut</tag> + <description>shortcut</description> + </snippet> + <snippet id="sidebar"> + <text><![CDATA[<sidebar> + ${1} +</sidebar>]]></text> + <tag>sidebar</tag> + <description>sidebar</description> + </snippet> + <snippet id="sidebarinfo"> + <text><![CDATA[<sidebarinfo> + ${1} +</sidebarinfo>]]></text> + <tag>sidebarinfo</tag> + <description>sidebarinfo</description> + </snippet> + <snippet id="simpara"> + <text><![CDATA[<simpara> + ${1} +</simpara>]]></text> + <tag>simpara</tag> + <description>simpara</description> + </snippet> + <snippet id="simplelist"> + <text><![CDATA[<simplelist> + ${1} +</simplelist>]]></text> + <tag>simplelist</tag> + <description>simplelist</description> + </snippet> + <snippet id="simplemsgentry"> + <text><![CDATA[<simplemsgentry> + ${1} +</simplemsgentry>]]></text> + <tag>simplemsgentry</tag> + <description>simplemsgentry</description> + </snippet> + <snippet id="simplesect"> + <text><![CDATA[<simplesect id="${1}"> + ${2} +</simplesect>]]></text> + <tag>simplesect</tag> + <description>simplesect</description> + </snippet> + <snippet id="spanspec"> + <text><![CDATA[<spanspec spanname="${1}" namest="${2}" nameend="${3}" />]]></text> + <tag>spanspec</tag> + <description>spanspec</description> + </snippet> + <snippet id="state"> + <text><![CDATA[<state>${1}</state>]]></text> + <tag>state</tag> + <description>state</description> + </snippet> + <snippet id="step"> + <text><![CDATA[<step> + ${1} +</step>]]></text> + <tag>step</tag> + <description>step</description> + </snippet> + <snippet id="stepalternatives"> + <text><![CDATA[<stepalternatives> + ${1} +</stepalternatives>]]></text> + <tag>stepalternatives</tag> + <description>stepalternatives</description> + </snippet> + <snippet id="street"> + <text><![CDATA[<street>${1}</street>]]></text> + <tag>street</tag> + <description>street</description> + </snippet> + <snippet id="structfield"> + <text><![CDATA[<structfield>${1}</structfield>]]></text> + <tag>structfield</tag> + <description>structfield</description> + </snippet> + <snippet id="structname"> + <text><![CDATA[<structname>${1}</structname>]]></text> + <tag>structname</tag> + <description>structname</description> + </snippet> + <snippet id="subject"> + <text><![CDATA[<subject> + ${1} +</subject>]]></text> + <tag>subject</tag> + <description>subject</description> + </snippet> + <snippet id="subjectset"> + <text><![CDATA[<subjectset> + ${1} +</subjectset>]]></text> + <tag>subjectset</tag> + <description>subjectset</description> + </snippet> + <snippet id="subjectterm"> + <text><![CDATA[<subjectterm>${1}</subjectterm>]]></text> + <tag>subjectterm</tag> + <description>subjectterm</description> + </snippet> + <snippet id="subscript"> + <text><![CDATA[<subscript>${1}</subscript>]]></text> + <tag>subscript</tag> + <description>subscript</description> + </snippet> + <snippet id="substeps"> + <text><![CDATA[<substeps> + ${1} +</substeps>]]></text> + <tag>substeps</tag> + <description>substeps</description> + </snippet> + <snippet id="subtitle"> + <text><![CDATA[<subtitle>${1}</subtitle>]]></text> + <tag>subtitle</tag> + <description>subtitle</description> + </snippet> + <snippet id="superscript"> + <text><![CDATA[<superscript>${1}</superscript>]]></text> + <tag>superscript</tag> + <description>superscript</description> + </snippet> + <snippet id="surname"> + <text><![CDATA[<surname>${1}</surname>]]></text> + <tag>surname</tag> + <description>surname</description> + </snippet> + <snippet id="symbol"> + <text><![CDATA[<symbol>${1}</symbol>]]></text> + <tag>symbol</tag> + <description>symbol</description> + </snippet> + <snippet id="synopfragment"> + <text><![CDATA[<synopfragment id="${1}"> + ${2} +</synopfragment>]]></text> + <tag>synopfragment</tag> + <description>synopfragment</description> + </snippet> + <snippet id="synopfragmentref"> + <text><![CDATA[<synopfragmentref linkend="${1}"> + ${2} +</synopfragmentref>]]></text> + <tag>synopfragmentref</tag> + <description>synopfragmentref</description> + </snippet> + <snippet id="synopsis"> + <text><![CDATA[<synopsis>${1}</synopsis>]]></text> + <tag>synopsis</tag> + <description>synopsis</description> + </snippet> + <snippet id="systemitem"> + <text><![CDATA[<systemitem>${1}</systemitem>]]></text> + <tag>systemitem</tag> + <description>systemitem</description> + </snippet> + <snippet id="table"> + <text><![CDATA[<table id="${1}"> + ${2} +</table>]]></text> + <tag>table</tag> + <description>table</description> + </snippet> + <snippet id="task"> + <text><![CDATA[<task> + ${1} +</task>]]></text> + <tag>task</tag> + <description>task</description> + </snippet> + <snippet id="taskprerequisites"> + <text><![CDATA[<taskprerequisites> + ${1} +</taskprerequisites>]]></text> + <tag>taskprerequisites</tag> + <description>taskprerequisites</description> + </snippet> + <snippet id="taskrelated"> + <text><![CDATA[<taskrelated> + ${1} +</taskrelated>]]></text> + <tag>taskrelated</tag> + <description>taskrelated</description> + </snippet> + <snippet id="tasksummary"> + <text><![CDATA[<tasksummary> + ${1} +</tasksummary>]]></text> + <tag>tasksummary</tag> + <description>tasksummary</description> + </snippet> + <snippet id="tbody"> + <text><![CDATA[<tbody> + ${1} +</tbody>]]></text> + <tag>tbody</tag> + <description>tbody</description> + </snippet> + <snippet id="td"> + <text><![CDATA[<td> + ${1} +</td>]]></text> + <tag>td</tag> + <description>td</description> + </snippet> + <snippet id="term"> + <text><![CDATA[<term>${1}</term>]]></text> + <tag>term</tag> + <description>term</description> + </snippet> + <snippet id="termdef"> + <text><![CDATA[<termdef>${1}</termdef>]]></text> + <tag>termdef</tag> + <description>termdef</description> + </snippet> + <snippet id="tertiary"> + <text><![CDATA[<tertiary>${1}</tertiary>]]></text> + <tag>tertiary</tag> + <description>tertiary</description> + </snippet> + <snippet id="tertiaryie"> + <text><![CDATA[<tertiaryie>${1}</tertiaryie>]]></text> + <tag>tertiaryie</tag> + <description>tertiaryie</description> + </snippet> + <snippet id="textdata"> + <text><![CDATA[<textdata fileref="${1}" />]]></text> + <tag>textdata</tag> + <description>textdata</description> + </snippet> + <snippet id="textobject"> + <text><![CDATA[<textobject> + ${1} +</textobject>]]></text> + <tag>textobject</tag> + <description>textobject</description> + </snippet> + <snippet id="tfoot"> + <text><![CDATA[<tfoot> + ${1} +</tfoot>]]></text> + <tag>tfoot</tag> + <description>tfoot</description> + </snippet> + <snippet id="tgroup"> + <text><![CDATA[<tgroup cols="${1}"> + ${2} +</tgroup>]]></text> + <tag>tgroup</tag> + <description>tgroup</description> + </snippet> + <snippet id="th"> + <text><![CDATA[<th> + ${1} +</th>]]></text> + <tag>th</tag> + <description>th</description> + </snippet> + <snippet id="thead"> + <text><![CDATA[<thead> + ${1} +</thead>]]></text> + <tag>thead</tag> + <description>thead</description> + </snippet> + <snippet id="tip"> + <text><![CDATA[<tip> + ${1} +</tip>]]></text> + <tag>tip</tag> + <description>tip</description> + </snippet> + <snippet id="title"> + <text><![CDATA[<title>${1}</title>]]></text> + <tag>title</tag> + <description>title</description> + </snippet> + <snippet id="titleabbrev"> + <text><![CDATA[<titleabbrev>${1}</titleabbrev>]]></text> + <tag>titleabbrev</tag> + <description>titleabbrev</description> + </snippet> + <snippet id="toc"> + <text><![CDATA[<toc> + ${1} +</toc>]]></text> + <tag>toc</tag> + <description>toc</description> + </snippet> + <snippet id="tocback"> + <text><![CDATA[<tocback linkend="${1}">${2}</tocback>]]></text> + <tag>tocback</tag> + <description>tocback</description> + </snippet> + <snippet id="tocchap"> + <text><![CDATA[<tocchap> + ${1} +</tocchap>]]></text> + <tag>tocchap</tag> + <description>tocchap</description> + </snippet> + <snippet id="tocentry"> + <text><![CDATA[<tocentry linkend="${1}">${2}</tocentry>]]></text> + <tag>tocentry</tag> + <description>tocentry</description> + </snippet> + <snippet id="tocfront"> + <text><![CDATA[<tocfront linkend="${1}">${2}</tocfront>]]></text> + <tag>tocfront</tag> + <description>tocfront</description> + </snippet> + <snippet id="toclevel1"> + <text><![CDATA[<toclevel1> + ${1} +</toclevel1>]]></text> + <tag>toclevel1</tag> + <description>toclevel1</description> + </snippet> + <snippet id="toclevel2"> + <text><![CDATA[<toclevel2> + ${1} +</toclevel2>]]></text> + <tag>toclevel2</tag> + <description>toclevel2</description> + </snippet> + <snippet id="toclevel3"> + <text><![CDATA[<toclevel3> + ${1} +</toclevel3>]]></text> + <tag>toclevel3</tag> + <description>toclevel3</description> + </snippet> + <snippet id="toclevel4"> + <text><![CDATA[<toclevel4> + ${1} +</toclevel4>]]></text> + <tag>toclevel4</tag> + <description>toclevel4</description> + </snippet> + <snippet id="toclevel5"> + <text><![CDATA[<toclevel5> + ${1} +</toclevel5>]]></text> + <tag>toclevel5</tag> + <description>toclevel5</description> + </snippet> + <snippet id="tocpart"> + <text><![CDATA[<tocpart> + ${1} +</tocpart>]]></text> + <tag>tocpart</tag> + <description>tocpart</description> + </snippet> + <snippet id="token"> + <text><![CDATA[<token>${1}</token>]]></text> + <tag>token</tag> + <description>token</description> + </snippet> + <snippet id="tr"> + <text><![CDATA[<tr> + ${1} +</tr>]]></text> + <tag>tr</tag> + <description>tr</description> + </snippet> + <snippet id="trademark"> + <text><![CDATA[<trademark class="${1:trade}">${2}</trademark>]]></text> + <tag>trademark</tag> + <description>trademark</description> + </snippet> + <snippet id="type"> + <text><![CDATA[<type>${1}</type>]]></text> + <tag>type</tag> + <description>type</description> + </snippet> + <snippet id="ulink"> + <text><![CDATA[<ulink url="${1}">${2}</ulink>]]></text> + <tag>ulink</tag> + <description>ulink</description> + </snippet> + <snippet id="uri"> + <text><![CDATA[<uri>${1}</uri>]]></text> + <tag>uri</tag> + <description>uri</description> + </snippet> + <snippet id="userinput"> + <text><![CDATA[<userinput>${1}</userinput>]]></text> + <tag>userinput</tag> + <description>userinput</description> + </snippet> + <snippet id="varargs"> + <text><![CDATA[<varargs />]]></text> + <tag>varargs</tag> + <description>varargs</description> + </snippet> + <snippet id="variablelist"> + <text><![CDATA[<variablelist> + ${1} +</variablelist>]]></text> + <tag>variablelist</tag> + <description>variablelist</description> + </snippet> + <snippet id="varlistentry"> + <text><![CDATA[<varlistentry> + ${1} +</varlistentry>]]></text> + <tag>varlistentry</tag> + <description>varlistentry</description> + </snippet> + <snippet id="varname"> + <text><![CDATA[<varname>${1}</varname>]]></text> + <tag>varname</tag> + <description>varname</description> + </snippet> + <snippet id="videodata"> + <text><![CDATA[<videodata fileref="${1}" scalefit="${2:0}" />]]></text> + <tag>videodata</tag> + <description>videodata</description> + </snippet> + <snippet id="videoobject"> + <text><![CDATA[<videoobject> + ${1} +</videoobject>]]></text> + <tag>videoobject</tag> + <description>videoobject</description> + </snippet> + <snippet id="void"> + <text><![CDATA[<void />]]></text> + <tag>void</tag> + <description>void</description> + </snippet> + <snippet id="volumenum"> + <text><![CDATA[<volumenum>${1}</volumenum>]]></text> + <tag>volumenum</tag> + <description>volumenum</description> + </snippet> + <snippet id="warning"> + <text><![CDATA[<warning> + ${1} +</warning>]]></text> + <tag>warning</tag> + <description>warning</description> + </snippet> + <snippet id="wordasword"> + <text><![CDATA[<wordasword>${1}</wordasword>]]></text> + <tag>wordasword</tag> + <description>wordasword</description> + </snippet> + <snippet id="xref"> + <text><![CDATA[<xref linkend="${1}" />]]></text> + <tag>xref</tag> + <description>xref</description> + </snippet> + <snippet id="year"> + <text><![CDATA[<year>${1}</year>]]></text> + <tag>year</tag> + <description>year</description> + </snippet> + <!-- DocBook Element Aliases: --> + <snippet id="bold"> + <text><![CDATA[<emphasis role="bold">${1}</emphasis>]]></text> + <tag>bold</tag> + <description>emphasis, role bold</description> + </snippet> + <snippet id="strong"> + <text><![CDATA[<emphasis role="strong">${1}</emphasis>]]></text> + <tag>strong</tag> + <description>emphasis, role strong</description> + </snippet> + <snippet id="devicefile"> + <text><![CDATA[<filename class="devicefile">${1}</filename>]]></text> + <tag>devicefile</tag> + <description>filename, class devicefile</description> + </snippet> + <snippet id="directory"> + <text><![CDATA[<filename class="directory">${1}</filename>]]></text> + <tag>directory</tag> + <description>filename, class directory</description> + </snippet> + <snippet id="extension"> + <text><![CDATA[<filename class="extension">${1}</filename>]]></text> + <tag>extension</tag> + <description>filename, class extension</description> + </snippet> + <snippet id="headerfile"> + <text><![CDATA[<filename class="headerfile">${1}</filename>]]></text> + <tag>headerfile</tag> + <description>filename, class headerfile</description> + </snippet> + <snippet id="libraryfile"> + <text><![CDATA[<filename class="libraryfile">${1}</filename>]]></text> + <tag>libraryfile</tag> + <description>filename, class libraryfile</description> + </snippet> + <snippet id="partition"> + <text><![CDATA[<filename class="partition">${1}</filename>]]></text> + <tag>partition</tag> + <description>filename, class partition</description> + </snippet> + <snippet id="symlink"> + <text><![CDATA[<filename class="symlink">${1}</filename>]]></text> + <tag>symlink</tag> + <description>filename, class symlink</description> + </snippet> + <snippet id="cartridge"> + <text><![CDATA[<medialabel class="cartridge">${1}</medialabel>]]></text> + <tag>cartridge</tag> + <description>medialabel, class cartridge</description> + </snippet> + <snippet id="cdrom"> + <text><![CDATA[<medialabel class="cdrom">${1}</medialabel>]]></text> + <tag>cdrom</tag> + <description>medialabel, class cdrom</description> + </snippet> + <snippet id="disk"> + <text><![CDATA[<medialabel class="disk">${1}</medialabel>]]></text> + <tag>disk</tag> + <description>medialabel, class disk</description> + </snippet> + <snippet id="tape"> + <text><![CDATA[<medialabel class="tape">${1}</medialabel>]]></text> + <tag>tape</tag> + <description>medialabel, class tape</description> + </snippet> + <snippet id="daemon"> + <text><![CDATA[<systemitem class="daemon">${1}</systemitem>]]></text> + <tag>daemon</tag> + <description>systemitem, class daemon</description> + </snippet> + <snippet id="domainname"> + <text><![CDATA[<systemitem class="domainname">${1}</systemitem>]]></text> + <tag>domainname</tag> + <description>systemitem, class domainname</description> + </snippet> + <snippet id="etheraddress"> + <text><![CDATA[<systemitem class="etheraddress">${1}</systemitem>]]></text> + <tag>etheraddress</tag> + <description>systemitem, class etheraddress</description> + </snippet> + <snippet id="eventhandler"> + <text><![CDATA[<systemitem class="eventhandler">${1}</systemitem>]]></text> + <tag>eventhandler</tag> + <description>systemitem, class eventhandler</description> + </snippet> + <snippet id="event"> + <text><![CDATA[<systemitem class="event">${1}</systemitem>]]></text> + <tag>event</tag> + <description>systemitem, class event</description> + </snippet> + <snippet id="filesystem"> + <text><![CDATA[<systemitem class="filesystem">${1}</systemitem>]]></text> + <tag>filesystem</tag> + <description>systemitem, class filesystem</description> + </snippet> + <snippet id="fqdomainname"> + <text><![CDATA[<systemitem class="fqdomainname">${1}</systemitem>]]></text> + <tag>fqdomainname</tag> + <description>systemitem, class fqdomainname</description> + </snippet> + <snippet id="groupname"> + <text><![CDATA[<systemitem class="groupname">${1}</systemitem>]]></text> + <tag>groupname</tag> + <description>systemitem, class groupname</description> + </snippet> + <snippet id="ipaddress"> + <text><![CDATA[<systemitem class="ipaddress">${1}</systemitem>]]></text> + <tag>ipaddress</tag> + <description>systemitem, class ipaddress</description> + </snippet> + <snippet id="library"> + <text><![CDATA[<systemitem class="library">${1}</systemitem>]]></text> + <tag>library</tag> + <description>systemitem, class library</description> + </snippet> + <snippet id="macro"> + <text><![CDATA[<systemitem class="macro">${1}</systemitem>]]></text> + <tag>macro</tag> + <description>systemitem, class macro</description> + </snippet> + <snippet id="netmask"> + <text><![CDATA[<systemitem class="netmask">${1}</systemitem>]]></text> + <tag>netmask</tag> + <description>systemitem, class netmask</description> + </snippet> + <snippet id="newsgroup"> + <text><![CDATA[<systemitem class="newsgroup">${1}</systemitem>]]></text> + <tag>newsgroup</tag> + <description>systemitem, class newsgroup</description> + </snippet> + <snippet id="osname"> + <text><![CDATA[<systemitem class="osname">${1}</systemitem>]]></text> + <tag>osname</tag> + <description>systemitem, class osname</description> + </snippet> + <snippet id="process"> + <text><![CDATA[<systemitem class="process">${1}</systemitem>]]></text> + <tag>process</tag> + <description>systemitem, class process</description> + </snippet> + <snippet id="protocol"> + <text><![CDATA[<systemitem class="protocol">${1}</systemitem>]]></text> + <tag>protocol</tag> + <description>systemitem, class protocol</description> + </snippet> + <snippet id="resource"> + <text><![CDATA[<systemitem class="resource">${1}</systemitem>]]></text> + <tag>resource</tag> + <description>systemitem, class resource</description> + </snippet> + <snippet id="server"> + <text><![CDATA[<systemitem class="server">${1}</systemitem>]]></text> + <tag>server</tag> + <description>systemitem, class server</description> + </snippet> + <snippet id="service"> + <text><![CDATA[<systemitem class="service">${1}</systemitem>]]></text> + <tag>service</tag> + <description>systemitem, class service</description> + </snippet> + <snippet id="systemname"> + <text><![CDATA[<systemitem class="systemname">${1}</systemitem>]]></text> + <tag>systemname</tag> + <description>systemitem, class systemname</description> + </snippet> + <snippet id="username"> + <text><![CDATA[<systemitem class="username">${1}</systemitem>]]></text> + <tag>username</tag> + <description>systemitem, class username</description> + </snippet> + <!-- XML-related Snippets: --> + <snippet id="include"> + <text><![CDATA[<xi:include href="${1}.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />]]></text> + <tag>include</tag> + <description>xi:include</description> + </snippet> + <snippet id="fallback"> + <text><![CDATA[<xi:fallback xmlns:xi="http://www.w3.org/2001/XInclude"> + ${1} +</xi:fallback>]]></text> + <tag>fallback</tag> + <description>xi:fallback</description> + </snippet> + <snippet id="xml"> + <text><![CDATA[<?xml version='1.0' encoding='utf-8' ?>]]></text> + <tag>xml</tag> + <description>xml</description> + </snippet> + <snippet id="doctype"> + <text><![CDATA[<!DOCTYPE ${1:chapter} PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ +<!ENTITY % BOOK_ENTITIES SYSTEM "${2}.ent"> +%BOOK_ENTITIES; +]>]]></text> + <tag>doctype</tag> + <description>DOCTYPE</description> + </snippet> + <snippet id="entity"> + <text><![CDATA[<!ENTITY ${1} "${2}">]]></text> + <tag>entity</tag> + <description>ENTITY</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/fortran.xml b/plugins/snippets/data/fortran.xml new file mode 100644 index 0000000..205abe6 --- /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 100644 index 0000000..afe3c0b --- /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 100644 index 0000000..54a8e7d --- /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 100644 index 0000000..dd9faea --- /dev/null +++ b/plugins/snippets/data/html.xml @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="HTML"> + <snippet id="doctype"> + <text><![CDATA[<!DOCTYPE html> +]]></text> + <description>HTML5 Doctype</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="doctype-6"> + <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="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:joe@example.com}?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}"> + $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 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:tool tip}" width="$4" height="$5" />$0]]></text> + <tag>img</tag> + <description>Image</description> + </snippet> + <snippet id="input"> + <text><![CDATA[<input type="${1:[button,checkbox,color,date,datetime,datetime-local,email,file,hidden,image,month,number,password,radio,range,reset,search,submit,tel,text,url,week]}" name="${2:some_name}" value="${3:default_value}" placeholder="${4:default_placeholder}" 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}"> +$0]]></text> + <description>Link</description> + <tag>link</tag> + </snippet> + <snippet id="meta"> + <text><![CDATA[<meta name="${1:name}" content="${2:content}" /> +$0]]></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"> + $0 +</script>]]></text> + <description>Script</description> + <tag>script</tag> + </snippet> + <snippet id="scriptsrc"> + <text><![CDATA[<script src="$1" type="text/javascript"></script>]]></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"> + $0 +</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> +$0]]></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> + <li>$2</li> + $3 +</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 100644 index 0000000..2b6ef30 --- /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 100644 index 0000000..f7f11c0 --- /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 100644 index 0000000..b55c5b3 --- /dev/null +++ b/plugins/snippets/data/javascript.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="js"> + <snippet id="fun"> + <text><![CDATA[function ${1:function_name}(${2:first_argument}) { + $0 +}]]></text> + <description>function</description> + <tag>fun</tag> + </snippet> +</snippets> diff --git a/plugins/snippets/data/lang/snippets.lang b/plugins/snippets/data/lang/snippets.lang new file mode 100644 index 0000000..7b755cd --- /dev/null +++ b/plugins/snippets/data/lang/snippets.lang @@ -0,0 +1,160 @@ +<?xml version="1.0"?> +<!-- + + Author: Jesse van den Kieboom <jesse@icecrew.nl> + Copyright (C) 2007-2008 Jesse van den Kieboom <jesse@icecrew.nl> + + 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, see <http://www.gnu.org/licenses/>. + +--> +<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 100644 index 0000000..71672ec --- /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 100644 index 0000000..bb08b43 --- /dev/null +++ b/plugins/snippets/data/mallard.xml @@ -0,0 +1,316 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + Mallard 1.0 snippets according to Mallard 1.0 DRAFT (as of 2013-02-11) + Copyright (C) 2013 Jaromir Hradilek + + Home Page: https://github.com/jhradilek/gedit-snippets + Last Change: 12 February 2013 +--> +<snippets language="mallard"> + <snippet id="app"> + <text><![CDATA[<app>${1}</app>]]></text> + <tag>app</tag> + <description>app</description> + </snippet> + <snippet id="cite"> + <text><![CDATA[<cite>${1}</cite>]]></text> + <tag>cite</tag> + <description>cite</description> + </snippet> + <snippet id="cmd"> + <text><![CDATA[<cmd>${1}</cmd>]]></text> + <tag>cmd</tag> + <description>cmd</description> + </snippet> + <snippet id="code"> + <text><![CDATA[<code>${1}</code>]]></text> + <tag>code</tag> + <description>code</description> + </snippet> + <snippet id="col"> + <text><![CDATA[<col/>${1}]]></text> + <tag>col</tag> + <description>col</description> + </snippet> + <snippet id="colgroup"> + <text><![CDATA[<colgroup>${1}</colgroup>]]></text> + <tag>colgroup</tag> + <description>colgroup</description> + </snippet> + <snippet id="comment"> + <text><![CDATA[<comment> + ${1} +</comment>]]></text> + <tag>comment</tag> + <description>comment</description> + </snippet> + <snippet id="credit"> + <text><![CDATA[<credit type="${1:author}"> + ${2} +</credit>]]></text> + <tag>credit</tag> + <description>credit</description> + </snippet> + <snippet id="desc"> + <text><![CDATA[<desc>${1}</desc>]]></text> + <tag>desc</tag> + <description>desc</description> + </snippet> + <snippet id="em"> + <text><![CDATA[<em>${1}</em>]]></text> + <tag>em</tag> + <description>em</description> + </snippet> + <snippet id="email"> + <text><![CDATA[<email>${1}</email>]]></text> + <tag>email</tag> + <description>email</description> + </snippet> + <snippet id="example"> + <text><![CDATA[<example> + ${1} +</example>]]></text> + <tag>example</tag> + <description>example</description> + </snippet> + <snippet id="figure"> + <text><![CDATA[<figure> + ${1} +</figure>]]></text> + <tag>figure</tag> + <description>figure</description> + </snippet> + <snippet id="file"> + <text><![CDATA[<file>${1}</file>]]></text> + <tag>file</tag> + <description>file</description> + </snippet> + <snippet id="gui"> + <text><![CDATA[<gui>${1}</gui>]]></text> + <tag>gui</tag> + <description>gui</description> + </snippet> + <snippet id="guiseq"> + <text><![CDATA[<guiseq>${1}</guiseq>]]></text> + <tag>guiseq</tag> + <description>guiseq</description> + </snippet> + <snippet id="info"> + <text><![CDATA[<info> + ${1} +</info>]]></text> + <tag>info</tag> + <description>info</description> + </snippet> + <snippet id="input"> + <text><![CDATA[<input>${1}</input>]]></text> + <tag>input</tag> + <description>input</description> + </snippet> + <snippet id="item"> + <text><![CDATA[<item>${1}</item>]]></text> + <tag>item</tag> + <description>item</description> + </snippet> + <snippet id="key"> + <text><![CDATA[<key>${1}</key>]]></text> + <tag>key</tag> + <description>key</description> + </snippet> + <snippet id="keyseq"> + <text><![CDATA[<keyseq type="${1:combo}">${2}</keyseq>]]></text> + <tag>keyseq</tag> + <description>keyseq</description> + </snippet> + <snippet id="license"> + <text><![CDATA[<license href="${1}"> + ${2} +</license>]]></text> + <tag>license</tag> + <description>license</description> + </snippet> + <snippet id="link"> + <text><![CDATA[<link type="${1:guide}" xref="${2:index}" group="${3}"/>]]></text> + <tag>link</tag> + <description>link</description> + </snippet> + <snippet id="links"> + <text><![CDATA[<links type="${1:topic}" groups="${2}"> + ${3} +</links>]]></text> + <tag>links</tag> + <description>links</description> + </snippet> + <snippet id="list"> + <text><![CDATA[<list type="${1:disc}"> + ${2} +</list>]]></text> + <tag>list</tag> + <description>list</description> + </snippet> + <snippet id="listing"> + <text><![CDATA[<listing> + ${1} +</listing>]]></text> + <tag>listing</tag> + <description>listing</description> + </snippet> + <snippet id="media"> + <text><![CDATA[<media type="${1:image}" mime="${2:image/png}" src="${3}"> + ${4} +</media>]]></text> + <tag>media</tag> + <description>media</description> + </snippet> + <snippet id="name"> + <text><![CDATA[<name>${1}</name>]]></text> + <tag>name</tag> + <description>name</description> + </snippet> + <snippet id="note"> + <text><![CDATA[<note style="${1:advanced}"> + ${2} +</note>]]></text> + <tag>note</tag> + <description>note</description> + </snippet> + <snippet id="output"> + <text><![CDATA[<output>${1}</output>]]></text> + <tag>output</tag> + <description>output</description> + </snippet> + <snippet id="p"> + <text><![CDATA[<p>${1}</p>]]></text> + <tag>p</tag> + <description>p</description> + </snippet> + <snippet id="page"> + <text><![CDATA[<page xmlns="http://projectmallard.org/1.0/" type="${1:topic}" id="${2}"> + ${3} +</page>]]></text> + <tag>page</tag> + <description>page</description> + </snippet> + <snippet id="quote"> + <text><![CDATA[<quote> + ${1} +</quote>]]></text> + <tag>quote</tag> + <description>quote</description> + </snippet> + <snippet id="revision"> + <text><![CDATA[<revision version="${1:0.1}" date="$(2:date +%Y-%m-%d)" status="${3:stub}"/>]]></text> + <tag>revision</tag> + <description>revision</description> + </snippet> + <snippet id="screen"> + <text><![CDATA[<screen>${1}</screen>]]></text> + <tag>screen</tag> + <description>screen</description> + </snippet> + <snippet id="section"> + <text><![CDATA[<section id="${1}"> + ${2} +</section>]]></text> + <tag>section</tag> + <description>section</description> + </snippet> + <snippet id="span"> + <text><![CDATA[<span>${1}</span>]]></text> + <tag>span</tag> + <description>span</description> + </snippet> + <snippet id="steps"> + <text><![CDATA[<steps> + ${1} +</steps>]]></text> + <tag>steps</tag> + <description>steps</description> + </snippet> + <snippet id="subtitle"> + <text><![CDATA[<subtitle>${1}</subtitle>]]></text> + <tag>subtitle</tag> + <description>subtitle</description> + </snippet> + <snippet id="synopsis"> + <text><![CDATA[<synopsis> + ${1} +</synopsis>]]></text> + <tag>synopsis</tag> + <description>synopsis</description> + </snippet> + <snippet id="sys"> + <text><![CDATA[<sys>${1}</sys>]]></text> + <tag>sys</tag> + <description>sys</description> + </snippet> + <snippet id="table"> + <text><![CDATA[<table frame="${1:all}" rules="${2:all}" shade="${3:none}"> + ${4} +</table>]]></text> + <tag>table</tag> + <description>table</description> + </snippet> + <snippet id="tbody"> + <text><![CDATA[<tbody> + ${1} +</tbody>]]></text> + <tag>tbody</tag> + <description>tbody</description> + </snippet> + <snippet id="td"> + <text><![CDATA[<td>${1}</td>]]></text> + <tag>td</tag> + <description>td</description> + </snippet> + <snippet id="terms"> + <text><![CDATA[<terms> + ${1} +</terms>]]></text> + <tag>terms</tag> + <description>terms</description> + </snippet> + <snippet id="tfoot"> + <text><![CDATA[<tfoot> + ${1} +</tfoot>]]></text> + <tag>tfoot</tag> + <description>tfoot</description> + </snippet> + <snippet id="thead"> + <text><![CDATA[<thead> + ${1} +</thead>]]></text> + <tag>thead</tag> + <description>thead</description> + </snippet> + <snippet id="title"> + <text><![CDATA[<title>${1}</title>]]></text> + <tag>title</tag> + <description>title</description> + </snippet> + <snippet id="tr"> + <text><![CDATA[<tr> + ${1} +</tr>]]></text> + <tag>tr</tag> + <description>tr</description> + </snippet> + <snippet id="tree"> + <text><![CDATA[<tree> + ${1} +</tree>]]></text> + <tag>tree</tag> + <description>tree</description> + </snippet> + <snippet id="var"> + <text><![CDATA[<var>${1}</var>]]></text> + <tag>var</tag> + <description>var</description> + </snippet> + <snippet id="years"> + <text><![CDATA[<years>$(1:date +%Y)</years>]]></text> + <tag>years</tag> + <description>years</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/markdown.xml b/plugins/snippets/data/markdown.xml new file mode 100644 index 0000000..e49209b --- /dev/null +++ b/plugins/snippets/data/markdown.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="Markdown"> + <snippet id="atx-header"> + <text><![CDATA[${1:#} ${2:Header} $1 + +]]></text> + <tag>h</tag> + <description>Header (atx-style)</description> + </snippet> + + <snippet id="setext-header-1"> + <text><![CDATA[${1:Header} +=============== + +]]></text> + <tag>Hs</tag> + <description>Header 1 (setext-style)</description> + </snippet> + + <snippet id="setext-header-2"> + <text><![CDATA[${1:Header} +--------------- + +]]></text> + <tag>hs</tag> + <description>Header 2 (setext-style)</description> + </snippet> + + <snippet id="horizontal-rule"> + <text><![CDATA[*************** + +$0]]></text> + <tag>hr</tag> + <description>Horizontal Rule</description> + </snippet> + + <snippet id="unordered-list"> + <text><![CDATA[* $1 +* $2 +* $3 +* $4 + +]]></text> + <tag>ul</tag> + <description>Unordered List</description> + </snippet> + + <snippet id="ordered-list"> + <text><![CDATA[1. $1 +2. $2 +3. $3 +4. $4 + +]]></text> + <tag>ol</tag> + <description>Ordered List</description> + </snippet> + + <snippet id="code-span"> + <text><![CDATA[\`${1:$GEDIT_SELECTED_TEXT}\`]]></text> + <tag>code</tag> + <description>Wrap Selection as Code Span</description> + <accelerator><![CDATA[<Control><Alt>c]]></accelerator> + </snippet> + + <snippet id="inline-link"> + <text><![CDATA[[${1:$GEDIT_SELECTED_TEXT}](${2:URL})]]></text> + <tag>a</tag> + <description>Wrap Selection as Inline Link</description> + <accelerator><![CDATA[<Control><Alt>a]]></accelerator> + </snippet> + + <snippet id="reference-link"> + <text><![CDATA[[${1:$GEDIT_SELECTED_TEXT}][${2:link label}]]]></text> + <tag>aref</tag> + <description>Wrap Selection as Reference Link</description> + <accelerator><![CDATA[<Control><Alt>r]]></accelerator> + </snippet> + + <snippet id="link-definition"> + <text><![CDATA[[${1:link label}]: ${2:URL} +]]></text> + <tag>adef</tag> + <description>Link Definition</description> + </snippet> + + <snippet id="inline-image"> + <text><![CDATA[![${1:alt text}](${2:URL})]]></text> + <tag>img</tag> + <description>Inline Image</description> + </snippet> + + <snippet id="reference-image"> + <text><![CDATA[![${1:alt text}][${2:image label}]]]></text> + <tag>iref</tag> + <description>Reference Image</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/perl.xml b/plugins/snippets/data/perl.xml new file mode 100644 index 0000000..add148f --- /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 100644 index 0000000..89b27e7 --- /dev/null +++ b/plugins/snippets/data/php.xml @@ -0,0 +1,192 @@ +<?xml version="1.0" encoding="UTF-8"?> +<snippets language="PHP"> + <snippet id="class"> + <text><![CDATA[class ${1:ClassName}${2: extends AnotherClass} +{ + function __construct(${3:argument}) + { + $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 { + $0 +} while (${1:$a <= 10});]]></text> + <description>do .. while ..</description> + <tag>do</tag> + </snippet> + <snippet id="elseif"> + <text><![CDATA[elseif (${1:condition}) { + $0 +}]]></text> + <description>elseif ..</description> + <tag>elseif</tag> + </snippet> + <snippet id="else"> + <text><![CDATA[else { + $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}) { + $0 +}]]></text> + <description>foreach ..</description> + <tag>foreach</tag> + </snippet> + <snippet id="for"> + <text><![CDATA[for ($${1:i} = ${2:0}; $${1:i} < $3; $${1:i}++) { + $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} +} else { + ${3} +} +$0]]></text> + <description>if .. else ..</description> + <tag>ifelse</tag> + </snippet> + <snippet id="if"> + <text><![CDATA[if (${1:condition}) { + $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}': + $0 + break;]]></text> + <description>case ..</description> + <tag>case</tag> + </snippet> + <snippet id="switch"> + <text><![CDATA[switch (${1:variable}) { + case '${2:value}': + ${3} + break; + + $0 + + default: + ${4} + break; +}]]></text> + <description>switch ..</description> + <tag>switch</tag> + </snippet> + <snippet id="while"> + <text><![CDATA[while (${1:$a <= 10}) { + $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 100644 index 0000000..a25617b --- /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/rpmspec.xml b/plugins/snippets/data/rpmspec.xml new file mode 100644 index 0000000..eb3396d --- /dev/null +++ b/plugins/snippets/data/rpmspec.xml @@ -0,0 +1,22 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- requires rpm-python package installed --> +<snippets language="rpmspec"> + <snippet id="ch"> + <text><![CDATA[$< +import rpm +import datetime + +spec = rpm.spec($GEDIT_CURRENT_DOCUMENT_PATH) +date = datetime.date.today().strftime("%a %b %d %Y") +headers = spec.packages[0].header +version = headers['Version'] +release = ".".join(headers['Release'].split(".")[:-1]) +packager = headers['Packager'] +newheader = "* %s %s - %s-%s\n- " % (date, packager, version, release) +return newheader +> +]]></text> + <tag>ch</tag> + <description>changelog entry</description> + </snippet> +</snippets> diff --git a/plugins/snippets/data/ruby.xml b/plugins/snippets/data/ruby.xml new file mode 100644 index 0000000..db13e69 --- /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 100644 index 0000000..b8fc0a6 --- /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 100644 index 0000000..ee405e6 --- /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 100644 index 0000000..73a50c0 --- /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 100644 index 0000000..a53d565 --- /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 100644 index 0000000..0ff5cc1 --- /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/meson.build b/plugins/snippets/meson.build new file mode 100644 index 0000000..91401d1 --- /dev/null +++ b/plugins/snippets/meson.build @@ -0,0 +1,23 @@ +subdir('snippets') + +install_subdir( + 'data', + strip_directory : true, + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'snippets', + ) +) + +custom_target( + 'snippets.plugin', + input: 'snippets.plugin.desktop.in', + output: 'snippets.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/snippets/snippets.plugin.desktop.in b/plugins/snippets/snippets.plugin.desktop.in new file mode 100644 index 0000000..129db21 --- /dev/null +++ b/plugins/snippets/snippets.plugin.desktop.in @@ -0,0 +1,9 @@ +[Plugin] +Loader=python3 +Module=snippets +IAge=3 +Name=Snippets +Description=Insert often-used pieces of text in a fast way. +Authors=Jesse van den Kieboom <jesse@icecrew.nl> +Copyright=Copyright © 2005 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/snippets/snippets/__init__.py b/plugins/snippets/snippets/__init__.py new file mode 100644 index 0000000..147aa69 --- /dev/null +++ b/plugins/snippets/snippets/__init__.py @@ -0,0 +1,26 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 gi +gi.require_version('Gedit', '3.0') +gi.require_version('Gtk', '3.0') + +from .appactivatable import AppActivatable +from .windowactivatable import WindowActivatable +from .document import Document + +# ex:ts=4:et: diff --git a/plugins/snippets/snippets/appactivatable.py b/plugins/snippets/snippets/appactivatable.py new file mode 100644 index 0000000..fb56e51 --- /dev/null +++ b/plugins/snippets/snippets/appactivatable.py @@ -0,0 +1,133 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 platform +from gi.repository import Gedit, Gtk, Gdk, GObject, Gio, GLib +from .library import Library +from .shareddata import SharedData + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class AppActivatable(GObject.Object, Gedit.AppActivatable): + __gtype_name__ = "GeditSnippetsAppActivatable" + + app = GObject.Property(type=Gedit.App) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + # Initialize snippets library + library = Library() + + if platform.system() == 'Windows': + snippetsdir = os.path.expanduser('~/gedit/snippets') + else: + snippetsdir = os.path.join(GLib.get_user_config_dir(), 'gedit/snippets') + + library.set_dirs(snippetsdir, self.system_dirs()) + + self.css = Gtk.CssProvider() + self.css.load_from_data(""" +.gedit-snippet-manager-paned { + border-style: solid; + border-color: @borders; +} +.gedit-snippet-manager-paned:dir(ltr) { + border-width: 0 1px 0 0; +} + +.gedit-snippet-manager-paned:dir(rtl) { + border-width: 0 0 0 1px; +} + +.gedit-snippet-manager-view { + border-width: 0 0 1px 0; +} + +.gedit-snippet-manager-treeview { + border-top-width: 0; +} + +.gedit-snippet-manager-treeview:dir(ltr) { + border-left-width: 0; +} + +.gedit-snippet-manager-treeview:dir(rtl) { + border-right-width: 0; +} +""".encode('utf-8')) + Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), + self.css, 600) + + action = Gio.SimpleAction(name="snippets") + action.connect('activate', self.on_action_snippets_activate) + self.app.add_action(action) + + item = Gio.MenuItem.new(_("Manage _Snippets…"), "app.snippets") + self.menu = self.extend_menu("preferences-section") + self.menu.append_menu_item(item) + + def do_deactivate(self): + self.app.remove_action("snippets") + self.menu = None + Gtk.StyleContext.remove_provider_for_screen(Gdk.Screen.get_default(), + self.css) + + def system_dirs(self): + dirs = [] + + if 'XDG_DATA_DIRS' in os.environ: + datadirs = os.environ['XDG_DATA_DIRS'] + elif platform.system() != 'Windows': + datadirs = '/usr/local/share' + os.pathsep + '/usr/share' + else: + datadirs = GLib.win32_get_package_installation_directory_of_module(None) + + for d in datadirs.split(os.pathsep): + d = os.path.join(d, 'gedit', 'plugins', 'snippets') + + if os.path.isdir(d): + dirs.append(d) + + dirs.append(self.plugin_info.get_data_dir()) + return dirs + + def accelerator_activated(self, group, obj, keyval, mod): + activatable = SharedData().lookup_window_activatable(obj) + + ret = False + + if activatable: + ret = activatable.accelerator_activated(keyval, mod) + + return ret + + def create_configure_dialog(self): + SharedData().show_manager(self.app.get_active_window(), self.plugin_info.get_data_dir()) + + def on_action_snippets_activate(self, action, parameter): + self.create_configure_dialog() + +# vi:ex:ts=4:et diff --git a/plugins/snippets/snippets/completion.py b/plugins/snippets/snippets/completion.py new file mode 100644 index 0000000..562b268 --- /dev/null +++ b/plugins/snippets/snippets/completion.py @@ -0,0 +1,187 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from gi.repository import GObject, Gtk, GtkSource, Gedit + +from .library import Library +from .languagemanager import get_language_manager +from .snippet import Snippet + +class Proposal(GObject.Object, GtkSource.CompletionProposal): + __gtype_name__ = "GeditSnippetsProposal" + + def __init__(self, snippet): + super(Proposal, self).__init__() + 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.Object, GtkSource.CompletionProvider): + __gtype_name__ = "GeditSnippetsProvider" + + def __init__(self, name, language_id, handler): + super(Provider, self).__init__() + + 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.IconTheme.get_default() + f, w, h = Gtk.icon_size_lookup(Gtk.IconSize.MENU) + + try: + self.icon = theme.load_icon(Gtk.STOCK_JUSTIFY_LEFT, w, 0) + except: + self.icon = None + + 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): + (valid_context, it) = context.get_iter() + if not valid_context: + return None + + 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 (False, None) + + return (True, 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 = (x for x in proposals if x['tag'].startswith(word)) + + return [Proposal(x) for x in 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.new_with_buffer(Gedit.Document()) + manager = get_language_manager() + + lang = manager.get_language('snippets') + view.get_buffer().set_language(lang) + + sw = Gtk.ScrolledWindow() + sw.add(view) + sw.show_all() + + # Fixed size + sw.set_size_request(300, 200) + + 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(), 0.0, False, 0.5, 0.5) + + def do_get_icon(self): + return self.icon + + def do_get_activation(self): + return GtkSource.CompletionActivation.USER_REQUESTED + +class Defaults(GObject.Object, GtkSource.CompletionProvider): + __gtype_name__ = "GeditSnippetsDefaultsProvider" + + def __init__(self, handler): + GObject.Object.__init__(self) + + self.handler = handler + self.proposals = [] + + def set_defaults(self, defaults): + self.proposals = [] + + for d in defaults: + self.proposals.append(GtkSource.CompletionItem.new(d, d, None, None)) + + 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 GtkSource.CompletionActivation.USER_REQUESTED + +# ex:ts=4:et: diff --git a/plugins/snippets/snippets/document.py b/plugins/snippets/snippets/document.py new file mode 100644 index 0000000..23df280 --- /dev/null +++ b/plugins/snippets/snippets/document.py @@ -0,0 +1,1097 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from gi.repository import Gtk, Gdk, Gio, GLib, Gedit, GObject + +from .library import Library +from .snippet import Snippet +from .placeholder import PlaceholderEnd +from . import completion +from .signals import Signals +from .shareddata import SharedData +from . import helper + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class DynamicSnippet(dict): + def __init__(self, text): + self['text'] = text + self.valid = True + +class Document(GObject.Object, Gedit.ViewActivatable, Signals): + TAB_KEY_VAL = (Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab) + SPACE_KEY_VAL = (Gdk.KEY_space,) + + view = GObject.Property(type=Gedit.View) + + def __init__(self): + GObject.Object.__init__(self) + Signals.__init__(self) + + self.placeholders = [] + self.active_snippets = [] + self.active_placeholder = None + + 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) + + def do_activate(self): + # Always have a reference to the global snippets + Library().ref(None) + + buf = self.view.get_buffer() + + self.connect_signal(self.view, 'key-press-event', self.on_view_key_press) + self.connect_signal(buf, 'notify::language', self.on_notify_language) + self.connect_signal(self.view, 'drag-data-received', self.on_drag_data_received) + + self.connect_signal_after(self.view, 'draw', self.on_draw) + + self.update_language() + + completion = self.view.get_completion() + completion.add_provider(self.provider) + + SharedData().register_controller(self.view, self) + + def do_deactivate(self): + if self.timeout_update_id != 0: + GLib.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.active_placeholder = None + + self.disconnect_signals(self.view) + self.disconnect_signals(self.view.get_buffer()) + + # Remove all active snippets + for snippet in list(self.active_snippets): + self.deactivate_snippet(snippet, True) + + completion = self.view.get_completion() + + if completion: + completion.remove_provider(self.provider) + + if self.language_id != 0: + Library().unref(self.language_id) + + SharedData().unregister_controller(self.view, self) + + # 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 is None and self.language_id is 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 langid != 0: + Library().unref(langid) + + Library().ref(self.language_id) + self.provider.language_id = self.language_id + + SharedData().update_state(self.view.get_toplevel()) + + 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) + + if len(snippets) == 0: + return False + elif len(snippets) == 1: + self.apply_snippet(snippets[0]) + else: + # Do the fancy completion dialog + provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) + provider.set_proposals(snippets) + + cm = self.view.get_completion() + cm.show([provider], cm.create_context(None)) + + 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): + found = placeholder + + # Find the current placeholder + if piter.compare(begin) >= 0 and \ + piter.compare(end) <= 0 and \ + current is 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: + provider = completion.Defaults(self.on_default_activated) + provider.set_defaults(next.defaults) + + cm = self.view.get_completion() + cm.show([provider], cm.create_context(None)) + + 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 string_in_native_doc_encoding(self, buf, s): + enc = buf.get_file().get_encoding() + + if not enc or enc.get_charset() == 'UTF-8': + return s + + try: + cv = GLib.convert(s, -1, enc.get_charset(), 'UTF-8') + return cv[0] + except GLib.GError: + pass + + return s + + def env_get_selected_text(self, buf): + bounds = buf.get_selection_bounds() + + if bounds: + u8 = buf.get_text(bounds[0], bounds[1], False) + + return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)} + else: + return '' + + def env_get_current_word(self, buf): + start, end = helper.buffer_word_boundary(buf) + + u8 = buf.get_text(start, end, False) + + return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)} + + def env_get_current_line(self, buf): + start, end = helper.buffer_line_boundary(buf) + + u8 = buf.get_text(start, end, False) + + return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)} + + def env_get_current_line_number(self, buf): + start, end = helper.buffer_line_boundary(buf) + + return str(start.get_line() + 1) + + def location_uri_for_env(self, location): + if not location: + return {'utf8': '', 'noenc': ''} + + u8 = location.get_parse_name() + + if location.has_uri_scheme('file'): + u8 = "file://" + u8 + + return {'utf8': u8, 'noenc': location.get_uri()} + + def location_name_for_env(self, location): + if location: + try: + info = location.query_info("standard::display-name", 0, None) + display_name = info.get_display_name() + except: + display_name = '' + + return {'utf8': display_name, + 'noenc': location.get_basename()} + else: + return '' + + def location_scheme_for_env(self, location): + if location: + return location.get_uri_scheme() + else: + return '' + + def location_path_for_env(self, location): + if location and location.has_uri_scheme('file'): + return {'utf8': location.get_parse_name(), + 'noenc': location.get_path()} + else: + return '' + + def location_dir_for_env(self, location): + if location: + parent = location.get_parent() + + if parent and parent.has_uri_scheme('file'): + return {'utf8': parent.get_parse_name(), + 'noenc': parent.get_path()} + + return '' + + def env_add_for_location(self, environ, location, prefix): + parts = {'URI': self.location_uri_for_env, + 'NAME': self.location_name_for_env, + 'SCHEME': self.location_scheme_for_env, + 'PATH': self.location_path_for_env, + 'DIR': self.location_dir_for_env} + + for k in parts: + v = parts[k](location) + key = prefix + '_' + k + + if isinstance(v, dict): + environ['utf8'][key] = v['utf8'] + environ['noenc'][key] = v['noenc'] + else: + environ['utf8'][key] = v + environ['noenc'][key] = str(v) + + return environ + + 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() + + documents_uri = {'utf8': [], 'noenc': []} + + if isinstance(toplevel, Gedit.Window): + for doc in toplevel.get_documents(): + r = self.location_uri_for_env(doc.get_file().get_location()) + + if isinstance(r, dict): + documents_uri['utf8'].append(r['utf8']) + documents_uri['noenc'].append(r['noenc']) + else: + documents_uri['utf8'].append(r) + documents_uri['noenc'].append(str(r)) + + return {'utf8': ' '.join(documents_uri['utf8']), + 'noenc': ' '.join(documents_uri['noenc'])} + + def env_get_documents_path(self, buf): + toplevel = self.view.get_toplevel() + + documents_path = {'utf8': [], 'noenc': []} + + if isinstance(toplevel, Gedit.Window): + for doc in toplevel.get_documents(): + r = self.location_path_for_env(doc.get_file().get_location()) + + if isinstance(r, dict): + documents_path['utf8'].append(r['utf8']) + documents_path['noenc'].append(r['noenc']) + else: + documents_path['utf8'].append(r) + documents_path['noenc'].append(str(r)) + + return {'utf8': ' '.join(documents_path['utf8']), + 'noenc': ' '.join(documents_path['noenc'])} + + def get_environment(self): + buf = self.view.get_buffer() + environ = {'utf8': {}, 'noenc': {}} + + for k in os.environ: + # Get the original environment, as utf-8 + v = os.environ[k] + environ['noenc'][k] = v + environ['utf8'][k] = os.environ[k].encode('utf-8') + + 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_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: + v = variables[var](buf) + + if isinstance(v, dict): + environ['utf8'][var] = v['utf8'] + environ['noenc'][var] = v['noenc'] + else: + environ['utf8'][var] = v + environ['noenc'][var] = str(v) + + self.env_add_for_location(environ, buf.get_file().get_location(), 'GEDIT_CURRENT_DOCUMENT') + + return environ + + 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, environ = {}): + if not snippet.valid: + return False + + # Set environmental variables + env = self.get_environment() + + if environ: + for k in environ['utf8']: + env['utf8'][k] = environ['utf8'][k] + + for k in environ['noenc']: + env['noenc'][k] = environ['noenc'][k] + + buf = self.view.get_buffer() + s = Snippet(snippet, env) + + 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 = helper.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 = helper.buffer_line_boundary(buf) + + # 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) + + if len(self.active_snippets) > 0: + self.block_signal(buf, 'cursor-moved') + + buf.begin_user_action() + + # Remove the tag, selection or current word + buf.delete(start, end) + + # Insert the snippet + if len(self.active_snippets) == 0: + self.first_snippet_inserted() + self.block_signal(buf, 'cursor-moved') + + sn = s.insert_into(self, start) + self.active_snippets.append(sn) + + # Put cursor at first tab placeholder + keys = [x for x in sn.placeholders.keys() if x > 0] + + 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]]) + + self.unblock_signal(buf, 'cursor-moved') + + 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 + first = True + + # Move start backward as long as there is a valid character + while start.backward_char(): + c = start.get_char() + + if not helper.is_tab_trigger_character(c): + # Check this for a single special char + if first and helper.is_tab_trigger(c): + break + + # Make sure first char is valid + while not start.equal(end) and \ + not helper.is_first_tab_trigger_character(start.get_char()): + start.forward_char() + + break + + first = False + + if not start.equal(end): + word = buf.get_text(start, end, False) + + if word and word != '': + return (word, start, end) + + return (None, None, None) + + def parse_and_run_snippet(self, data, iter): + if not self.view.get_editable(): + return + + self.apply_snippet(DynamicSnippet(data), iter, iter) + + def run_snippet_trigger(self, trigger, bounds): + if not self.view: + return False + + if not self.view.get_editable(): + return False + + buf = self.view.get_buffer() + + if buf.get_has_selection(): + return False + + snippets = Library().from_tag(trigger, self.language_id) + + if snippets: + if len(snippets) == 1: + return self.apply_snippet(snippets[0], bounds[0], bounds[1]) + else: + # Do the fancy completion dialog + provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) + provider.set_proposals(snippets) + + cm = self.view.get_completion() + cm.show([provider], cm.create_context(None)) + + return True + + return False + + def run_snippet(self): + if not self.view: + return False + + if not self.view.get_editable(): + 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): + 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 + + 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 = GLib.timeout_add(0, + self.update_snippet_contents) + + def on_buffer_changed(self, buf): + for snippet in list(self.active_snippets): + begin = snippet.begin_iter() + end = snippet.end_iter() + + if begin.compare(end) >= 0: + # Begin collapsed on end, just remove it + self.deactivate_snippet(snippet) + + 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 = GLib.timeout_add(0, \ + self.update_snippet_contents) + + def on_buffer_insert_text(self, buf, piter, text, length): + ctx = helper.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_view_key_press(self, view, event): + library = Library() + + state = event.get_state() + + if not self.view.get_editable(): + return False + + if not (state & Gdk.ModifierType.CONTROL_MASK) and \ + not (state & Gdk.ModifierType.MOD1_MASK) and \ + event.keyval in self.TAB_KEY_VAL: + if not state & Gdk.ModifierType.SHIFT_MASK: + return self.run_snippet() + else: + return self.skip_to_previous_placeholder() + elif not library.loaded and \ + library.valid_accelerator(event.keyval, state): + library.ensure_files() + library.ensure(self.language_id) + self.accelerator_activate(event.keyval, \ + 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 apply_uri_snippet(self, snippet, mime, uri): + # Remove file scheme + gfile = Gio.file_new_for_uri(uri) + + environ = {'utf8': {'GEDIT_DROP_DOCUMENT_TYPE': mime.encode('utf-8')}, + 'noenc': {'GEDIT_DROP_DOCUMENT_TYPE': mime}} + + self.env_add_for_location(environ, gfile, 'GEDIT_DROP_DOCUMENT') + + buf = self.view.get_buffer() + location = buf.get_file().get_location() + + relpath = location.get_relative_path(gfile) + + # CHECK: what is the encoding of relpath? + environ['utf8']['GEDIT_DROP_DOCUMENT_RELATIVE_PATH'] = relpath.encode('utf-8') + environ['noenc']['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, environ) + + def in_bounds(self, x, y): + rect = self.view.get_visible_rect() + rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.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 self.view.get_editable(): + return + + uris = helper.drop_get_uris(data) + if not uris: + return + + if not self.in_bounds(x, y): + return + + 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_proposal_activated(self, proposal, piter): + if not self.view.get_editable(): + return False + + 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.TextWindowType.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): + 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.TextWindowType.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.TextWindowType.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): + 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 draw_placeholder(self, ctx, placeholder): + if isinstance(placeholder, PlaceholderEnd): + return + + col = self.view.get_style_context().get_color(Gtk.StateFlags.INSENSITIVE) + col.alpha = 0.5 + Gdk.cairo_set_source_rgba(ctx, col) + + 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) + else: + self.draw_placeholder_rect(ctx, placeholder) + + def on_draw(self, view, ctx): + window = view.get_window(Gtk.TextWindowType.TEXT) + + if not Gtk.cairo_should_draw_window(ctx, window): + return False + + # Draw something + ctx.set_line_width(1.0) + + Gtk.cairo_transform_to_window(ctx, view, window) + + clipped, clip = Gdk.cairo_get_clip_rectangle(ctx) + + if not clipped: + return False + + for placeholder in self.ordered_placeholders: + if not self.placeholder_in_area(placeholder, clip): + continue + + ctx.save() + self.draw_placeholder(ctx, placeholder) + ctx.restore() + + return False + +# ex:ts=4:et: diff --git a/plugins/snippets/snippets/exporter.py b/plugins/snippets/snippets/exporter.py new file mode 100644 index 0000000..8f8249e --- /dev/null +++ b/plugins/snippets/snippets/exporter.py @@ -0,0 +1,122 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 xml.etree.ElementTree as et +from . import helper + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +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 + helper.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=4:et: diff --git a/plugins/snippets/snippets/helper.py b/plugins/snippets/snippets/helper.py new file mode 100644 index 0000000..2fa3b3f --- /dev/null +++ b/plugins/snippets/snippets/helper.py @@ -0,0 +1,204 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from xml.sax import saxutils +import xml.etree.ElementTree as et +import re +import codecs + +from gi.repository import Gtk + +def message_dialog(par, typ, msg): + d = Gtk.MessageDialog(par, Gtk.DialogFlags.MODAL, typ, Gtk.ButtonsType.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') + buf = view.get_buffer() + + buf._snippets_context = 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' + + buf.insert(piter, text[:-1]) + + buf._snippets_context = None + +def get_buffer_context(buf): + if hasattr(buf, "_snippets_context"): + return buf._snippets_context + return None + +def snippets_debug(*s): + return + +def write_xml(node, f, cdata_nodes=()): + assert node is not None + + if not hasattr(f, "write"): + f = codecs.open(f, "wb", encoding="utf-8") + + # 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 et.Comment: + _write_indent(file, "<!-- %s -->\n" % saxutils.escape(node.text), indent) + elif node is et.ProcessingInstruction: + _write_indent(file, "<?%s?>\n" % saxutils.escape(node.text), indent) + else: + items = node.items() + + if items or node.text or len(node): + _write_indent(file, "<" + tag, indent) + + if items: + items.sort() # lexical order + for k, v in items: + file.write(" %s=%s" % (k, saxutils.quoteattr(v))) + 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)) + else: + file.write("\n") + + for n in node: + _write_node(n, file, cdata_nodes, indent + 1) + + if not len(node): + file.write("</" + tag + ">\n") + else: + _write_indent(file, "</" + tag + ">\n", \ + indent) + else: + file.write(" />\n") + + if node.tail and node.tail.strip() != "": + file.write(saxutils.escape(node.tail)) + +def _cdata(text): + return '<![CDATA[' + text.replace(']]>', ']]]]><![CDATA[>') + ']]>' + +def is_tab_trigger(w): + if len(w) == 1 and not (w.isalnum() or w.isspace()): + return True + + if not is_first_tab_trigger_character(w[0]): + return False + + for c in w: + if not is_tab_trigger_character(c): + return False + + return True + +def is_first_tab_trigger_character(c): + return c.isalpha() or c in '_:.' + +def is_tab_trigger_character(c): + return c.isalnum() or c in '_:.' + +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): + uris = [] + if selection.targets_include_uri(): + data = selection.get_data() + lines = re.split('\\s*[\\n\\r]+\\s*', data.strip()) + + for line in lines: + if not line.startswith('#'): + uris.append(line) + + return uris + +# ex:ts=4:et: diff --git a/plugins/snippets/snippets/importer.py b/plugins/snippets/snippets/importer.py new file mode 100644 index 0000000..2718596 --- /dev/null +++ b/plugins/snippets/snippets/importer.py @@ -0,0 +1,134 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 errno +import tempfile +import sys +import shutil + +from .library import Library + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +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 (userdir, 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 + destdir, dest = self.import_destination(filename) + + # Make sure dir exists + try: + os.makedirs(destdir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + # 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=4:et: diff --git a/plugins/snippets/snippets/languagemanager.py b/plugins/snippets/snippets/languagemanager.py new file mode 100644 index 0000000..6f74319 --- /dev/null +++ b/plugins/snippets/snippets/languagemanager.py @@ -0,0 +1,40 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from gi.repository import GtkSource +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 = GtkSource.LanguageManager() + manager.set_search_path(dirs + manager.get_search_path()) + + return manager + +# ex:ts=4:et: diff --git a/plugins/snippets/snippets/library.py b/plugins/snippets/snippets/library.py new file mode 100644 index 0000000..7ce163b --- /dev/null +++ b/plugins/snippets/snippets/library.py @@ -0,0 +1,989 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 re + +from gi.repository import Gdk, Gtk + +import xml.etree.ElementTree as et +from . import helper + +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 is 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 + + helper.snippets_debug('Added ', prop ,' ', value, ' to ', str(self.language)) + + if prop == 'accelerator': + keyval, mod = Gtk.accelerator_parse(value) + self.accel_group.connect(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 + + helper.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): + 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): + helper.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 + # gnomevfs.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.XMLParser(target=builder) + self.insnippet = False + + try: + f = open(self.path, "r", encoding='utf-8') + except IOError: + self.ok = False + return + + while True: + try: + data = f.read(readsize) + except IOError: + self.ok = False + break + + if not data: + break + + try: + parser.feed(data) + except Exception: + self.ok = False + break + + for element in elements: + yield element + + del elements[:] + + f.close() + + def load(self): + if not self.ok: + return + + helper.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: + 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): + helper.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 is 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: + helper.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: + 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 is None or not self.tainted: + return + + path = os.path.dirname(self.path) + + try: + if not os.path.isdir(path): + os.makedirs(path, 0o755) + except OSError: + # TODO: this is bad... + sys.stderr.write("Error in making dirs\n") + + try: + helper.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 = [] + 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 add_accelerator_callback(self, cb): + self._accelerator_activated_cb.append(cb) + + def remove_accelerator_callback(self, cb): + self._accelerator_activated_cb.remove(cb) + + def accelerator_activated(self, group, obj, keyval, mod): + ret = False + + for cb in self._accelerator_activated_cb: + ret = cb(group, obj, keyval, mod) + + if ret: + break + + return ret + + def add_snippet(self, library, element): + container = self.container(library.language) + overrided = self.overrided(library, element) + + if overrided: + overrided.set_library(library) + helper.snippets_debug('Snippet is overriden: ' + overrided['description']) + return None + + snippet = SnippetData(element, library) + + if snippet.id in self.loaded_ids: + helper.snippets_debug('Not added snippet ' + str(library.language) + \ + '::' + snippet['description'] + ' (duplicate)') + return None + + snippet = container.append(snippet) + helper.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 + helper.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): + helper.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: + helper.snippets_debug('Library in wrong format, ignoring') + return False + + helper.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): + helper.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) + + helper.snippets_debug('Ref:', language) + self.container(language).ref() + + def unref(self, language): + language = self.normalize_language(language) + + helper.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(Gdk.KEY_F1, Gdk.KEY_F12 + 1))) + + def valid_tab_trigger(self, trigger): + if not trigger: + return True + + return helper.is_tab_trigger(trigger) + + # 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 [] + + 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=4:et: diff --git a/plugins/snippets/snippets/manager.py b/plugins/snippets/snippets/manager.py new file mode 100644 index 0000000..ca64f18 --- /dev/null +++ b/plugins/snippets/snippets/manager.py @@ -0,0 +1,1143 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from gi.repository import Gtk, Gio, Gdk, GObject + +from .snippet import Snippet +from . import helper +from .library import Library +from .importer import Importer +from .exporter import Exporter +from .languagemanager import get_language_manager + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class Manager(Gtk.Window, Gtk.Buildable): + NAME_COLUMN = 0 + SORT_COLUMN = 1 + LANG_COLUMN = 2 + SNIPPET_COLUMN = 3 + TARGET_URI = 105 + + __gtype_name__ = "GeditSnippetsManager" + + model = None + drag_icons = ('gnome-mime-application-x-tarz', 'gnome-package', 'package') + default_export_name = _('Snippets archive') + '.tar.gz' + dragging = False + + def __init__(self): + self.snippet = None + self._temp_export = None + self._size = (0, 0) + + self.key_press_id = 0 + self.dnd_target_list = Gtk.TargetList.new([]) + self.dnd_target_list.add(Gdk.atom_intern("text/uri-list", True), 0, self.TARGET_URI) + + def get_final_size(self): + return self._size + + 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, 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 = Gio.Application.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() + 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, GObject.Object, object) + self.model.set_sort_column_id(self.SORT_COLUMN, Gtk.SortType.ASCENDING) + + manager = get_language_manager() + + langs = [manager.get_language(x) for x in manager.get_language_ids()] + langs.sort(key=lambda x: x.get_name()) + + piter = self.model.append(None, (_('Global'), '', None, None)) + + # Add dummy node + self.model.append(piter, ('', '', None, 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, None)) + + # Add dummy node + self.model.append(parent, ('', '', None, 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_first() + + 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, data): + snippet = model.get_value(iter, self.SNIPPET_COLUMN) + + if snippet and not snippet.valid: + cell.set_property('icon-name', 'dialog-error') + else: + cell.set_property('icon-name', None) + + cell.set_property('xalign', 1.0) + + def get_cell_data_cb(self, column, cell, model, iter, data): + snippet = model.get_value(iter, self.SNIPPET_COLUMN) + + cell.set_property('editable', snippet != None) + 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_new_for_path(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: + Gtk.drag_set_icon_name(context, 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() + + files = [Gio.file_new_for_uri(u) for u in uris] + + self.import_snippets(files) + + 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.DragAction.COPY: + action = Gdk.DragAction.COPY + else: + for act in context.actions: + if act == Gdk.DragAction.COPY: + action = Gdk.DragAction.COPY + break + + if action == Gdk.DragAction.COPY: + context.drag_status(Gdk.DragAction.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.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY) + tv.drag_source_set_target_list(self.dnd_target_list) + + # Set it as a drag destination for importing snippets + tv.drag_dest_set(Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP, + [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY) + + tv.drag_dest_set_target_list(self.dnd_target_list) + + 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.IconTheme.get_for_screen(tv.get_screen()) + + self.dnd_name = None + for name in self.drag_icons: + icon = theme.lookup_icon(name, Gtk.IconSize.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, None) + + renderer = Gtk.CellRendererPixbuf() + self.column.pack_start(renderer, True) + self.column.set_cell_data_func(renderer, self.get_cell_data_pixbuf_cb, None) + + 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.SelectionMode.MULTIPLE) + selection.connect('changed', self.on_tree_view_selection_changed) + + self.build_dnd() + + def do_parser_finished(self, builder): + self.builder = builder + + handlers_dic = { + 'on_add_snippet_button_clicked': self.on_add_snippet_button_clicked, + 'on_remove_snippet_button_clicked': self.on_remove_snippet_button_clicked, + 'on_import_snippets_button_clicked': self.on_import_snippets_button_clicked, + 'on_export_snippets_button_clicked': self.on_export_snippets_button_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() + + # join treeview and toolbar + context = self['scrolled_window_snippets'].get_style_context() + context.set_junction_sides(Gtk.JunctionSides.BOTTOM) + context = self['toolbar'].get_style_context() + context.set_junction_sides(Gtk.JunctionSides.TOP) + context.set_junction_sides(Gtk.JunctionSides.BOTTOM) + + 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) + + combo = self['combo_drop_targets'] + + entry = combo.get_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.add_uri_targets(self.TARGET_URI) + + def do_configure_event(self, event): + if self.get_realized(): + alloc = self.get_allocation() + self._size = (alloc.width, alloc.height) + + return Gtk.Dialog.do_configure_event(self, event) + + 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) + lang = self.model.get_value(child, self.LANG_COLUMN) + snippet = self.model.get_value(child, self.SNIPPET_COLUMN) + + return (lang or snippet 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): + sn = self.model.get_value(piter, self.SNIPPET_COLUMN) + + if sn == 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_toolbar_buttons(self): + button_add = self['add_snippet_button'] + button_remove = self['remove_snippet_button'] + + button_add.set_sensitive(self.language_path != None) + override, remove, system = self.selected_snippets_state() + + if not (override ^ remove) or system: + button_remove.set_sensitive(False) + button_remove.set_icon_name('list-remove-symbolic') + else: + button_remove.set_sensitive(True) + + if override: + button_remove.set_icon_name('edit-undo-symbolic') + tooltip = _('Revert selected snippet') + else: + button_remove.set_icon_name('list-remove-symbolic') + 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.SNIPPET_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_value(piter, self.NAME_COLUMN, nm) + self.model.set_value(piter, self.SORT_COLUMN, nm) + self.update_toolbar_buttons() + self.entry_tab_trigger_update_valid() + + return piter + + def add_snippet(self, parent, snippet): + piter = self.model.append(parent, ('', '', None, snippet)) + + return self.snippet_changed(piter) + + def snippet_from_iter(self, model, piter): + parent = model.iter_parent(piter) + + if parent: + return model.get_value(piter, self.SNIPPET_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'].get_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'].get_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_toolbar_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.get_indices()[0] == 0: + return None + else: + return self.model.get_value(self.model.get_iter(path), + self.LANG_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.SNIPPET_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 do_destroy(self): + Gtk.Dialog.do_destroy(self) + + if not self.model: + return + + # Remove temporary drag export + if self._temp_export: + shutil.rmtree(os.path.dirname(self._temp_export)) + self._temp_export = None + + self.unref_languages() + self.snippet = None + self.model = None + + 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.SNIPPET_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_value(piter, self.SNIPPET_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_icon_name('dialog-error', Gtk.IconSize.BUTTON) + img.show() + + #self['hbox_tab_trigger'].set_spacing(3) + tip = _('This is not a valid Tab trigger. Triggers can either contain alphanumeric characters (or _, : and .) 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(), False) + + self.snippet['text'] = text + self.snippet_changed() + + def on_add_snippet_button_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, files): + success = True + + for gfile in files: + if not gfile.has_uri_scheme('file'): + continue + + # Remove file:// + filename = gfile.get_path() + + importer = Importer(filename) + error = importer.run() + + if error: + message = _('The following error occurred while importing: %s') % error + success = False + helper.message_dialog(self.get_toplevel(), Gtk.MessageType.ERROR, message) + + self.build_model(True) + + if success: + message = _('Import successfully completed') + helper.message_dialog(self.get_toplevel(), Gtk.MessageType.INFO, message) + + def on_import_response(self, dialog, response): + if response == Gtk.ResponseType.CANCEL or response == Gtk.ResponseType.CLOSE: + dialog.destroy() + return + + f = dialog.get_files() + dialog.destroy() + + self.import_snippets(f) + + def on_import_snippets_button_clicked(self, button): + dlg = Gtk.FileChooserDialog(parent=self.get_toplevel(), title=_("Import snippets"), + action=Gtk.FileChooserAction.OPEN, + buttons=(_("_Cancel"), Gtk.ResponseType.CANCEL, + _("_Open"), Gtk.ResponseType.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.MessageType.ERROR + retval = False + else: + message = _('Export successfully completed') + msgtype = Gtk.MessageType.INFO + retval = True + + if show_dialogs: + helper.message_dialog(self.get_toplevel(), msgtype, message) + + return retval + + def on_export_response(self, dialog, response): + filename = dialog.get_filename() + snippets = dialog._export_snippets + + dialog.destroy() + + if response != Gtk.ResponseType.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.DialogFlags.MODAL, + type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.YES_NO, + message_format=message) + mes.set_property('use-markup', True) + resp = mes.run() + mes.destroy() + + if resp == Gtk.ResponseType.NO: + export_snippets = usersnippets + elif resp != Gtk.ResponseType.YES: + return False + + if len(export_snippets) == 0 and show_dialogs: + message = _('There are no snippets selected to be exported') + helper.message_dialog(self.get_toplevel(), Gtk.MessageType.QUESTION, message) + return False + + if not filename: + dlg = Gtk.FileChooserDialog(parent=self.get_toplevel(), title=_('Export snippets'), + action=Gtk.FileChooserAction.SAVE, + buttons=(_("_Cancel"), Gtk.ResponseType.CANCEL, + _("_Save"), Gtk.ResponseType.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_export_snippets_button_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.get_toplevel(), title=_('Export snippets'), + action=Gtk.FileChooserAction.SAVE, + buttons=(_('_Cancel'), Gtk.ResponseType.CANCEL, + _('_Save'), Gtk.ResponseType.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.DialogFlags.MODAL, + type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.YES_NO, + message_format=message) + mes.set_property('use-markup', True) + resp = mes.run() + mes.destroy() + + if resp == Gtk.ResponseType.NO: + dlg._export_snippets = usersnippets + elif resp != Gtk.ResponseType.YES: + dlg.destroy() + return + + if len(dlg._export_snippets) == 0: + dlg.destroy() + + message = _('There are no snippets selected to be exported') + helper.message_dialog(self.get_toplevel(), Gtk.MessageType.QUESTION, 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) + idx = path.get_indices() + + if self.model.remove(piter): + return piter + elif idx[-1] != 0: + self.select_iter(self.model.get_iter((idx[0], idx[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_remove_snippet_button_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: + # FIXME: this should be fixed in pygobject or something + references.append(Gtk.TreeRowReference.new(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): + 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.get_state()): + # New accelerator + self.set_accelerator(event.keyval, \ + event.get_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'].get_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_remove_snippet_button_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): + uris = helper.drop_get_uris(selection_data) + if not uris: + return + + text = entry.get_text() + + if text: + mimes = [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=4:et: diff --git a/plugins/snippets/snippets/meson.build b/plugins/snippets/snippets/meson.build new file mode 100644 index 0000000..1ca34de --- /dev/null +++ b/plugins/snippets/snippets/meson.build @@ -0,0 +1,39 @@ +snippets_sources = [ + '__init__.py', + 'appactivatable.py', + 'completion.py', + 'document.py', + 'exporter.py', + 'helper.py', + 'importer.py', + 'languagemanager.py', + 'library.py', + 'manager.py', + 'parser.py', + 'placeholder.py', + 'shareddata.py', + 'signals.py', + 'singleton.py', + 'snippet.py', + 'substitutionparser.py', + 'windowactivatable.py', +] + +install_data( + snippets_sources, + install_dir: join_paths( + pkglibdir, + 'plugins', + 'snippets', + ) +) + +install_data( + 'snippets.ui', + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'snippets', + 'ui', + ) +) diff --git a/plugins/snippets/snippets/parser.py b/plugins/snippets/snippets/parser.py new file mode 100644 index 0000000..2b6043d --- /dev/null +++ b/plugins/snippets/snippets/parser.py @@ -0,0 +1,256 @@ +# Gedit snippets plugin +# Copyright (C) 2006-2007 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 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=4:et: diff --git a/plugins/snippets/snippets/placeholder.py b/plugins/snippets/snippets/placeholder.py new file mode 100644 index 0000000..e70a31e --- /dev/null +++ b/plugins/snippets/snippets/placeholder.py @@ -0,0 +1,714 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 sys +import signal +import locale +import subprocess +from gi.repository import GObject, Gtk + +from . import helper +from .substitutionparser import SubstitutionParser + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +# These are places in a view where the cursor can go and do things +class Placeholder: + def __init__(self, view, tabstop, environ, 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.environ = environ + 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 get_environ(self): + return self.environ['utf8'] + + 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): + env = self.get_environ() + + if m.group(1) or not m.group(2) in env: + return '$' + m.group(2) + else: + return self.format_environment(env[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: + helper.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(), False) + 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 + helper.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, environ, begin): + Placeholder.__init__(self, view, -1, environ, 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, environ, begin, default): + Placeholder.__init__(self, view, 0, environ, 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, environ, begin, s): + Placeholder.__init__(self, view, tabstop, environ, 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): + 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] is 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, environ, begin, s): + PlaceholderExpand.__init__(self, view, tabstop, environ, begin, s) + + self.shell = None + self.remove_me = False + + def get_environ(self): + return self.environ['noenc'] + + 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) + + helper.message_dialog(None, Gtk.MessageType.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 is 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 = line.decode('utf-8') + except UnicodeDecodeError: + line = line.decode(locale.getdefaultlocale()[1], errors='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': self.get_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, environ, refs, begin, s, namespace): + PlaceholderExpand.__init__(self, view, tabstop, environ, 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, 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() + + helper.message_dialog(None, Gtk.MessageType.ERROR, \ + _('Execution of the Python command (%s) exceeds the maximum ' \ + 'time, execution aborted.') % self.command) + + return False + except Exception as detail: + self.remove_timeout() + + helper.message_dialog(None, Gtk.MessageType.ERROR, + _('Execution of the Python command (%s) failed: %s') % + (self.command, detail)) + + return False + + if result is 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, environ, begin, inp, pattern, substitution, modifiers): + PlaceholderExpand.__init__(self, view, tabstop, environ, 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): + env = self.get_environ() + + if isinstance(self.inp, int): + return self.mirror_text[self.inp] + elif self.inp in env: + return env[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 as 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=4:et: diff --git a/plugins/snippets/snippets/shareddata.py b/plugins/snippets/snippets/shareddata.py new file mode 100644 index 0000000..be6fd14 --- /dev/null +++ b/plugins/snippets/snippets/shareddata.py @@ -0,0 +1,83 @@ +# Gedit snippets plugin +# Copyright (C) 2011 Jesse van den Kieboom <jessevdk@gnome.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from .singleton import Singleton +import os + +from gi.repository import Gtk + +# To register the GeditSnippetsManager type +from .manager import Manager + +class SharedData(object, metaclass=Singleton): + def __init__(self): + self.dlg = None + self.dlg_default_size = None + self.controller_registry = {} + self.windows = {} + + def register_controller(self, view, controller): + self.controller_registry[view] = controller + + def unregister_controller(self, view, controller): + if self.controller_registry[view] == controller: + del self.controller_registry[view] + + def register_window(self, window): + self.windows[window.window] = window + + def unregister_window(self, window): + if window.window in self.windows: + del self.windows[window.window] + + def update_state(self, window): + if window in self.windows: + self.windows[window].do_update_state() + + def get_active_controller(self, window): + view = window.get_active_view() + + if not view or not view in self.controller_registry: + return None + + return self.controller_registry[view] + + def get_controller(self, view): + if view in self.controller_registry: + return self.controller_registry[view] + else: + return None + + def manager_destroyed(self, dlg): + self.dlg_default_size = dlg.get_final_size() + self.dlg = None + + def show_manager(self, window, datadir): + if not self.dlg: + builder = Gtk.Builder() + builder.add_from_file(os.path.join(datadir, 'ui', 'snippets.ui')) + + self.dlg = builder.get_object('snippets_manager') + self.dlg.connect('destroy', self.manager_destroyed) + + if self.dlg_default_size: + self.dlg.set_default_size(self.dlg_default_size[0], self.dlg_default_size[1]) + + self.dlg.set_transient_for(window) + self.dlg.present() + +# vi:ex:ts=4:et diff --git a/plugins/snippets/snippets/signals.py b/plugins/snippets/snippets/signals.py new file mode 100644 index 0000000..647b616 --- /dev/null +++ b/plugins/snippets/snippets/signals.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# +# signals.py +# +# 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, see <http://www.gnu.org/licenses/>. + +class Signals: + def __init__(self): + self._signals = {} + + def _connect(self, obj, name, handler, connector): + ret = self._signals.setdefault(obj, {}) + + hid = connector(name, handler) + ret.setdefault(name, []).append(hid) + + return hid + + def connect_signal(self, obj, name, handler): + return self._connect(obj, name, handler, obj.connect) + + def connect_signal_after(self, obj, name, handler): + return self._connect(obj, name, handler, obj.connect_after) + + def disconnect_signals(self, obj): + if obj not in self._signals: + return False + + for name in self._signals[obj]: + for hid in self._signals[obj][name]: + obj.disconnect(hid) + + del self._signals[obj] + return True + + def block_signal(self, obj, name): + if obj not in self._signals: + return False + + if name not in self._signals[obj]: + return False + + for hid in self._signals[obj][name]: + obj.handler_block(hid) + + return True + + def unblock_signal(self, obj, name): + if obj not in self._signals: + return False + + if name not in self._signals[obj]: + return False + + for hid in self._signals[obj][name]: + obj.handler_unblock(hid) + + return True + + def disconnect_signal(self, obj, name): + if obj not in self._signals: + return False + + if name not in self._signals[obj]: + return False + + for hid in self._signals[obj][name]: + obj.disconnect(hid) + + del self._signals[obj][name] + + if len(self._signals[obj]) == 0: + del self._signals[obj] + + return True + +# ex:ts=4:et: diff --git a/plugins/snippets/snippets/singleton.py b/plugins/snippets/snippets/singleton.py new file mode 100644 index 0000000..9bc346d --- /dev/null +++ b/plugins/snippets/snippets/singleton.py @@ -0,0 +1,27 @@ +# Gedit snippets plugin +# Copyright (C) 2011 Jesse van den Kieboom <jessevdk@gnome.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +class Singleton(type): + def __init__(cls, name, bases, dict): + super(Singleton, cls).__init__(name, bases, dict) + cls.instance = None + + def __call__(cls, *args, **kw): + if cls.instance is None: + cls.instance = super(Singleton, cls).__call__(*args, **kw) + + return cls.instance diff --git a/plugins/snippets/snippets/snippet.py b/plugins/snippets/snippets/snippet.py new file mode 100644 index 0000000..6b18156 --- /dev/null +++ b/plugins/snippets/snippets/snippet.py @@ -0,0 +1,360 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from gi.repository import Gio, Gtk + +import sys + +from .placeholder import PlaceholderEnd, PlaceholderMirror, Placeholder, PlaceholderShell, PlaceholderEval, PlaceholderRegex, PlaceholderExpand +from .parser import Parser +from . import helper + +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_new_for_path(filename) + + return gfile.get_uri() + + def util_readfile(self, filename): + stream = Gio.file_new_for_path(filename).read() + + if not stream: + return '' + + res = stream.read() + stream.close() + + return res + + def util_filesize(self, filename): + gfile = Gio.file_new_for_path(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" * int(((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, environ = {}): + self.data = data + self.environ = environ + + 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 = helper.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>' + helper.markup_escape(', '.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 = str.join('\n' + self._indent, helper.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): + if data in self.environ['utf8']: + val = self.environ['utf8'][data] + else: + val = '' + + # Get all the current indentation + all_indent = helper.compute_indentation(self._view, self._insert_iter()) + + # Substract initial indentation to get the snippet indentation + indent = all_indent[len(self._indent):] + + # Keep indentation + return str.join('\n' + 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, self.environ, begin, data['default']) + elif tabstop in self.placeholders: + # Mirror placeholder + return PlaceholderMirror(self._view, tabstop, self.environ, begin) + else: + # Default placeholder + return Placeholder(self._view, tabstop, self.environ, data['default'], begin) + + def _create_shell(self, data): + begin = self._insert_iter() + return PlaceholderShell(self._view, data['tabstop'], self.environ, begin, data['contents']) + + def _create_eval(self, data): + begin = self._insert_iter() + return PlaceholderEval(self._view, data['tabstop'], self.environ, data['dependencies'], begin, data['contents'], self._utils.namespace) + + def _create_regex(self, data): + begin = self._insert_iter() + return PlaceholderRegex(self._view, data['tabstop'], self.environ, 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 = helper.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 KeyError: + sys.stderr.write('Token class not supported: %s (%s)\n' % token.klass) + continue + + if isinstance(val, str): + # 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.environ, 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 = sorted(self.placeholders.keys(), 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=4:et: diff --git a/plugins/snippets/snippets/snippets.ui b/plugins/snippets/snippets/snippets.ui new file mode 100644 index 0000000..e80d633 --- /dev/null +++ b/plugins/snippets/snippets/snippets.ui @@ -0,0 +1,417 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.16.0 --> +<interface> + <requires lib="gtk+" version="3.0"/> + <object class="GtkListStore" id="model1"> + <columns> + <!-- column-name gchararray --> + <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"/> + <object class="GeditSnippetsManager" id="snippets_manager"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Manage Snippets</property> + <property name="default_width">800</property> + <property name="default_height">600</property> + <property name="type_hint">dialog</property> + <child type="titlebar"> + <object class="GtkHeaderBar" id="headerbar"> + <property name="visible">True</property> + <property name="title" translatable="yes">Manage Snippets</property> + <property name="show_close_button">True</property> + </object> + </child> + <child> + <object class="GtkPaned" id="hpaned_paned"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="position">275</property> + <style> + <class name="gedit-snippet-manager-paned"/> + </style> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow" id="scrolled_window_snippets"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <style> + <class name="gedit-snippet-manager-treeview"/> + </style> + <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> + <signal name="key-press-event" handler="on_tree_view_snippets_key_press" swapped="no"/> + <signal name="row-expanded" handler="on_tree_view_snippets_row_expanded" swapped="no"/> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection"/> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToolbar" id="toolbar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="toolbar_style">icons</property> + <property name="icon_size">1</property> + <child> + <object class="GtkToolButton" id="add_snippet_button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="has_tooltip">True</property> + <property name="tooltip_markup" translatable="yes">Create new snippet</property> + <property name="tooltip_text" translatable="yes">Create new snippet</property> + <property name="label" translatable="yes">Add Snippet</property> + <property name="use_underline">True</property> + <property name="icon_name">list-add-symbolic</property> + <signal name="clicked" handler="on_add_snippet_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="remove_snippet_button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="has_tooltip">True</property> + <property name="tooltip_markup" translatable="yes">Delete selected snippet</property> + <property name="tooltip_text" translatable="yes">Delete selected snippet</property> + <property name="label" translatable="yes">Remove Snippet</property> + <property name="use_underline">True</property> + <property name="icon_name">list-remove-symbolic</property> + <signal name="clicked" handler="on_remove_snippet_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="import_snippets_button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="has_tooltip">True</property> + <property name="tooltip_markup" translatable="yes">Import snippets</property> + <property name="tooltip_text" translatable="yes">Import snippets</property> + <property name="label" translatable="yes">Import Snippets</property> + <property name="use_underline">True</property> + <property name="icon_name">document-open-symbolic</property> + <signal name="clicked" handler="on_import_snippets_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="export_snippets_button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="has_tooltip">True</property> + <property name="tooltip_markup" translatable="yes">Export selected snippets</property> + <property name="tooltip_text" translatable="yes">Export selected snippets</property> + <property name="label" translatable="yes">Export Snippets</property> + <property name="use_underline">True</property> + <property name="icon_name">document-save-as-symbolic</property> + <signal name="clicked" handler="on_export_snippets_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <style> + <class name="inline-toolbar"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="resize">False</property> + <property name="shrink">True</property> + </packing> + </child> + <child> + <object class="GtkBox" id="vbox_snippet"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow" id="scrolled_window_snippet"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <style> + <class name="gedit-snippet-manager-view"/> + </style> + <child> + <object class="GeditView" id="source_view_snippet"> + <property name="buffer">source_buffer</property> + <property name="visible">True</property> + <signal handler="on_source_view_snippet_focus_out" name="focus_out_event"/> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">6</property> + <property name="margin_end">6</property> + <property name="margin_top">6</property> + <property name="margin_bottom">6</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Activation</property> + <property name="use_markup">True</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="hbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label"> </property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">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 name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" comments=""tab" here means the tab key, not the notebook tab!">_Tab trigger:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_tab_trigger</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="hbox_tab_trigger"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkEntry" id="entry_tab_trigger"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Single word the snippet is activated with after pressing Tab</property> + <signal name="changed" handler="on_entry_tab_trigger_changed" swapped="no"/> + <signal name="focus-out-event" handler="on_entry_tab_trigger_focus_out" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkImage" id="image_tab_trigger"> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">3</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label_accelerator"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">S_hortcut key:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_accelerator</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="entry_accelerator"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Shortcut key with which the snippet is activated</property> + <property name="editable">False</property> + <signal name="focus-in-event" handler="on_entry_accelerator_focus_in" swapped="no"/> + <signal name="focus-out-event" handler="on_entry_accelerator_focus_out" swapped="no"/> + <signal name="key-press-event" handler="on_entry_accelerator_key_press" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label_drop_targets"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Drop targets:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_accelerator</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="combo_drop_targets"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="model">model1</property> + <property name="has_entry">True</property> + <child> + <object class="GtkCellRendererText" id="renderer1"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + <child internal-child="entry"> + <object class="GtkEntry" id="combobox-entry"> + <property name="can_focus">True</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="resize">True</property> + <property name="shrink">True</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/plugins/snippets/snippets/substitutionparser.py b/plugins/snippets/snippets/substitutionparser.py new file mode 100644 index 0000000..8469dd3 --- /dev/null +++ b/plugins/snippets/snippets/substitutionparser.py @@ -0,0 +1,203 @@ +# Gedit snippets plugin +# Copyright (C) 2006-2007 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 = '\\\\|\\(\\?|,|\\)' + + REG_GROUP = '(?:(%s)|<(%s|%s)(?:,(%s))?>)' % (REG_ID, REG_ID, REG_NAME, REG_MOD) + + def __init__(self, pattern, groups = {}, modifiers = {}): + self.pattern = pattern + self.groups = groups + + 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 is None or self._peek(tokens) != ',': + raise ParseError + + truepart, tokens = self._parse(self._remains(tokens), ',') + + if truepart is None: + raise ParseError + + falsepart, tokens = self._parse(tokens, ')') + + if falsepart is None: + raise ParseError + + if condition: + return truepart, tokens + else: + return falsepart, tokens + + @staticmethod + def escape_substitution(substitution): + return re.sub('(%s|%s)' % (SubstitutionParser.REG_GROUP, SubstitutionParser.REG_ESCAPE), '\\\\\\1', substitution) + +# ex:ts=4:et: diff --git a/plugins/snippets/snippets/windowactivatable.py b/plugins/snippets/snippets/windowactivatable.py new file mode 100644 index 0000000..6729a69 --- /dev/null +++ b/plugins/snippets/snippets/windowactivatable.py @@ -0,0 +1,186 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 + +from gi.repository import Gtk, Gedit, GObject +from .snippet import Snippet +from .library import Library +from .shareddata import SharedData +from .signals import Signals + + +class Message(Gedit.Message): + view = GObject.Property(type=Gedit.View) + iter = GObject.Property(type=Gtk.TextIter) + +class Activate(Message): + trigger = GObject.Property(type=str) + +class ParseAndActivate(Message): + snippet = GObject.Property(type=str) + +class WindowActivatable(GObject.Object, Gedit.WindowActivatable, Signals): + __gtype_name__ = "GeditSnippetsWindowActivatable" + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + Signals.__init__(self) + + self.current_language_accel_group = None + + def do_activate(self): + self.register_messages() + + library = Library() + library.add_accelerator_callback(self.accelerator_activated) + + self.accel_group = Library().get_accel_group(None) + + if self.accel_group: + self.window.add_accel_group(self.accel_group) + + self.connect_signal(self.window, + 'active-tab-changed', + self.on_active_tab_changed) + + self.do_update_state() + + SharedData().register_window(self) + + def do_deactivate(self): + if self.accel_group: + self.window.remove_accel_group(self.accel_group) + + self.accel_group = None + + self.unregister_messages() + + library = Library() + library.remove_accelerator_callback(self.accelerator_activated) + + self.disconnect_signals(self.window) + + SharedData().unregister_window(self) + + def do_update_state(self): + controller = SharedData().get_active_controller(self.window) + + self.update_language(controller) + + def register_messages(self): + bus = self.window.get_message_bus() + + bus.register(Activate, '/plugins/snippets', 'activate') + bus.register(ParseAndActivate, '/plugins/snippets', 'parse-and-activate') + + self.signal_ids = set() + + sid = bus.connect('/plugins/snippets', 'activate', self.on_message_activate, None) + self.signal_ids.add(sid) + + sid = bus.connect('/plugins/snippets', 'parse-and-activate', self.on_message_parse_and_activate, None) + self.signal_ids.add(sid) + + def unregister_messages(self): + bus = self.window.get_message_bus() + for sid in self.signal_ids: + bus.disconnect(sid) + self.signal_ids = set() + bus.unregister_all('/plugins/snippets') + + def on_message_activate(self, bus, message, userdata): + view = message.props.view + + if not view: + view = self.window.get_active_view() + + controller = SharedData().get_controller(view) + + if not controller: + return + + iter = message.props.iter + + if not iter: + iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert()) + + controller.run_snippet_trigger(message.props.trigger, (iter, iter)) + + def on_message_parse_and_activate(self, bus, message, userdata): + view = message.props.view + + if not view: + view = self.window.get_active_view() + + controller = SharedData().get_controller(view) + + if not controller: + return + + iter = message.props.iter + + if not iter: + iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert()) + + controller.parse_and_run_snippet(message.props.snippet, iter) + + def find_snippet(self, snippets, tag): + result = [] + + for snippet in snippets: + if Snippet(snippet)['tag'] == tag: + result.append(snippet) + + return result + + def update_language(self, controller): + if not self.window: + return + + if controller: + langid = controller.language_id + else: + langid = None + + if langid != None: + accelgroup = Library().get_accel_group(langid) + else: + accelgroup = None + + if accelgroup != self.current_language_accel_group: + if self.current_language_accel_group: + self.window.remove_accel_group(self.current_language_accel_group) + + if accelgroup: + self.window.add_accel_group(accelgroup) + + self.current_language_accel_group = accelgroup + + def on_active_tab_changed(self, window, tab): + self.update_language(SharedData().get_controller(tab.get_view())) + + def accelerator_activated(self, group, obj, keyval, mod): + if obj == self.window: + controller = SharedData().get_active_controller(self.window) + + if controller: + return controller.accelerator_activate(keyval, mod) + else: + return False + +# ex:ts=4:et: diff --git a/plugins/sort/gedit-sort-plugin.c b/plugins/sort/gedit-sort-plugin.c new file mode 100644 index 0000000..41774fc --- /dev/null +++ b/plugins/sort/gedit-sort-plugin.c @@ -0,0 +1,418 @@ +/* + * gedit-sort-plugin.c + * + * Original author: Carlo Borreo <borreo@softhome.net> + * Ported to Gedit2 by Lee Mallabone <gnome@fonicmonkey.net> + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gedit-sort-plugin.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-app.h> +#include <gedit/gedit-window.h> +#include <gedit/gedit-app-activatable.h> +#include <gedit/gedit-window-activatable.h> + +static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface); +static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface); + +struct _GeditSortPluginPrivate +{ + GeditWindow *window; + + GSimpleAction *action; + GtkWidget *dialog; + GtkWidget *col_num_spinbutton; + GtkWidget *reverse_order_checkbutton; + GtkWidget *case_checkbutton; + GtkWidget *remove_dups_checkbutton; + + GeditApp *app; + GeditMenuExtension *menu_ext; + + GtkTextIter start, end; /* selection */ +}; + +enum +{ + PROP_0, + PROP_WINDOW, + PROP_APP +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditSortPlugin, + gedit_sort_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE, + gedit_app_activatable_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE, + gedit_window_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditSortPlugin)) + +static void +do_sort (GeditSortPlugin *plugin) +{ + GeditSortPluginPrivate *priv; + GeditDocument *doc; + GtkSourceSortFlags sort_flags = 0; + gint starting_column; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + doc = gedit_window_get_active_document (priv->window); + g_return_if_fail (doc != NULL); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->case_checkbutton))) + { + sort_flags |= GTK_SOURCE_SORT_FLAGS_CASE_SENSITIVE; + } + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->reverse_order_checkbutton))) + { + sort_flags |= GTK_SOURCE_SORT_FLAGS_REVERSE_ORDER; + } + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->remove_dups_checkbutton))) + { + sort_flags |= GTK_SOURCE_SORT_FLAGS_REMOVE_DUPLICATES; + } + + starting_column = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (priv->col_num_spinbutton)) - 1; + + gtk_source_buffer_sort_lines (GTK_SOURCE_BUFFER (doc), + &priv->start, + &priv->end, + sort_flags, + starting_column); + + gedit_debug_message (DEBUG_PLUGINS, "Done."); +} + +static void +sort_dialog_response_handler (GtkDialog *dlg, + gint response, + GeditSortPlugin *plugin) +{ + gedit_debug (DEBUG_PLUGINS); + + if (response == GTK_RESPONSE_OK) + { + do_sort (plugin); + } + + gtk_widget_destroy (GTK_WIDGET (dlg)); +} + +/* 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 (GeditSortPlugin *plugin) +{ + GeditSortPluginPrivate *priv; + GeditDocument *doc; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + doc = gedit_window_get_active_document (priv->window); + + if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), + &priv->start, + &priv->end)) + { + /* No selection, get the whole document. */ + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), + &priv->start, + &priv->end); + } +} + +static void +create_sort_dialog (GeditSortPlugin *plugin) +{ + GeditSortPluginPrivate *priv; + GtkBuilder *builder; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + builder = gtk_builder_new (); + gtk_builder_add_from_resource (builder, "/org/gnome/gedit/plugins/sort/ui/gedit-sort-plugin.ui", NULL); + priv->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "sort_dialog")); + priv->reverse_order_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder, "reverse_order_checkbutton")); + priv->col_num_spinbutton = GTK_WIDGET (gtk_builder_get_object (builder, "col_num_spinbutton")); + priv->case_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder, "case_checkbutton")); + priv->remove_dups_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder, "remove_dups_checkbutton")); + g_object_unref (builder); + + gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog), + GTK_RESPONSE_OK); + + g_signal_connect (priv->dialog, + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &priv->dialog); + + g_signal_connect (priv->dialog, + "response", + G_CALLBACK (sort_dialog_response_handler), + plugin); + + get_current_selection (plugin); +} + +static void +sort_cb (GAction *action, + GVariant *parameter, + GeditSortPlugin *plugin) +{ + GeditSortPluginPrivate *priv; + GtkWindowGroup *wg; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + create_sort_dialog (plugin); + + wg = gedit_window_get_group (priv->window); + gtk_window_group_add_window (wg, + GTK_WINDOW (priv->dialog)); + + gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), + GTK_WINDOW (priv->window)); + + gtk_window_set_modal (GTK_WINDOW (priv->dialog), + TRUE); + + gtk_widget_show (GTK_WIDGET (priv->dialog)); +} + +static void +update_ui (GeditSortPlugin *plugin) +{ + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (plugin->priv->window); + + g_simple_action_set_enabled (plugin->priv->action, + (view != NULL) && + gtk_text_view_get_editable (GTK_TEXT_VIEW (view))); +} + +static void +gedit_sort_plugin_app_activate (GeditAppActivatable *activatable) +{ + GeditSortPluginPrivate *priv; + GMenuItem *item; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_SORT_PLUGIN (activatable)->priv; + + priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "tools-section"); + item = g_menu_item_new (_("S_ort…"), "win.sort"); + gedit_menu_extension_append_menu_item (priv->menu_ext, item); + g_object_unref (item); +} + +static void +gedit_sort_plugin_app_deactivate (GeditAppActivatable *activatable) +{ + GeditSortPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_SORT_PLUGIN (activatable)->priv; + + g_clear_object (&priv->menu_ext); +} + +static void +gedit_sort_plugin_window_activate (GeditWindowActivatable *activatable) +{ + GeditSortPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_SORT_PLUGIN (activatable)->priv; + + priv->action = g_simple_action_new ("sort", NULL); + g_signal_connect (priv->action, "activate", + G_CALLBACK (sort_cb), activatable); + g_action_map_add_action (G_ACTION_MAP (priv->window), + G_ACTION (priv->action)); + + update_ui (GEDIT_SORT_PLUGIN (activatable)); +} + +static void +gedit_sort_plugin_window_deactivate (GeditWindowActivatable *activatable) +{ + GeditSortPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_SORT_PLUGIN (activatable)->priv; + g_action_map_remove_action (G_ACTION_MAP (priv->window), "sort"); +} + +static void +gedit_sort_plugin_window_update_state (GeditWindowActivatable *activatable) +{ + gedit_debug (DEBUG_PLUGINS); + + update_ui (GEDIT_SORT_PLUGIN (activatable)); +} + +static void +gedit_sort_plugin_init (GeditSortPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin initializing"); + + plugin->priv = gedit_sort_plugin_get_instance_private (plugin); +} + +static void +gedit_sort_plugin_dispose (GObject *object) +{ + GeditSortPlugin *plugin = GEDIT_SORT_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin disposing"); + + g_clear_object (&plugin->priv->action); + g_clear_object (&plugin->priv->window); + g_clear_object (&plugin->priv->menu_ext); + g_clear_object (&plugin->priv->app); + + G_OBJECT_CLASS (gedit_sort_plugin_parent_class)->dispose (object); +} + + +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_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditSortPlugin *plugin = GEDIT_SORT_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value)); + break; + case PROP_APP: + plugin->priv->app = GEDIT_APP (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_sort_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditSortPlugin *plugin = GEDIT_SORT_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, plugin->priv->window); + break; + case PROP_APP: + g_value_set_object (value, plugin->priv->app); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_sort_plugin_class_init (GeditSortPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_sort_plugin_dispose; + object_class->finalize = gedit_sort_plugin_finalize; + object_class->set_property = gedit_sort_plugin_set_property; + object_class->get_property = gedit_sort_plugin_get_property; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); + g_object_class_override_property (object_class, PROP_APP, "app"); +} + +static void +gedit_sort_plugin_class_finalize (GeditSortPluginClass *klass) +{ +} + +static void +gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface) +{ + iface->activate = gedit_sort_plugin_app_activate; + iface->deactivate = gedit_sort_plugin_app_deactivate; +} + +static void +gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface) +{ + iface->activate = gedit_sort_plugin_window_activate; + iface->deactivate = gedit_sort_plugin_window_deactivate; + iface->update_state = gedit_sort_plugin_window_update_state; +} + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_sort_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_APP_ACTIVATABLE, + GEDIT_TYPE_SORT_PLUGIN); + peas_object_module_register_extension_type (module, + GEDIT_TYPE_WINDOW_ACTIVATABLE, + GEDIT_TYPE_SORT_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/sort/gedit-sort-plugin.h b/plugins/sort/gedit-sort-plugin.h new file mode 100644 index 0000000..e8739e0 --- /dev/null +++ b/plugins/sort/gedit-sort-plugin.h @@ -0,0 +1,59 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_SORT_PLUGIN_H +#define GEDIT_SORT_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> + +G_BEGIN_DECLS + +#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)) + +typedef struct _GeditSortPlugin GeditSortPlugin; +typedef struct _GeditSortPluginPrivate GeditSortPluginPrivate; +typedef struct _GeditSortPluginClass GeditSortPluginClass; + +struct _GeditSortPlugin +{ + PeasExtensionBase parent; + + /*< private >*/ + GeditSortPluginPrivate *priv; +}; + +struct _GeditSortPluginClass +{ + PeasExtensionBaseClass parent_class; +}; + +GType gedit_sort_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_SORT_PLUGIN_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/sort/meson.build b/plugins/sort/meson.build new file mode 100644 index 0000000..7f91440 --- /dev/null +++ b/plugins/sort/meson.build @@ -0,0 +1,34 @@ +libsort_sources = files( + 'gedit-sort-plugin.c', +) + +libsort_deps = [ + libgedit_dep, +] + +subdir('resources') + +libsort_sha = shared_module( + 'sort', + sources: libsort_sources, + include_directories: root_include_dir, + dependencies: libsort_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'sort.plugin', + input: 'sort.plugin.desktop.in', + output: 'sort.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/sort/resources/gedit-sort.gresource.xml b/plugins/sort/resources/gedit-sort.gresource.xml new file mode 100644 index 0000000..3855c8f --- /dev/null +++ b/plugins/sort/resources/gedit-sort.gresource.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/gedit/plugins/sort"> + <file preprocess="xml-stripblanks">ui/gedit-sort-plugin.ui</file> + </gresource> +</gresources> diff --git a/plugins/sort/resources/meson.build b/plugins/sort/resources/meson.build new file mode 100644 index 0000000..7178761 --- /dev/null +++ b/plugins/sort/resources/meson.build @@ -0,0 +1,8 @@ +libsort_res = gnome.compile_resources( + 'gedit-sort-resources', + 'gedit-sort.gresource.xml', +) + +libsort_sources += [ + libsort_res.get(0), +] diff --git a/plugins/sort/resources/ui/gedit-sort-plugin.ui b/plugins/sort/resources/ui/gedit-sort-plugin.ui new file mode 100644 index 0000000..95a0e17 --- /dev/null +++ b/plugins/sort/resources/ui/gedit-sort-plugin.ui @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 2.12 --> + <object class="GtkAdjustment" id="adjustment1"> + <property name="lower">1</property> + <property name="upper">100</property> + <property name="value">1</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <object class="GtkDialog" id="sort_dialog"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Sort</property> + <property name="resizable">False</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">dialog</property> + <property name="use-header-bar">1</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <property name="spacing">18</property> + <child> + <object class="GtkBox" id="vbox5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkCheckButton" id="reverse_order_checkbutton"> + <property name="label" translatable="yes">_Reverse order</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="remove_dups_checkbutton"> + <property name="label" translatable="yes">R_emove duplicates</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="case_checkbutton"> + <property name="label" translatable="yes">C_ase sensitive</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="hbox13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label18"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">S_tart at column:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">col_num_spinbutton</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="col_num_spinbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">adjustment1</property> + <property name="climb_rate">1</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="action"> + <object class="GtkButton" id="cancel"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Cancel</property> + <property name="use_underline">True</property> + </object> + </child> + <child type="action"> + <object class="GtkButton" id="sort"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Sort</property> + <property name="can-default">True</property> + <property name="use_underline">True</property> + </object> + </child> + <action-widgets> + <action-widget response="-6">cancel</action-widget> + <action-widget response="-5">sort</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/sort/sort.plugin.desktop.in b/plugins/sort/sort.plugin.desktop.in new file mode 100644 index 0000000..f2e2c78 --- /dev/null +++ b/plugins/sort/sort.plugin.desktop.in @@ -0,0 +1,11 @@ +[Plugin] +Module=sort +IAge=3 +Name=Sort +Description=Sorts a document or selected text. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +Icon=view-sort-ascending +Authors=Carlo Borreo <borreo@softhome.net>;Lee Mallabone <gnome@fonicmonkey.net>;Paolo Maggi <paolo.maggi@polito.it>;Jorge Alberto Torres H. <jorge@deadoak.com> +Copyright=Copyright © 2001 Carlo Borreo;Copyright © 2002-2003 Lee Mallabone, Paolo Maggi;Copyright © 2004-2005 Paolo Maggi +Website=http://www.gedit.org diff --git a/plugins/spell/gedit-spell-app-activatable.c b/plugins/spell/gedit-spell-app-activatable.c new file mode 100644 index 0000000..c08b0cd --- /dev/null +++ b/plugins/spell/gedit-spell-app-activatable.c @@ -0,0 +1,184 @@ +/* + * gedit-spell-app-activatable.c + * This file is part of gedit + * + * Copyright (C) 2014 - Ignacio Casal Quinteiro + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gedit-spell-app-activatable.h" +#include <glib/gi18n.h> +#include <libpeas/peas-object-module.h> +#include <gedit/gedit-app-activatable.h> +#include <gedit/gedit-app.h> + +typedef struct _GeditSpellAppActivatablePrivate GeditSpellAppActivatablePrivate; + +struct _GeditSpellAppActivatable +{ + GObject parent; +}; + +struct _GeditSpellAppActivatablePrivate +{ + GeditApp *app; + GeditMenuExtension *menu_ext; +}; + +enum +{ + PROP_0, + PROP_APP +}; + +static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditSpellAppActivatable, + gedit_spell_app_activatable, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditSpellAppActivatable) + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE, + gedit_app_activatable_iface_init)) + +static void +gedit_spell_app_activatable_dispose (GObject *object) +{ + GeditSpellAppActivatable *activatable = GEDIT_SPELL_APP_ACTIVATABLE (object); + GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (activatable); + + g_clear_object (&priv->app); + g_clear_object (&priv->menu_ext); + + G_OBJECT_CLASS (gedit_spell_app_activatable_parent_class)->dispose (object); +} + +static void +gedit_spell_app_activatable_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditSpellAppActivatable *activatable = GEDIT_SPELL_APP_ACTIVATABLE (object); + GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (activatable); + + switch (prop_id) + { + case PROP_APP: + priv->app = GEDIT_APP (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_spell_app_activatable_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditSpellAppActivatable *activatable = GEDIT_SPELL_APP_ACTIVATABLE (object); + GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (activatable); + + switch (prop_id) + { + case PROP_APP: + g_value_set_object (value, priv->app); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_spell_app_activatable_class_init (GeditSpellAppActivatableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_spell_app_activatable_dispose; + object_class->set_property = gedit_spell_app_activatable_set_property; + object_class->get_property = gedit_spell_app_activatable_get_property; + + g_object_class_override_property (object_class, PROP_APP, "app"); +} + +static void +gedit_spell_app_activatable_class_finalize (GeditSpellAppActivatableClass *klass) +{ +} + +static void +gedit_spell_app_activatable_init (GeditSpellAppActivatable *self) +{ +} + +static void +gedit_spell_app_activatable_activate (GeditAppActivatable *activatable) +{ + GeditSpellAppActivatable *app_activatable = GEDIT_SPELL_APP_ACTIVATABLE (activatable); + GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (app_activatable); + GMenuItem *item; + + const gchar *accels[] = { "<Shift>F7", NULL }; + gtk_application_set_accels_for_action (GTK_APPLICATION (priv->app),"win.check-spell", accels); + priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "spell-section"); + + item = g_menu_item_new (_("_Check Spelling…"), "win.check-spell"); + gedit_menu_extension_append_menu_item (priv->menu_ext, item); + g_object_unref (item); + + item = g_menu_item_new (_("Set _Language…"), "win.config-spell"); + gedit_menu_extension_append_menu_item (priv->menu_ext, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Highlight Misspelled Words"), "win.inline-spell-checker"); + gedit_menu_extension_append_menu_item (priv->menu_ext, item); + g_object_unref (item); +} + +static void +gedit_spell_app_activatable_deactivate (GeditAppActivatable *activatable) +{ + GeditSpellAppActivatable *app_activatable = GEDIT_SPELL_APP_ACTIVATABLE (activatable); + GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (app_activatable); + + const gchar *accels[] = { NULL }; + gtk_application_set_accels_for_action (GTK_APPLICATION (priv->app),"win.check-spell", accels); + g_clear_object (&priv->menu_ext); +} + +static void +gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface) +{ + iface->activate = gedit_spell_app_activatable_activate; + iface->deactivate = gedit_spell_app_activatable_deactivate; +} + +void +gedit_spell_app_activatable_register (GTypeModule *module) +{ + gedit_spell_app_activatable_register_type (module); + + peas_object_module_register_extension_type (PEAS_OBJECT_MODULE (module), + GEDIT_TYPE_APP_ACTIVATABLE, + GEDIT_TYPE_SPELL_APP_ACTIVATABLE); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/spell/gedit-spell-app-activatable.h b/plugins/spell/gedit-spell-app-activatable.h new file mode 100644 index 0000000..aebb5ca --- /dev/null +++ b/plugins/spell/gedit-spell-app-activatable.h @@ -0,0 +1,38 @@ +/* + * gedit-spell-app-activatable.h + * This file is part of gedit + * + * Copyright (C) 2014 - Ignacio Casal Quinteiro + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef GEDIT_SPELL_APP_ACTIVATABLE_H +#define GEDIT_SPELL_APP_ACTIVATABLE_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_SPELL_APP_ACTIVATABLE (gedit_spell_app_activatable_get_type ()) +G_DECLARE_FINAL_TYPE (GeditSpellAppActivatable, gedit_spell_app_activatable, + GEDIT, SPELL_APP_ACTIVATABLE, + GObject) + +void gedit_spell_app_activatable_register (GTypeModule *module); + +G_END_DECLS + +#endif /* GEDIT_SPELL_APP_ACTIVATABLE_H */ diff --git a/plugins/spell/gedit-spell-plugin.c b/plugins/spell/gedit-spell-plugin.c new file mode 100644 index 0000000..6116778 --- /dev/null +++ b/plugins/spell/gedit-spell-plugin.c @@ -0,0 +1,810 @@ +/* + * gedit-spell-plugin.c + * + * Copyright (C) 2002-2005 Paolo Maggi + * Copyright (C) 2015-2016 Sébastien Wilmet + * + * This program is free software; you can redistribute 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, see <http://www.gnu.org/licenses/>. + */ + +#include "gedit-spell-plugin.h" + +#include <glib/gi18n.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-app.h> +#include <gedit/gedit-window.h> +#include <gedit/gedit-window-activatable.h> +#include <gspell/gspell.h> +#include <libpeas-gtk/peas-gtk-configurable.h> + +#include "gedit-spell-app-activatable.h" + +#define GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE "gedit-spell-language" +#define GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED "gedit-spell-enabled" + +#define SPELL_ENABLED_STR "1" +#define SPELL_BASE_SETTINGS "org.gnome.gedit.plugins.spell" +#define SETTINGS_KEY_HIGHLIGHT_MISSPELLED "highlight-misspelled" + +static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface); +static void peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface); + +struct _GeditSpellPluginPrivate +{ + GeditWindow *window; + GSettings *settings; +}; + +enum +{ + PROP_0, + PROP_WINDOW +}; + +typedef struct _SpellConfigureWidget SpellConfigureWidget; + +struct _SpellConfigureWidget +{ + GtkWidget *content; + GtkWidget *highlight_button; + + GSettings *settings; +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditSpellPlugin, + gedit_spell_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE, + gedit_window_activatable_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (PEAS_GTK_TYPE_CONFIGURABLE, + peas_gtk_configurable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditSpellPlugin)) + +static void +gedit_spell_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_spell_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, plugin->priv->window); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_spell_plugin_dispose (GObject *object) +{ + GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin disposing"); + + g_clear_object (&plugin->priv->window); + g_clear_object (&plugin->priv->settings); + + G_OBJECT_CLASS (gedit_spell_plugin_parent_class)->dispose (object); +} + +static void +gedit_spell_plugin_class_init (GeditSpellPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gedit_spell_plugin_set_property; + object_class->get_property = gedit_spell_plugin_get_property; + object_class->dispose = gedit_spell_plugin_dispose; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); +} + +static void +gedit_spell_plugin_class_finalize (GeditSpellPluginClass *klass) +{ +} + +static void +gedit_spell_plugin_init (GeditSpellPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin initializing"); + + plugin->priv = gedit_spell_plugin_get_instance_private (plugin); + plugin->priv->settings = g_settings_new (SPELL_BASE_SETTINGS); +} + +static GspellChecker * +get_spell_checker (GeditDocument *doc) +{ + GspellTextBuffer *gspell_buffer; + + gspell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (GTK_TEXT_BUFFER (doc)); + return gspell_text_buffer_get_spell_checker (gspell_buffer); +} + +static const GspellLanguage * +get_language_from_metadata (GeditDocument *doc) +{ + const GspellLanguage *lang = NULL; + gchar *language_code = NULL; + + language_code = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE); + + if (language_code != NULL) + { + lang = gspell_language_lookup (language_code); + g_free (language_code); + } + + return lang; +} + +static void +check_spell_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data); + GeditSpellPluginPrivate *priv; + GeditView *view; + GspellNavigator *navigator; + GtkWidget *dialog; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + view = gedit_window_get_active_view (priv->window); + g_return_if_fail (view != NULL); + + navigator = gspell_navigator_text_view_new (GTK_TEXT_VIEW (view)); + dialog = gspell_checker_dialog_new (GTK_WINDOW (priv->window), navigator); + + gtk_widget_show (dialog); +} + +static void +language_dialog_response_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + if (response_id == GTK_RESPONSE_HELP) + { + gedit_app_show_help (GEDIT_APP (g_application_get_default ()), + GTK_WINDOW (dialog), + NULL, + "gedit-spellcheck"); + return; + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +set_language_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data); + GeditSpellPluginPrivate *priv; + GeditDocument *doc; + GspellChecker *checker; + const GspellLanguage *lang; + GtkWidget *dialog; + GtkWindowGroup *window_group; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + doc = gedit_window_get_active_document (priv->window); + g_return_if_fail (doc != NULL); + + checker = get_spell_checker (doc); + g_return_if_fail (checker != NULL); + + lang = gspell_checker_get_language (checker); + + dialog = gspell_language_chooser_dialog_new (GTK_WINDOW (priv->window), + lang, + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT); + + g_object_bind_property (dialog, "language", + checker, "language", + G_BINDING_DEFAULT); + + window_group = gedit_window_get_group (priv->window); + + gtk_window_group_add_window (window_group, GTK_WINDOW (dialog)); + + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Help"), + GTK_RESPONSE_HELP); + + g_signal_connect (dialog, + "response", + G_CALLBACK (language_dialog_response_cb), + NULL); + + gtk_widget_show (dialog); +} + +static void +inline_checker_activate_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data); + GeditSpellPluginPrivate *priv = plugin->priv; + GVariant *state; + gboolean active; + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + state = g_action_get_state (G_ACTION (action)); + g_return_if_fail (state != NULL); + + active = g_variant_get_boolean (state); + g_variant_unref (state); + + /* We must toggle ourself the value. */ + active = !active; + g_action_change_state (G_ACTION (action), g_variant_new_boolean (active)); + + view = gedit_window_get_active_view (priv->window); + if (view != NULL) + { + GeditDocument *doc; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + + /* Set metadata in the "activate" handler, not in "change-state" + * because "change-state" is called every time the state + * changes, not specifically when the user has changed the state + * herself. For example "change-state" is called to initialize + * the sate to the default value specified in the GActionEntry. + */ + gedit_document_set_metadata (doc, + GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED, + active ? SPELL_ENABLED_STR : NULL, + NULL); + } +} + +static void +inline_checker_change_state_cb (GSimpleAction *action, + GVariant *state, + gpointer data) +{ + GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data); + GeditSpellPluginPrivate *priv = plugin->priv; + GeditView *view; + gboolean active; + + gedit_debug (DEBUG_PLUGINS); + + active = g_variant_get_boolean (state); + + gedit_debug_message (DEBUG_PLUGINS, active ? "Inline Checker activated" : "Inline Checker deactivated"); + + view = gedit_window_get_active_view (priv->window); + if (view != NULL) + { + GspellTextView *gspell_view; + + gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view)); + gspell_text_view_set_inline_spell_checking (gspell_view, active); + + g_simple_action_set_state (action, g_variant_new_boolean (active)); + } +} + +static void +update_ui (GeditSpellPlugin *plugin) +{ + GeditSpellPluginPrivate *priv; + GeditTab *tab; + GeditView *view = NULL; + gboolean editable_view; + GAction *check_spell_action; + GAction *config_spell_action; + GAction *inline_checker_action; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + tab = gedit_window_get_active_tab (priv->window); + if (tab != NULL) + { + view = gedit_tab_get_view (tab); + } + + editable_view = (view != NULL) && gtk_text_view_get_editable (GTK_TEXT_VIEW (view)); + + check_spell_action = g_action_map_lookup_action (G_ACTION_MAP (priv->window), + "check-spell"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (check_spell_action), + editable_view); + + config_spell_action = g_action_map_lookup_action (G_ACTION_MAP (priv->window), + "config-spell"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (config_spell_action), + editable_view); + + inline_checker_action = g_action_map_lookup_action (G_ACTION_MAP (priv->window), + "inline-spell-checker"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (inline_checker_action), + editable_view); + + /* Update only on normal state to avoid garbage changes during e.g. file + * loading. + */ + if (tab != NULL && + gedit_tab_get_state (tab) == GEDIT_TAB_STATE_NORMAL) + { + GspellTextView *gspell_view; + gboolean inline_checking_enabled; + + gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view)); + inline_checking_enabled = gspell_text_view_get_inline_spell_checking (gspell_view); + + g_action_change_state (inline_checker_action, + g_variant_new_boolean (inline_checking_enabled)); + } +} + +static void +setup_inline_checker_from_metadata (GeditSpellPlugin *plugin, + GeditView *view) +{ + GeditDocument *doc; + gboolean enabled; + gchar *enabled_str; + GspellTextView *gspell_view; + GeditView *active_view; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + + enabled = g_settings_get_boolean(plugin->priv->settings, SETTINGS_KEY_HIGHLIGHT_MISSPELLED); + enabled_str = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED); + if (enabled_str != NULL) + { + enabled = g_str_equal (enabled_str, SPELL_ENABLED_STR); + g_free (enabled_str); + } + + gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view)); + gspell_text_view_set_inline_spell_checking (gspell_view, enabled); + + /* In case that the view is the active one we mark the spell action */ + active_view = gedit_window_get_active_view (plugin->priv->window); + + if (active_view == view) + { + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (plugin->priv->window), + "inline-spell-checker"); + g_action_change_state (action, g_variant_new_boolean (enabled)); + } +} + +static void +language_notify_cb (GspellChecker *checker, + GParamSpec *pspec, + GeditDocument *doc) +{ + const GspellLanguage *lang; + const gchar *language_code; + + g_return_if_fail (GEDIT_IS_DOCUMENT (doc)); + + lang = gspell_checker_get_language (checker); + g_return_if_fail (lang != NULL); + + language_code = gspell_language_get_code (lang); + g_return_if_fail (language_code != NULL); + + gedit_document_set_metadata (doc, + GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE, language_code, + NULL); +} + +static void +on_document_loaded (GeditDocument *doc, + GeditSpellPlugin *plugin) +{ + GspellChecker *checker; + GeditTab *tab; + GeditView *view; + + checker = get_spell_checker (doc); + + if (checker != NULL) + { + const GspellLanguage *lang; + + lang = get_language_from_metadata (doc); + + if (lang != NULL) + { + g_signal_handlers_block_by_func (checker, language_notify_cb, doc); + gspell_checker_set_language (checker, lang); + g_signal_handlers_unblock_by_func (checker, language_notify_cb, doc); + } + } + + tab = gedit_tab_get_from_document (doc); + view = gedit_tab_get_view (tab); + setup_inline_checker_from_metadata (plugin, view); +} + +static void +on_document_saved (GeditDocument *doc, + gpointer user_data) +{ + GeditTab *tab; + GeditView *view; + GspellChecker *checker; + const gchar *language_code = NULL; + GspellTextView *gspell_view; + gboolean inline_checking_enabled; + + /* Make sure to save the metadata here too */ + + checker = get_spell_checker (doc); + + if (checker != NULL) + { + const GspellLanguage *lang; + + lang = gspell_checker_get_language (checker); + if (lang != NULL) + { + language_code = gspell_language_get_code (lang); + } + } + + tab = gedit_tab_get_from_document (doc); + view = gedit_tab_get_view (tab); + + gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view)); + inline_checking_enabled = gspell_text_view_get_inline_spell_checking (gspell_view); + + gedit_document_set_metadata (doc, + GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED, + inline_checking_enabled ? SPELL_ENABLED_STR : NULL, + GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE, + language_code, + NULL); +} + +static void +activate_spell_checking_in_view (GeditSpellPlugin *plugin, + GeditView *view) +{ + GeditDocument *doc; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + + /* It is possible that a GspellChecker has already been set, for example + * if a GeditTab has moved to another window. + */ + if (get_spell_checker (doc) == NULL) + { + const GspellLanguage *lang; + GspellChecker *checker; + GspellTextBuffer *gspell_buffer; + + lang = get_language_from_metadata (doc); + checker = gspell_checker_new (lang); + + g_signal_connect_object (checker, + "notify::language", + G_CALLBACK (language_notify_cb), + doc, + 0); + + gspell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (GTK_TEXT_BUFFER (doc)); + gspell_text_buffer_set_spell_checker (gspell_buffer, checker); + g_object_unref (checker); + + setup_inline_checker_from_metadata (plugin, view); + } + + g_signal_connect_object (doc, + "loaded", + G_CALLBACK (on_document_loaded), + plugin, + 0); + + g_signal_connect_object (doc, + "saved", + G_CALLBACK (on_document_saved), + plugin, + 0); +} + +static void +disconnect_view (GeditSpellPlugin *plugin, + GeditView *view) +{ + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + /* It should still be the same buffer as the one where the signal + * handlers were connected. If not, we assume that the old buffer is + * finalized. And it is anyway safe to call + * g_signal_handlers_disconnect_by_func() if no signal handlers are + * found. + */ + g_signal_handlers_disconnect_by_func (buffer, on_document_loaded, plugin); + g_signal_handlers_disconnect_by_func (buffer, on_document_saved, plugin); +} + +static void +deactivate_spell_checking_in_view (GeditSpellPlugin *plugin, + GeditView *view) +{ + GtkTextBuffer *gtk_buffer; + GspellTextBuffer *gspell_buffer; + GspellTextView *gspell_view; + + disconnect_view (plugin, view); + + gtk_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + gspell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (gtk_buffer); + gspell_text_buffer_set_spell_checker (gspell_buffer, NULL); + + gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view)); + gspell_text_view_set_inline_spell_checking (gspell_view, FALSE); +} + +static void +tab_added_cb (GeditWindow *window, + GeditTab *tab, + GeditSpellPlugin *plugin) +{ + activate_spell_checking_in_view (plugin, gedit_tab_get_view (tab)); +} + +static void +tab_removed_cb (GeditWindow *window, + GeditTab *tab, + GeditSpellPlugin *plugin) +{ + /* Don't deactivate completely the spell checking in @tab, since the tab + * can be moved to another window and we don't want to loose the spell + * checking settings (they are not saved in metadata for unsaved + * documents). + */ + disconnect_view (plugin, gedit_tab_get_view (tab)); +} + +static void +gedit_spell_plugin_activate (GeditWindowActivatable *activatable) +{ + GeditSpellPlugin *plugin; + GeditSpellPluginPrivate *priv; + GList *views; + GList *l; + + const GActionEntry action_entries[] = + { + { "check-spell", check_spell_cb }, + { "config-spell", set_language_cb }, + { "inline-spell-checker", + inline_checker_activate_cb, + NULL, + "false", + inline_checker_change_state_cb } + }; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_SPELL_PLUGIN (activatable); + priv = plugin->priv; + + g_action_map_add_action_entries (G_ACTION_MAP (priv->window), + action_entries, + G_N_ELEMENTS (action_entries), + activatable); + + update_ui (plugin); + + views = gedit_window_get_views (priv->window); + for (l = views; l != NULL; l = l->next) + { + activate_spell_checking_in_view (plugin, GEDIT_VIEW (l->data)); + } + + g_signal_connect (priv->window, + "tab-added", + G_CALLBACK (tab_added_cb), + activatable); + + g_signal_connect (priv->window, + "tab-removed", + G_CALLBACK (tab_removed_cb), + activatable); +} + +static void +gedit_spell_plugin_deactivate (GeditWindowActivatable *activatable) +{ + GeditSpellPlugin *plugin; + GeditSpellPluginPrivate *priv; + GList *views; + GList *l; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_SPELL_PLUGIN (activatable); + priv = plugin->priv; + + g_action_map_remove_action (G_ACTION_MAP (priv->window), "check-spell"); + g_action_map_remove_action (G_ACTION_MAP (priv->window), "config-spell"); + g_action_map_remove_action (G_ACTION_MAP (priv->window), "inline-spell-checker"); + + g_signal_handlers_disconnect_by_func (priv->window, tab_added_cb, activatable); + g_signal_handlers_disconnect_by_func (priv->window, tab_removed_cb, activatable); + + views = gedit_window_get_views (priv->window); + for (l = views; l != NULL; l = l->next) + { + deactivate_spell_checking_in_view (plugin, GEDIT_VIEW (l->data)); + } +} + +static void +gedit_spell_plugin_update_state (GeditWindowActivatable *activatable) +{ + gedit_debug (DEBUG_PLUGINS); + + update_ui (GEDIT_SPELL_PLUGIN (activatable)); +} + +static void +gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface) +{ + iface->activate = gedit_spell_plugin_activate; + iface->deactivate = gedit_spell_plugin_deactivate; + iface->update_state = gedit_spell_plugin_update_state; +} + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_spell_plugin_register_type (G_TYPE_MODULE (module)); + gedit_spell_app_activatable_register (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_WINDOW_ACTIVATABLE, + GEDIT_TYPE_SPELL_PLUGIN); + peas_object_module_register_extension_type (module, + PEAS_GTK_TYPE_CONFIGURABLE, + GEDIT_TYPE_SPELL_PLUGIN); +} + +static void +highlight_button_toggled (GtkToggleButton *button, + SpellConfigureWidget *widget) +{ + gboolean status = gtk_toggle_button_get_active (button); + g_settings_set_boolean(widget->settings, SETTINGS_KEY_HIGHLIGHT_MISSPELLED, status); +} + +static void +configure_widget_destroyed (GtkWidget *widget, + gpointer data) +{ + SpellConfigureWidget *conf_widget = (SpellConfigureWidget *)data; + + gedit_debug (DEBUG_PLUGINS); + + g_object_unref (conf_widget->settings); + g_slice_free (SpellConfigureWidget, data); + + gedit_debug_message (DEBUG_PLUGINS, "END"); +} + +static SpellConfigureWidget * +get_configure_widget (GeditSpellPlugin *plugin) +{ + SpellConfigureWidget *widget; + GtkBuilder *builder; + gchar *root_objects[] = { + "spell_dialog_content", + NULL + }; + + gedit_debug (DEBUG_PLUGINS); + + widget = g_slice_new (SpellConfigureWidget); + widget->settings = g_object_ref (plugin->priv->settings); + + builder = gtk_builder_new (); + gtk_builder_add_objects_from_resource (builder, "/org/gnome/gedit/plugins/spell/ui/gedit-spell-setup-dialog.ui", + root_objects, NULL); + widget->content = GTK_WIDGET (gtk_builder_get_object (builder, "spell_dialog_content")); + g_object_ref (widget->content); + + widget->highlight_button = GTK_WIDGET (gtk_builder_get_object (builder, "highlight_button")); + g_object_unref (builder); + + gboolean status = g_settings_get_boolean(widget->settings, SETTINGS_KEY_HIGHLIGHT_MISSPELLED); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->highlight_button), status); + + g_signal_connect (widget->highlight_button, + "toggled", + G_CALLBACK (highlight_button_toggled), + widget); + + g_signal_connect (widget->content, + "destroy", + G_CALLBACK (configure_widget_destroyed), + widget); + + return widget; +} + + +static GtkWidget * +gedit_spell_plugin_create_configure_widget (PeasGtkConfigurable *configurable) +{ + SpellConfigureWidget *widget; + + widget = get_configure_widget (GEDIT_SPELL_PLUGIN (configurable)); + + return widget->content; +} + +static void +peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface) +{ + iface->create_configure_widget = gedit_spell_plugin_create_configure_widget; +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/spell/gedit-spell-plugin.h b/plugins/spell/gedit-spell-plugin.h new file mode 100644 index 0000000..13539b4 --- /dev/null +++ b/plugins/spell/gedit-spell-plugin.h @@ -0,0 +1,62 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_SPELL_PLUGIN_H +#define GEDIT_SPELL_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> + +G_BEGIN_DECLS + +#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)) + +typedef struct _GeditSpellPlugin GeditSpellPlugin; +typedef struct _GeditSpellPluginPrivate GeditSpellPluginPrivate; +typedef struct _GeditSpellPluginClass GeditSpellPluginClass; + +struct _GeditSpellPlugin +{ + PeasExtensionBase parent_instance; + + /*< private >*/ + GeditSpellPluginPrivate *priv; +}; + +struct _GeditSpellPluginClass +{ + PeasExtensionBaseClass parent_class; +}; + +GType gedit_spell_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_SPELL_PLUGIN_H */ + +/* ex:set ts=8 noet: */ diff --git a/plugins/spell/meson.build b/plugins/spell/meson.build new file mode 100644 index 0000000..8ce7634 --- /dev/null +++ b/plugins/spell/meson.build @@ -0,0 +1,54 @@ +libspell_sources = files( + 'gedit-spell-app-activatable.c', + 'gedit-spell-plugin.c', +) + +libspell_deps = [ + libgedit_dep, + gspell_dep, +] + +subdir('resources') + +libspell_sha = shared_module( + 'spell', + sources: libspell_sources, + include_directories: root_include_dir, + dependencies: libspell_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +spell_gschema_file = files('org.gnome.gedit.plugins.spell.gschema.xml') +install_data( + spell_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-spell-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + spell_gschema_file, + ] + ) +endif + +custom_target( + 'spell.plugin', + input: 'spell.plugin.desktop.in', + output: 'spell.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml b/plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml new file mode 100644 index 0000000..c1dd186 --- /dev/null +++ b/plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml @@ -0,0 +1,9 @@ +<schemalist gettext-domain="gedit"> + <schema id="org.gnome.gedit.plugins.spell" path="/org/gnome/gedit/plugins/spell/"> + <key name="highlight-misspelled" type="b"> + <default>false</default> + <summary>Highlight misspelled words</summary> + <description>Default setting for highlight misspelled words.</description> + </key> + </schema> +</schemalist> diff --git a/plugins/spell/resources/gedit-spell.gresource.xml b/plugins/spell/resources/gedit-spell.gresource.xml new file mode 100644 index 0000000..861e061 --- /dev/null +++ b/plugins/spell/resources/gedit-spell.gresource.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/gedit/plugins/spell"> + <file preprocess="xml-stripblanks">ui/gedit-spell-setup-dialog.ui</file> + </gresource> +</gresources> diff --git a/plugins/spell/resources/meson.build b/plugins/spell/resources/meson.build new file mode 100644 index 0000000..ad15d5e --- /dev/null +++ b/plugins/spell/resources/meson.build @@ -0,0 +1,8 @@ +libspell_res = gnome.compile_resources( + 'gedit-spell-resources', + 'gedit-spell.gresource.xml', +) + +libspell_sources += [ + libspell_res.get(0), +] diff --git a/plugins/spell/resources/ui/gedit-spell-setup-dialog.ui b/plugins/spell/resources/ui/gedit-spell-setup-dialog.ui new file mode 100644 index 0000000..5e922c2 --- /dev/null +++ b/plugins/spell/resources/ui/gedit-spell-setup-dialog.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface> + <requires lib="gtk+" version="3.0"/> + <object class="GtkDialog" id="spell_dialog"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Configure spell plugin</property> + <property name="type_hint">normal</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">8</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button1"> + <property name="label" translatable="yes">_Close</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <property name="always_show_image">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">10</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="spell_dialog_content"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Defaults for new documents</property> + <property name="xalign">0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="highlight_button"> + <property name="label" translatable="yes">Highlight misspelled words</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/plugins/spell/spell.plugin.desktop.in b/plugins/spell/spell.plugin.desktop.in new file mode 100644 index 0000000..1741079 --- /dev/null +++ b/plugins/spell/spell.plugin.desktop.in @@ -0,0 +1,11 @@ +[Plugin] +Module=spell +IAge=3 +Name=Spell Checker +Description=Checks the spelling of the current document. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +Icon=tools-check-spelling +Authors=Paolo Maggi <paolo@gnome.org>;Sébastien Wilmet <swilmet@gnome.org> +Copyright=Copyright © 2002-2005 Paolo Maggi;Copyright © 2015 Sébastien Wilmet +Website=http://www.gedit.org diff --git a/plugins/time/gedit-time-plugin.c b/plugins/time/gedit-time-plugin.c new file mode 100644 index 0000000..c205ce8 --- /dev/null +++ b/plugins/time/gedit-time-plugin.c @@ -0,0 +1,1078 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <glib/gi18n-lib.h> +#include <glib.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-utils.h> +#include <gedit/gedit-window.h> +#include <gedit/gedit-app-activatable.h> +#include <gedit/gedit-window-activatable.h> +#include <libpeas-gtk/peas-gtk-configurable.h> +#include <gedit/gedit-app.h> +#include "gedit-time-plugin.h" + +/* gsettings keys */ +#define TIME_BASE_SETTINGS "org.gnome.gedit.plugins.time" +#define PROMPT_TYPE_KEY "prompt-type" +#define SELECTED_FORMAT_KEY "selected-format" +#define CUSTOM_FORMAT_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", + "%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", + "%a, %d %b %Y %H:%M:%S %z", + NULL +}; + +enum +{ + COLUMN_FORMATS = 0, + COLUMN_INDEX, + NUM_COLUMNS +}; + +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; + +typedef struct _TimeConfigureWidget TimeConfigureWidget; + +struct _TimeConfigureWidget +{ + GtkWidget *content; + + GtkWidget *list; + + /* Radio buttons to indicate what should be done */ + GtkWidget *prompt; + GtkWidget *use_list; + GtkWidget *custom; + + GtkWidget *custom_entry; + GtkWidget *custom_format_example; + + GSettings *settings; +}; + +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; + + GSettings *settings; +}; + +struct _GeditTimePluginPrivate +{ + GSettings *settings; + + GSimpleAction *action; + GeditWindow *window; + + GeditApp *app; + GeditMenuExtension *menu_ext; +}; + +enum +{ + PROP_0, + PROP_WINDOW, + PROP_APP +}; + +static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface); +static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface); +static void peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditTimePlugin, + gedit_time_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE, + gedit_app_activatable_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE, + gedit_window_activatable_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (PEAS_GTK_TYPE_CONFIGURABLE, + peas_gtk_configurable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditTimePlugin)) + +static void time_cb (GAction *action, GVariant *parameter, GeditTimePlugin *plugin); + +static void +gedit_time_plugin_init (GeditTimePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin initializing"); + + plugin->priv = gedit_time_plugin_get_instance_private (plugin); + + plugin->priv->settings = g_settings_new (TIME_BASE_SETTINGS); +} + +static void +gedit_time_plugin_dispose (GObject *object) +{ + GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin disposing"); + + g_clear_object (&plugin->priv->settings); + g_clear_object (&plugin->priv->action); + g_clear_object (&plugin->priv->window); + g_clear_object (&plugin->priv->menu_ext); + g_clear_object (&plugin->priv->app); + + G_OBJECT_CLASS (gedit_time_plugin_parent_class)->dispose (object); +} + +static void +gedit_time_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value)); + break; + case PROP_APP: + plugin->priv->app = GEDIT_APP (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_time_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, plugin->priv->window); + break; + case PROP_APP: + g_value_set_object (value, plugin->priv->app); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +update_ui (GeditTimePlugin *plugin) +{ + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (plugin->priv->window); + + gedit_debug_message (DEBUG_PLUGINS, "View: %p", view); + + g_simple_action_set_enabled (plugin->priv->action, + (view != NULL) && + gtk_text_view_get_editable (GTK_TEXT_VIEW (view))); +} + +static void +gedit_time_plugin_app_activate (GeditAppActivatable *activatable) +{ + GeditTimePluginPrivate *priv; + GMenuItem *item; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_TIME_PLUGIN (activatable)->priv; + + priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "tools-section"); + item = g_menu_item_new (_("In_sert Date and Time…"), "win.time"); + gedit_menu_extension_append_menu_item (priv->menu_ext, item); + g_object_unref (item); +} + +static void +gedit_time_plugin_app_deactivate (GeditAppActivatable *activatable) +{ + GeditTimePluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_TIME_PLUGIN (activatable)->priv; + + g_clear_object (&priv->menu_ext); +} + +static void +gedit_time_plugin_window_activate (GeditWindowActivatable *activatable) +{ + GeditTimePluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_TIME_PLUGIN (activatable)->priv; + + priv->action = g_simple_action_new ("time", NULL); + g_signal_connect (priv->action, "activate", + G_CALLBACK (time_cb), activatable); + g_action_map_add_action (G_ACTION_MAP (priv->window), + G_ACTION (priv->action)); + + update_ui (GEDIT_TIME_PLUGIN (activatable)); +} + +static void +gedit_time_plugin_window_deactivate (GeditWindowActivatable *activatable) +{ + GeditTimePluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_TIME_PLUGIN (activatable)->priv; + + g_action_map_remove_action (G_ACTION_MAP (priv->window), "time"); +} + +static void +gedit_time_plugin_window_update_state (GeditWindowActivatable *activatable) +{ + gedit_debug (DEBUG_PLUGINS); + + update_ui (GEDIT_TIME_PLUGIN (activatable)); +} + +/* The selected format in the list */ +static gchar * +get_selected_format (GeditTimePlugin *plugin) +{ + gchar *sel_format; + + sel_format = g_settings_get_string (plugin->priv->settings, + SELECTED_FORMAT_KEY); + + return sel_format ? sel_format : g_strdup (formats [0]); +} + +/* the custom format in the entry */ +static gchar * +get_custom_format (GeditTimePlugin *plugin) +{ + gchar *format; + + format = g_settings_get_string (plugin->priv->settings, + CUSTOM_FORMAT_KEY); + + return format ? format : g_strdup (DEFAULT_CUSTOM_FORMAT); +} + +static gchar * +get_time (const gchar *format) +{ + gchar *out; + GDateTime *now; + + gedit_debug (DEBUG_PLUGINS); + + g_return_val_if_fail (format != NULL, NULL); + + if (*format == '\0') + return g_strdup (" "); + + now = g_date_time_new_now_local (); + out = g_date_time_format (now, format); + g_date_time_unref (now); + + return out; +} + +static void +configure_widget_destroyed (GtkWidget *widget, + gpointer data) +{ + TimeConfigureWidget *conf_widget = (TimeConfigureWidget *)data; + + gedit_debug (DEBUG_PLUGINS); + + g_object_unref (conf_widget->settings); + g_slice_free (TimeConfigureWidget, data); + + gedit_debug_message (DEBUG_PLUGINS, "END"); +} + +static void +choose_format_dialog_destroyed (GtkWidget *widget, + gpointer dialog_pointer) +{ + gedit_debug (DEBUG_PLUGINS); + + g_slice_free (ChooseFormatDialog, 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); + } + else 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); + } + else + { + g_return_if_reached (); + } +} + +static void +configure_widget_button_toggled (GtkToggleButton *button, + TimeConfigureWidget *conf_widget) +{ + GeditTimePluginPromptType prompt_type; + + gedit_debug (DEBUG_PLUGINS); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (conf_widget->custom))) + { + gtk_widget_set_sensitive (conf_widget->list, FALSE); + gtk_widget_set_sensitive (conf_widget->custom_entry, TRUE); + gtk_widget_set_sensitive (conf_widget->custom_format_example, TRUE); + + prompt_type = USE_CUSTOM_FORMAT; + } + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (conf_widget->use_list))) + { + gtk_widget_set_sensitive (conf_widget->list, TRUE); + gtk_widget_set_sensitive (conf_widget->custom_entry, FALSE); + gtk_widget_set_sensitive (conf_widget->custom_format_example, FALSE); + + prompt_type = USE_SELECTED_FORMAT; + } + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (conf_widget->prompt))) + { + gtk_widget_set_sensitive (conf_widget->list, FALSE); + gtk_widget_set_sensitive (conf_widget->custom_entry, FALSE); + gtk_widget_set_sensitive (conf_widget->custom_format_example, FALSE); + + prompt_type = PROMPT_SELECTED_FORMAT; + } + else + { + g_return_if_reached (); + } + + g_settings_set_enum (conf_widget->settings, + PROMPT_TYPE_KEY, + prompt_type); +} + +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 void +on_configure_widget_selection_changed (GtkTreeSelection *selection, + TimeConfigureWidget *conf_widget) +{ + gint sel_format; + + sel_format = get_format_from_list (conf_widget->list); + g_settings_set_string (conf_widget->settings, + SELECTED_FORMAT_KEY, + formats[sel_format]); +} + +static TimeConfigureWidget * +get_configure_widget (GeditTimePlugin *plugin) +{ + TimeConfigureWidget *widget; + GtkTreeSelection *selection; + GtkWidget *viewport; + GeditTimePluginPromptType prompt_type; + gchar *sf; + GtkBuilder *builder; + gchar *root_objects[] = { + "time_dialog_content", + NULL + }; + + gedit_debug (DEBUG_PLUGINS); + + widget = g_slice_new (TimeConfigureWidget); + widget->settings = g_object_ref (plugin->priv->settings); + + builder = gtk_builder_new (); + gtk_builder_add_objects_from_resource (builder, "/org/gnome/gedit/plugins/time/ui/gedit-time-setup-dialog.ui", + root_objects, NULL); + widget->content = GTK_WIDGET (gtk_builder_get_object (builder, "time_dialog_content")); + g_object_ref (widget->content); + viewport = GTK_WIDGET (gtk_builder_get_object (builder, "formats_viewport")); + widget->list = GTK_WIDGET (gtk_builder_get_object (builder, "formats_tree")); + widget->prompt = GTK_WIDGET (gtk_builder_get_object (builder, "always_prompt")); + widget->use_list = GTK_WIDGET (gtk_builder_get_object (builder, "never_prompt")); + widget->custom = GTK_WIDGET (gtk_builder_get_object (builder, "use_custom")); + widget->custom_entry = GTK_WIDGET (gtk_builder_get_object (builder, "custom_entry")); + widget->custom_format_example = GTK_WIDGET (gtk_builder_get_object (builder, "custom_format_example")); + g_object_unref (builder); + + sf = get_selected_format (plugin); + create_formats_list (widget->list, sf, plugin); + g_free (sf); + + prompt_type = g_settings_get_enum (plugin->priv->settings, + PROMPT_TYPE_KEY); + + g_settings_bind (widget->settings, + CUSTOM_FORMAT_KEY, + widget->custom_entry, + "text", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET); + + if (prompt_type == USE_CUSTOM_FORMAT) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->custom), TRUE); + + gtk_widget_set_sensitive (widget->list, FALSE); + gtk_widget_set_sensitive (widget->custom_entry, TRUE); + gtk_widget_set_sensitive (widget->custom_format_example, TRUE); + } + else if (prompt_type == USE_SELECTED_FORMAT) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->use_list), TRUE); + + gtk_widget_set_sensitive (widget->list, TRUE); + gtk_widget_set_sensitive (widget->custom_entry, FALSE); + gtk_widget_set_sensitive (widget->custom_format_example, FALSE); + } + else + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->prompt), TRUE); + + gtk_widget_set_sensitive (widget->list, FALSE); + gtk_widget_set_sensitive (widget->custom_entry, FALSE); + gtk_widget_set_sensitive (widget->custom_format_example, FALSE); + } + + updated_custom_format_example (GTK_ENTRY (widget->custom_entry), + GTK_LABEL (widget->custom_format_example)); + + /* setup a window of a sane size. */ + gtk_widget_set_size_request (GTK_WIDGET (viewport), 10, 200); + + g_signal_connect (widget->custom, + "toggled", + G_CALLBACK (configure_widget_button_toggled), + widget); + g_signal_connect (widget->prompt, + "toggled", + G_CALLBACK (configure_widget_button_toggled), + widget); + g_signal_connect (widget->use_list, + "toggled", + G_CALLBACK (configure_widget_button_toggled), + widget); + g_signal_connect (widget->content, + "destroy", + G_CALLBACK (configure_widget_destroyed), + widget); + g_signal_connect (widget->custom_entry, + "changed", + G_CALLBACK (updated_custom_format_example), + widget->custom_format_example); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->list)); + + g_signal_connect (selection, + "changed", + G_CALLBACK (on_configure_widget_selection_changed), + widget); + + return widget; +} + +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]); + + g_settings_set_enum (dialog->settings, + PROMPT_TYPE_KEY, + PROMPT_SELECTED_FORMAT); + g_settings_set_string (dialog->settings, + SELECTED_FORMAT_KEY, + 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; + GtkBuilder *builder; + gchar *sf, *cf; + GtkWindowGroup *wg = NULL; + + if (parent != NULL) + wg = gtk_window_get_group (parent); + + dialog = g_slice_new (ChooseFormatDialog); + dialog->settings = plugin->priv->settings; + + builder = gtk_builder_new (); + gtk_builder_add_from_resource (builder, "/org/gnome/gedit/plugins/time/ui/gedit-time-dialog.ui", + NULL); + dialog->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "choose_format_dialog")); + dialog->list = GTK_WIDGET (gtk_builder_get_object (builder, "choice_list")); + dialog->use_list = GTK_WIDGET (gtk_builder_get_object (builder, "use_sel_format_radiobutton")); + dialog->custom = GTK_WIDGET (gtk_builder_get_object (builder, "use_custom_radiobutton")); + dialog->custom_entry = GTK_WIDGET (gtk_builder_get_object (builder, "custom_entry")); + dialog->custom_format_example = GTK_WIDGET (gtk_builder_get_object (builder, "custom_format_example")); + g_object_unref (builder); + + 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 (choose_format_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_app_show_help (GEDIT_APP (g_application_get_default ()), + GTK_WINDOW (widget), + NULL, + "gedit-plugins-insert-date-time"); + 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]); + + g_settings_set_enum (dialog->settings, + PROMPT_TYPE_KEY, + PROMPT_SELECTED_FORMAT); + g_settings_set_string (dialog->settings, + SELECTED_FORMAT_KEY, + formats[sel_format]); + } + else + { + const gchar *format; + + format = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry)); + the_time = get_time (format); + + g_settings_set_enum (dialog->settings, + PROMPT_TYPE_KEY, + PROMPT_CUSTOM_FORMAT); + g_settings_set_string (dialog->settings, + CUSTOM_FORMAT_KEY, + 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 (GAction *action, + GVariant *parameter, + GeditTimePlugin *plugin) +{ + GeditTimePluginPrivate *priv; + GtkTextBuffer *buffer; + GeditTimePluginPromptType prompt_type; + gchar *the_time = NULL; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + buffer = GTK_TEXT_BUFFER (gedit_window_get_active_document (priv->window)); + g_return_if_fail (buffer != NULL); + + prompt_type = g_settings_get_enum (plugin->priv->settings, + PROMPT_TYPE_KEY); + + if (prompt_type == USE_CUSTOM_FORMAT) + { + gchar *cf = get_custom_format (plugin); + the_time = get_time (cf); + g_free (cf); + } + else if (prompt_type == USE_SELECTED_FORMAT) + { + gchar *sf = get_selected_format (plugin); + the_time = get_time (sf); + g_free (sf); + } + else + { + ChooseFormatDialog *dialog; + + dialog = get_choose_format_dialog (GTK_WINDOW (priv->window), + prompt_type, + plugin); + if (dialog != NULL) + { + dialog->buffer = buffer; + dialog->settings = plugin->priv->settings; + + 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 GtkWidget * +gedit_time_plugin_create_configure_widget (PeasGtkConfigurable *configurable) +{ + TimeConfigureWidget *widget; + + widget = get_configure_widget (GEDIT_TIME_PLUGIN (configurable)); + + return widget->content; +} + +static void +gedit_time_plugin_class_init (GeditTimePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_time_plugin_dispose; + object_class->set_property = gedit_time_plugin_set_property; + object_class->get_property = gedit_time_plugin_get_property; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); + g_object_class_override_property (object_class, PROP_APP, "app"); +} + +static void +gedit_time_plugin_class_finalize (GeditTimePluginClass *klass) +{ +} + +static void +peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface) +{ + iface->create_configure_widget = gedit_time_plugin_create_configure_widget; +} + +static void +gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface) +{ + iface->activate = gedit_time_plugin_app_activate; + iface->deactivate = gedit_time_plugin_app_deactivate; +} + +static void +gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface) +{ + iface->activate = gedit_time_plugin_window_activate; + iface->deactivate = gedit_time_plugin_window_deactivate; + iface->update_state = gedit_time_plugin_window_update_state; +} + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_time_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_APP_ACTIVATABLE, + GEDIT_TYPE_TIME_PLUGIN); + peas_object_module_register_extension_type (module, + GEDIT_TYPE_WINDOW_ACTIVATABLE, + GEDIT_TYPE_TIME_PLUGIN); + peas_object_module_register_extension_type (module, + PEAS_GTK_TYPE_CONFIGURABLE, + GEDIT_TYPE_TIME_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/time/gedit-time-plugin.h b/plugins/time/gedit-time-plugin.h new file mode 100644 index 0000000..42ad349 --- /dev/null +++ b/plugins/time/gedit-time-plugin.h @@ -0,0 +1,61 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_TIME_PLUGIN_H +#define GEDIT_TIME_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> + +G_BEGIN_DECLS + +#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)) + +typedef struct _GeditTimePlugin GeditTimePlugin; +typedef struct _GeditTimePluginPrivate GeditTimePluginPrivate; +typedef struct _GeditTimePluginClass GeditTimePluginClass; + +struct _GeditTimePlugin +{ + PeasExtensionBase parent_instance; + + /*< private >*/ + GeditTimePluginPrivate *priv; +}; + +struct _GeditTimePluginClass +{ + PeasExtensionBaseClass parent_class; +}; + +GType gedit_time_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_TIME_PLUGIN_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/time/meson.build b/plugins/time/meson.build new file mode 100644 index 0000000..f3e43a3 --- /dev/null +++ b/plugins/time/meson.build @@ -0,0 +1,68 @@ +libtime_sources = files( + 'gedit-time-plugin.c', +) + +libtime_deps = [ + libgedit_dep, +] + +gnome.mkenums( + 'org.gnome.gedit.plugins.time.enums.xml', + sources: libtime_sources, + comments: '<!-- @comment@ -->', + fhead: '<schemalist>', + vhead: ' <@type@ id="org.gnome.gedit.plugins.time.@EnumName@">', + vprod: ' <value nick="@valuenick@" value="@valuenum@"/>', + vtail: ' </@type@>', + ftail: '</schemalist>', + install_header: true, + install_dir: join_paths( + glibdir, + 'schemas', + ) +) + +subdir('resources') + +libtime_sha = shared_module( + 'time', + sources: libtime_sources, + include_directories: root_include_dir, + dependencies: libtime_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +time_gschema_file = files('org.gnome.gedit.plugins.time.gschema.xml') +install_data( + time_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-time-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + time_gschema_file, + ] + ) +endif + +custom_target( + 'time.plugin', + input: 'time.plugin.desktop.in', + output: 'time.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/time/org.gnome.gedit.plugins.time.gschema.xml b/plugins/time/org.gnome.gedit.plugins.time.gschema.xml new file mode 100644 index 0000000..d012191 --- /dev/null +++ b/plugins/time/org.gnome.gedit.plugins.time.gschema.xml @@ -0,0 +1,19 @@ +<schemalist gettext-domain="gedit"> + <schema id="org.gnome.gedit.plugins.time" path="/org/gnome/gedit/plugins/time/"> + <key name="prompt-type" enum="org.gnome.gedit.plugins.time.GeditTimePluginPromptType"> + <default>'prompt-selected-format'</default> + <summary>Prompt Type</summary> + <description>If the user should be prompted for a format or if the selected or custom format should be used.</description> + </key> + <key name="selected-format" type="s"> + <default>'%c'</default> + <summary>Selected Format</summary> + <description>The selected format used when inserting the date/time.</description> + </key> + <key name="custom-format" type="s"> + <default>'%d/%m/%Y %H:%M:%S'</default> + <summary>Custom Format</summary> + <description>The custom format used when inserting the date/time.</description> + </key> + </schema> +</schemalist> diff --git a/plugins/time/resources/gedit-time.gresource.xml b/plugins/time/resources/gedit-time.gresource.xml new file mode 100644 index 0000000..c7cf490 --- /dev/null +++ b/plugins/time/resources/gedit-time.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/gedit/plugins/time"> + <file preprocess="xml-stripblanks">ui/gedit-time-dialog.ui</file> + <file preprocess="xml-stripblanks">ui/gedit-time-setup-dialog.ui</file> + </gresource> +</gresources> diff --git a/plugins/time/resources/meson.build b/plugins/time/resources/meson.build new file mode 100644 index 0000000..bcf0372 --- /dev/null +++ b/plugins/time/resources/meson.build @@ -0,0 +1,8 @@ +libtime_res = gnome.compile_resources( + 'gedit-time-resources', + 'gedit-time.gresource.xml', +) + +libtime_sources += [ + libtime_res.get(0), +] diff --git a/plugins/time/resources/ui/gedit-time-dialog.ui b/plugins/time/resources/ui/gedit-time-dialog.ui new file mode 100644 index 0000000..063a061 --- /dev/null +++ b/plugins/time/resources/ui/gedit-time-dialog.ui @@ -0,0 +1,198 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkDialog" id="choose_format_dialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Insert Date and Time</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkGrid" id="grid1"> + <property name="border_width">10</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkRadioButton" id="use_sel_format_radiobutton"> + <property name="label" translatable="yes">Use the _selected format</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="height_request">180</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="margin_start">12</property> + <property name="margin_bottom">8</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="choice_list"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="use_custom_radiobutton"> + <property name="label" translatable="yes">_Use custom format</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">use_sel_format_radiobutton</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="custom_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="text" translatable="yes">%d/%m/%Y %H:%M:%S</property> + <property name="margin_start">6</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="custom_format_example"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">01/11/2009 17:52:00</property> + <property name="justify">right</property> + <attributes> + <attribute name="scale" value="0.80000000000000004"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="help_button"> + <property name="label" translatable="yes">_Help</property> + <property name="use_action_appearance">False</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_action_appearance">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="label" translatable="yes">_Cancel</property> + <property name="use_action_appearance">False</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_action_appearance">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="insert_button"> + <property name="label" translatable="yes">_Insert</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-11">help_button</action-widget> + <action-widget response="-6">cancel_button</action-widget> + <action-widget response="-5">insert_button</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/time/resources/ui/gedit-time-setup-dialog.ui b/plugins/time/resources/ui/gedit-time-setup-dialog.ui new file mode 100644 index 0000000..3eff64f --- /dev/null +++ b/plugins/time/resources/ui/gedit-time-setup-dialog.ui @@ -0,0 +1,214 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkDialog" id="time_dialog"> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Configure date/time plugin</property> + <property name="type_hint">normal</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">8</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button1"> + <property name="label" translatable="yes">_Close</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="time_dialog_content"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">When inserting date/time…</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">12</property> + <child> + <object class="GtkRadioButton" id="always_prompt"> + <property name="label" translatable="yes">_Prompt for a format</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="never_prompt"> + <property name="label" translatable="yes">Use the _selected format</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">always_prompt</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="formats_viewport"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="margin_start">12</property> + <property name="margin_bottom">8</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="formats_tree"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="headers_visible">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection"/> + </child> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="use_custom"> + <property name="label" translatable="yes">_Use custom format</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">always_prompt</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="custom_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</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="margin_start">6</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="custom_format_example"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="xalign">1</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="justify">right</property> + <attributes> + <attribute name="scale" value="0.80000000000000004"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">button4</action-widget> + </action-widgets> + </object> +</interface> diff --git a/plugins/time/time.plugin.desktop.in b/plugins/time/time.plugin.desktop.in new file mode 100644 index 0000000..1086547 --- /dev/null +++ b/plugins/time/time.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=time +IAge=3 +Name=Insert Date/Time +Description=Inserts current date and time at the cursor position. +Authors=Paolo Maggi <paolo.maggi@polito.it>;Lee Mallabone <gnome@fonicmonkey.net> +Copyright=Copyright © 2002-2005 Paolo Maggi +Website=http://www.gedit.org |