[Xfce4-commits] <xfce4-appfinder:nick/properties-dialog> Initial work on custom actions.

Nick Schermer noreply at xfce.org
Mon Sep 19 21:10:01 CEST 2011


Updating branch refs/heads/nick/properties-dialog
         to 710bf82fbb6e7e28d44e6bae2811f99fc0ccbcd0 (commit)
       from de1b26d156b3a76ca94a3e0949a2b642fcfc8c8d (commit)

commit 710bf82fbb6e7e28d44e6bae2811f99fc0ccbcd0
Author: Nick Schermer <nick at xfce.org>
Date:   Sun Aug 7 14:55:02 2011 +0200

    Initial work on custom actions.
    
    Action removing and adding is not working yet.

 src/Makefile.am                 |    2 +
 src/appfinder-actions.c         |  524 +++++++++++++++++++++++++++++++++++++++
 src/appfinder-actions.h         |   57 +++++
 src/appfinder-preferences.c     |  280 ++++++++++++++++++++--
 src/appfinder-preferences.glade |  134 +++++-----
 src/appfinder-window.c          |   71 ++----
 6 files changed, 934 insertions(+), 134 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index 91065af..37e1509 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,6 +13,8 @@ xfce4_appfinder_built_sources = \
 
 xfce4_appfinder_SOURCES = \
 	$(xfce4_appfinder_built_sources) \
+	appfinder-actions.c \
+	appfinder-actions.h \
 	appfinder-category-model.c \
 	appfinder-category-model.h \
 	appfinder-model.c \
diff --git a/src/appfinder-actions.c b/src/appfinder-actions.c
new file mode 100644
index 0000000..cfb5b46
--- /dev/null
+++ b/src/appfinder-actions.c
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2011 Nick Schermer <nick at xfce.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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include <libxfce4util/libxfce4util.h>
+#include <libxfce4ui/libxfce4ui.h>
+#include <xfconf/xfconf.h>
+
+#include <src/appfinder-actions.h>
+#include <src/appfinder-private.h>
+
+
+
+static void xfce_appfinder_actions_finalize (GObject              *object);
+static void xfce_appfinder_actions_free     (XfceAppfinderAction  *action);
+static void xfce_appfinder_actions_load     (XfceAppfinderActions *actions);
+static void xfce_appfinder_actions_save     (XfceAppfinderActions *actions,
+                                             gboolean              save_actions);
+static void xfce_appfinder_actions_changed  (XfconfChannel        *channel,
+                                             const gchar          *prop_name,
+                                             const GValue         *value,
+                                             XfceAppfinderActions *actions);
+
+
+
+struct _XfceAppfinderActionsClass
+{
+  GObjectClass __parent__;
+};
+
+struct _XfceAppfinderActions
+{
+  GObject __parent__;
+
+  XfconfChannel *channel;
+  gulong         property_watch_id;
+
+  GSList        *actions;
+};
+
+typedef enum
+{
+  XFCE_APPFINDER_ACTION_TYPE_PREFIX,
+  XFCE_APPFINDER_ACTION_TYPE_REGEX
+}
+AppfinderActionType;
+
+struct _XfceAppfinderAction
+{
+  AppfinderActionType  type;
+
+  gint                 unique_id;
+  gchar               *pattern;
+  gchar               *command;
+
+  GRegex              *regex;
+};
+
+
+
+G_DEFINE_TYPE (XfceAppfinderActions, xfce_appfinder_actions, G_TYPE_OBJECT)
+
+
+
+static void
+xfce_appfinder_actions_class_init (XfceAppfinderActionsClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = xfce_appfinder_actions_finalize;
+}
+
+
+
+static void
+xfce_appfinder_actions_init (XfceAppfinderActions *actions)
+{
+  actions->channel = xfconf_channel_get ("xfce4-appfinder");
+
+  xfce_appfinder_actions_load (actions);
+
+  actions->property_watch_id =
+    g_signal_connect (G_OBJECT (actions->channel), "property-changed",
+                      G_CALLBACK (xfce_appfinder_actions_changed), actions);
+}
+
+
+
+static void
+xfce_appfinder_actions_finalize (GObject *object)
+{
+  XfceAppfinderActions *actions = XFCE_APPFINDER_ACTIONS (object);
+
+  g_signal_handler_disconnect (actions->channel, actions->property_watch_id);
+
+  g_slist_foreach (actions->actions, (GFunc) xfce_appfinder_actions_free, NULL);
+  g_slist_free (actions->actions);
+
+  (*G_OBJECT_CLASS (xfce_appfinder_actions_parent_class)->finalize) (object);
+}
+
+
+
+static void
+xfce_appfinder_actions_free (XfceAppfinderAction *action)
+{
+  g_free (action->pattern);
+  g_free (action->command);
+
+  if (action->regex != NULL)
+    g_regex_unref (action->regex);
+
+  g_slice_free (XfceAppfinderAction, action);
+}
+
+
+
+static void
+xfce_appfinder_actions_load_defaults (XfceAppfinderActions *actions)
+{
+  guint                i;
+  XfceAppfinderAction *action;
+  XfceAppfinderAction  defaults[] =
+  {
+    { XFCE_APPFINDER_ACTION_TYPE_PREFIX, 0,
+      "#",
+      "exo-open --launch TerminalEmulator man %s",
+      NULL },
+    { XFCE_APPFINDER_ACTION_TYPE_PREFIX, 0,
+      "!",
+      "exo-open --launch TerminalEmulator %s",
+      NULL },
+    { XFCE_APPFINDER_ACTION_TYPE_PREFIX, 0,
+      "!w",
+      "exo-open --launch WebBrowser http://en.wikipedia.org/wiki/%s",
+      NULL },
+    { XFCE_APPFINDER_ACTION_TYPE_REGEX, 0,
+      "^(file|http|https):\\/\\/(.*)$",
+      "exo-open \\0",
+      NULL }
+  };
+
+  APPFINDER_DEBUG ("loaded default actions");
+
+  for (i = 0; i < G_N_ELEMENTS (defaults); i++)
+    {
+      action = g_slice_new0 (XfceAppfinderAction);
+      action->type = defaults[i].type;
+      action->unique_id = i + 1;
+      action->pattern = g_strdup (defaults[i].pattern);
+      action->command = g_strdup (defaults[i].command);
+
+      actions->actions = g_slist_prepend (actions->actions, action);
+    }
+}
+
+
+
+static gint
+xfce_appfinder_actions_sort (gconstpointer a,
+                             gconstpointer b)
+{
+  const XfceAppfinderAction *action_a = a;
+  const XfceAppfinderAction *action_b = b;
+
+  if (action_a->type != action_b->type)
+    return action_a->type == XFCE_APPFINDER_ACTION_TYPE_PREFIX ? -1 : 1;
+
+  /* reverse the order so prefixes are properly matched */
+  return -g_strcmp0 (action_a->pattern, action_b->pattern);
+}
+
+
+
+static void
+xfce_appfinder_actions_load (XfceAppfinderActions *actions)
+{
+  XfceAppfinderAction *action;
+  gchar                prop[32];
+  gint                 type;
+  gchar               *pattern, *command;
+  const GValue        *value;
+  guint                i;
+  gint                 unique_id;
+  GPtrArray           *array;
+
+  if (xfconf_channel_has_property (actions->channel, "/actions"))
+    {
+      array = xfconf_channel_get_arrayv (actions->channel, "/actions");
+      if (G_LIKELY (array != NULL))
+        {
+          for (i = 0; i < array->len; i++)
+            {
+              value = g_ptr_array_index (array, i);
+              appfinder_assert (value != NULL);
+              unique_id = g_value_get_int (value);
+
+              g_snprintf (prop, sizeof (prop), "/actions/action-%d/type", unique_id);
+              type = xfconf_channel_get_int (actions->channel, prop, -1);
+              if (type < XFCE_APPFINDER_ACTION_TYPE_PREFIX
+                  || type > XFCE_APPFINDER_ACTION_TYPE_REGEX)
+                continue;
+
+              g_snprintf (prop, sizeof (prop), "/actions/action-%d/pattern", unique_id);
+              pattern = xfconf_channel_get_string (actions->channel, prop, NULL);
+
+              g_snprintf (prop, sizeof (prop), "/actions/action-%d/command", unique_id);
+              command = xfconf_channel_get_string (actions->channel, prop, NULL);
+
+              if (pattern != NULL && command != NULL)
+                {
+                  action = g_slice_new0 (XfceAppfinderAction);
+                  action->type = type;
+                  action->unique_id = unique_id;
+                  action->pattern = pattern;
+                  action->command = command;
+
+                  actions->actions = g_slist_prepend (actions->actions, action);
+                }
+              else
+                {
+                  g_free (pattern);
+                  g_free (command);
+                }
+            }
+
+          xfconf_array_free (array);
+        }
+    }
+  else
+    {
+      xfce_appfinder_actions_load_defaults (actions);
+
+      xfce_appfinder_actions_save (actions, TRUE);
+    }
+
+  actions->actions = g_slist_sort (actions->actions, xfce_appfinder_actions_sort);
+
+  APPFINDER_DEBUG ("loaded %d actions", g_slist_length (actions->actions));
+}
+
+
+
+static void
+xfce_appfinder_actions_save (XfceAppfinderActions *actions,
+                             gboolean              save_actions)
+{
+  XfceAppfinderAction *action;
+  GSList              *li;
+  GValue              *value;
+  GPtrArray           *array;
+  gchar                prop[32];
+
+  g_signal_handler_block (actions->channel, actions->property_watch_id);
+
+  array = g_ptr_array_new ();
+
+  for (li = actions->actions; li != NULL; li = li->next)
+    {
+      value = g_new0 (GValue, 1);
+      g_value_init (value, G_TYPE_INT);
+
+      action = li->data;
+      g_value_set_int (value, action->unique_id);
+      g_ptr_array_add (array, value);
+
+      if (save_actions)
+        {
+          g_snprintf (prop, sizeof (prop), "/actions/action-%d/type", action->unique_id);
+          xfconf_channel_set_int (actions->channel, prop, action->type);
+
+          g_snprintf (prop, sizeof (prop), "/actions/action-%d/pattern", action->unique_id);
+          xfconf_channel_set_string (actions->channel, prop, action->pattern);
+
+          g_snprintf (prop, sizeof (prop), "/actions/action-%d/command", action->unique_id);
+          xfconf_channel_set_string (actions->channel, prop, action->command);
+        }
+    }
+
+  xfconf_channel_set_arrayv (actions->channel, "/actions", array);
+
+  xfconf_array_free (array);
+
+  g_signal_handler_unblock (actions->channel, actions->property_watch_id);
+}
+
+
+
+static void
+xfce_appfinder_actions_changed (XfconfChannel        *channel,
+                                const gchar          *prop_name,
+                                const GValue         *value,
+                                XfceAppfinderActions *actions)
+{
+  gint                 unique_id;
+  gchar                field[32];
+  GSList              *li;
+  XfceAppfinderAction *action;
+
+  if (prop_name == NULL)
+    return;
+
+  if (strcmp (prop_name, "/actions") == 0)
+    {
+
+    }
+  else if (sscanf (prop_name, "/actions/action-%d/%30s",
+                   &unique_id, field) == 2)
+    {
+      for (li = actions->actions; li != NULL; li = li->next)
+        {
+          action = li->data;
+
+          if (action->unique_id == unique_id)
+            {
+              if (strcmp (field, "type") == 0
+                  && G_VALUE_HOLDS_INT (value))
+                {
+                  action->type = g_value_get_int (value);
+                }
+              else if (strcmp (field, "pattern") == 0
+                       && G_VALUE_HOLDS_STRING (value))
+                {
+                  g_free (action->pattern);
+                  action->pattern = g_value_dup_string (value);
+
+                  if (action->regex != NULL)
+                    {
+                      g_regex_unref (action->regex);
+                      action->regex = NULL;
+                    }
+                }
+              else if (strcmp (field, "command") == 0
+                       && G_VALUE_HOLDS_STRING (value))
+                {
+                  g_free (action->command);
+                  action->command = g_value_dup_string (value);
+                }
+
+              break;
+            }
+        }
+    }
+}
+
+
+
+static gchar *
+xfce_appfinder_actions_expand_command (XfceAppfinderAction *action,
+                                       const gchar         *text)
+{
+  GString     *string;
+  const gchar *p;
+  gsize        len;
+  gchar       *trim;
+
+  if (G_UNLIKELY (action->command == NULL))
+    return NULL;
+
+  string = g_string_sized_new (128);
+  len = strlen (action->pattern);
+
+  for (p = action->command; *p != '\0'; ++p)
+    {
+      if (G_UNLIKELY (p[0] == '%' && p[1] != '\0'))
+        {
+          switch (*++p)
+            {
+            case 's':
+              trim = g_strdup (text + len);
+              g_string_append (string, g_strchug (trim));
+              g_free (trim);
+              break;
+
+            case 'S':
+              g_string_append (string, text);
+              break;
+
+            case '%':
+              g_string_append_c (string, '%');
+              break;
+            }
+        }
+      else
+        {
+          g_string_append_c (string, *p);
+        }
+    }
+
+  return g_string_free (string, FALSE);
+}
+
+
+
+XfceAppfinderActions *
+xfce_appfinder_actions_get (void)
+{
+  static XfceAppfinderActions *actions = NULL;
+
+  if (G_LIKELY (actions != NULL))
+    {
+      g_object_ref (G_OBJECT (actions));
+    }
+  else
+    {
+      actions = g_object_new (XFCE_TYPE_APPFINDER_ACTIONS, NULL);
+      g_object_add_weak_pointer (G_OBJECT (actions), (gpointer) &actions);
+      APPFINDER_DEBUG ("allocate actions");
+    }
+
+  return actions;
+}
+
+
+
+XfceAppfinderActionsResult
+xfce_appfinder_actions_execute (XfceAppfinderActions  *actions,
+                                const gchar           *text,
+                                GdkScreen             *screen,
+                                GError               **error)
+{
+  GSList                     *li;
+  XfceAppfinderAction        *action;
+  XfceAppfinderActionsResult  result = XFCE_APPFINDER_ACTIONS_NOTHING_FOUND;
+  GError                     *err = NULL;
+  gchar                      *cmd = NULL;
+  GMatchInfo                 *match_info = NULL;
+  gchar                      *expanded;
+
+  appfinder_return_val_if_fail (error != NULL && *error == NULL, XFCE_APPFINDER_ACTIONS_NOTHING_FOUND);
+  appfinder_return_val_if_fail (GDK_IS_SCREEN (screen), XFCE_APPFINDER_ACTIONS_NOTHING_FOUND);
+  appfinder_return_val_if_fail (XFCE_IS_APPFINDER_ACTIONS (actions), XFCE_APPFINDER_ACTIONS_NOTHING_FOUND);
+  appfinder_return_val_if_fail (text != NULL, XFCE_APPFINDER_ACTIONS_NOTHING_FOUND);
+
+  for (li = actions->actions;
+       result == XFCE_APPFINDER_ACTIONS_NOTHING_FOUND && li != NULL;
+       li = li->next)
+    {
+      action = li->data;
+
+      switch (action->type)
+        {
+        case XFCE_APPFINDER_ACTION_TYPE_PREFIX:
+          if (g_str_has_prefix (text, action->pattern))
+            {
+              cmd = xfce_appfinder_actions_expand_command (action, text);
+              result = XFCE_APPFINDER_ACTIONS_SUCCEED;
+            }
+          break;
+
+        case XFCE_APPFINDER_ACTION_TYPE_REGEX:
+          if (action->regex == NULL)
+            {
+              action->regex = g_regex_new (action->pattern, 0, 0, &err);
+              if (action->regex == NULL)
+                {
+                  g_message ("Failed to create regex for \"%s\": %s",
+                             action->pattern, err->message);
+                  g_error_free (err);
+
+                  break;
+                }
+            }
+
+          if (g_regex_match (action->regex, text, 0, &match_info))
+            {
+              /* expand the command string */
+              cmd = g_match_info_expand_references (match_info, action->command, &err);
+
+              if (G_UNLIKELY (err != NULL))
+                *error = err;
+            }
+
+          if (match_info != NULL)
+            g_match_info_free (match_info);
+
+          break;
+        }
+    }
+
+  if (result == XFCE_APPFINDER_ACTIONS_SUCCEED
+      && cmd != NULL
+      && *error == NULL)
+    {
+      /* also expand variables... */
+      expanded = xfce_expand_variables (cmd, NULL);
+      g_free (cmd);
+      cmd = expanded;
+
+      APPFINDER_DEBUG ("spawn command \"%s\"", cmd);
+
+      if (xfce_spawn_command_line_on_screen (screen, cmd, FALSE, FALSE, error))
+        result = XFCE_APPFINDER_ACTIONS_SUCCEED;
+      else
+        result = XFCE_APPFINDER_ACTIONS_ERROR;
+    }
+
+  g_free (cmd);
+
+  return result;
+}
diff --git a/src/appfinder-actions.h b/src/appfinder-actions.h
new file mode 100644
index 0000000..97ceb80
--- /dev/null
+++ b/src/appfinder-actions.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 Nick Schermer <nick at xfce.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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __XFCE_APPFINDER_ACTIONS_H__
+#define __XFCE_APPFINDER_ACTIONS_H__
+
+#include <gtk/gtk.h>
+#include <garcon/garcon.h>
+
+G_BEGIN_DECLS
+
+typedef struct _XfceAppfinderActionsClass XfceAppfinderActionsClass;
+typedef struct _XfceAppfinderActions      XfceAppfinderActions;
+typedef struct _XfceAppfinderAction       XfceAppfinderAction;
+
+#define XFCE_TYPE_APPFINDER_ACTIONS            (xfce_appfinder_actions_get_type ())
+#define XFCE_APPFINDER_ACTIONS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_TYPE_APPFINDER_ACTIONS, XfceAppfinderActions))
+#define XFCE_APPFINDER_ACTIONS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), XFCE_TYPE_APPFINDER_ACTIONS, XfceAppfinderActionsClass))
+#define XFCE_IS_APPFINDER_ACTIONS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCE_TYPE_APPFINDER_ACTIONS))
+#define XFCE_IS_APPFINDER_ACTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), XFCE_TYPE_APPFINDER_ACTIONS))
+#define XFCE_APPFINDER_ACTIONS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), XFCE_TYPE_APPFINDER_ACTIONS, XfceAppfinderActionsClass))
+
+typedef enum
+{
+  XFCE_APPFINDER_ACTIONS_SUCCEED, /* launched an action */
+  XFCE_APPFINDER_ACTIONS_NOTHING_FOUND, /* no suitable action found */
+  XFCE_APPFINDER_ACTIONS_ERROR /* action found, but spawn error */
+}
+XfceAppfinderActionsResult;
+
+GType                       xfce_appfinder_actions_get_type    (void) G_GNUC_CONST;
+
+XfceAppfinderActions       *xfce_appfinder_actions_get         (void) G_GNUC_MALLOC;
+
+XfceAppfinderActionsResult  xfce_appfinder_actions_execute     (XfceAppfinderActions  *actions,
+                                                                const gchar           *text,
+                                                                GdkScreen             *screen,
+                                                                GError               **error);
+
+G_END_DECLS
+
+#endif /* !__XFCE_APPFINDER_ACTIONS_H__ */
diff --git a/src/appfinder-preferences.c b/src/appfinder-preferences.c
index 026acfd..a277f0f 100644
--- a/src/appfinder-preferences.c
+++ b/src/appfinder-preferences.c
@@ -35,10 +35,20 @@
 
 
 
-static void xfce_appfinder_preferences_response      (GtkWidget                *window,
-                                                      gint                      response_id,
-                                                      XfceAppfinderPreferences *preferences);
-static void xfce_appfinder_preferences_clear_history (XfceAppfinderPreferences *preferences);
+static void xfce_appfinder_preferences_response          (GtkWidget                *window,
+                                                          gint                      response_id,
+                                                          XfceAppfinderPreferences *preferences);
+static void xfce_appfinder_preferences_clear_history     (XfceAppfinderPreferences *preferences);
+static void xfce_appfinder_preferences_action_add        (XfceAppfinderPreferences *preferences);
+static void xfce_appfinder_preferences_action_remove     (GtkWidget                *button,
+                                                          XfceAppfinderPreferences *preferences);
+static void xfce_appfinder_preferences_action_changed    (XfconfChannel            *channel,
+                                                          const gchar              *prop_name,
+                                                          const GValue             *value,
+                                                          XfceAppfinderPreferences *preferences);
+static void xfce_appfinder_preferences_action_populate   (XfceAppfinderPreferences *preferences);
+static void xfce_appfinder_preferences_selection_changed (GtkTreeSelection         *selection,
+                                                          XfceAppfinderPreferences *preferences);
 
 
 
@@ -51,7 +61,18 @@ struct _XfceAppfinderPreferences
 {
   GtkBuilder __parent__;
 
-  GObject  *dialog;
+  GObject       *dialog;
+
+  XfconfChannel *channel;
+
+  gulong         bindings[3];
+  gulong         property_watch_id;
+};
+
+enum
+{
+  COLUMN_PATTERN,
+  COLUMN_UNIQUE_ID
 };
 
 
@@ -70,8 +91,11 @@ xfce_appfinder_preferences_class_init (XfceAppfinderPreferencesClass *klass)
 static void
 xfce_appfinder_preferences_init (XfceAppfinderPreferences *preferences)
 {
-  XfconfChannel *channel;
-  GObject       *object;
+  GObject          *object;
+  GtkTreeSelection *selection;
+  GtkTreePath      *path;
+
+  preferences->channel = xfconf_channel_get ("xfce4-appfinder");
 
   /* load the builder data into the object */
   gtk_builder_add_from_string (GTK_BUILDER (preferences), appfinder_preferences_ui,
@@ -82,19 +106,41 @@ xfce_appfinder_preferences_init (XfceAppfinderPreferences *preferences)
   g_signal_connect (G_OBJECT (preferences->dialog), "response",
       G_CALLBACK (xfce_appfinder_preferences_response), preferences);
 
-  channel = xfconf_channel_get ("xfce4-appfinder");
-
   object = gtk_builder_get_object (GTK_BUILDER (preferences), "remember-category");
-  xfconf_g_property_bind (channel, "/RememberCategory", G_TYPE_BOOLEAN,
+  xfconf_g_property_bind (preferences->channel, "/remember-category", G_TYPE_BOOLEAN,
                           G_OBJECT (object), "active");
 
   object = gtk_builder_get_object (GTK_BUILDER (preferences), "always-center");
-  xfconf_g_property_bind (channel, "/AlwaysCenter", G_TYPE_BOOLEAN,
+  xfconf_g_property_bind (preferences->channel, "/always-center", G_TYPE_BOOLEAN,
                           G_OBJECT (object), "active");
 
   object = gtk_builder_get_object (GTK_BUILDER (preferences), "button-clear");
   g_signal_connect_swapped (G_OBJECT (object), "clicked",
       G_CALLBACK (xfce_appfinder_preferences_clear_history), preferences);
+
+  object = gtk_builder_get_object (GTK_BUILDER (preferences), "button-add");
+  g_signal_connect_swapped (G_OBJECT (object), "clicked",
+      G_CALLBACK (xfce_appfinder_preferences_action_add), preferences);
+
+  object = gtk_builder_get_object (GTK_BUILDER (preferences), "button-remove");
+  g_signal_connect (G_OBJECT (object), "clicked",
+      G_CALLBACK (xfce_appfinder_preferences_action_remove), preferences);
+
+  xfce_appfinder_preferences_action_populate (preferences);
+
+  object = gtk_builder_get_object (GTK_BUILDER (preferences), "actions-treeview");
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (object));
+  g_signal_connect (G_OBJECT (selection), "changed",
+      G_CALLBACK (xfce_appfinder_preferences_selection_changed), preferences);
+
+  path = gtk_tree_path_new_first ();
+  gtk_tree_selection_select_path (selection, path);
+  gtk_tree_path_free (path);
+
+  preferences->property_watch_id =
+    g_signal_connect (G_OBJECT (preferences->channel), "property-changed",
+        G_CALLBACK (xfce_appfinder_preferences_action_changed), preferences);
 }
 
 
@@ -107,7 +153,10 @@ xfce_appfinder_preferences_response (GtkWidget                *window,
   appfinder_return_if_fail (GTK_IS_DIALOG (window));
   appfinder_return_if_fail (XFCE_IS_APPFINDER_PREFERENCES (preferences));
 
+  g_signal_handler_disconnect (preferences->channel, preferences->property_watch_id);
+
   gtk_widget_destroy (window);
+
   g_object_unref (G_OBJECT (preferences));
 }
 
@@ -117,11 +166,10 @@ static void
 xfce_appfinder_preferences_clear_history (XfceAppfinderPreferences *preferences)
 {
   XfceAppfinderModel *model;
-  
+
   appfinder_return_if_fail (XFCE_IS_APPFINDER_PREFERENCES (preferences));
-  
-  if (xfce_dialog_confirm (GTK_WINDOW (preferences->dialog), GTK_STOCK_CLEAR,
-                           _("Clear Command History"),
+
+  if (xfce_dialog_confirm (GTK_WINDOW (preferences->dialog), GTK_STOCK_CLEAR, _("C_lear"),
                            _("This will permanently clear the custom command history."),
                            _("Are you sure you want to clear the command history?")))
     {
@@ -133,6 +181,208 @@ xfce_appfinder_preferences_clear_history (XfceAppfinderPreferences *preferences)
 
 
 
+static void
+xfce_appfinder_preferences_action_add (XfceAppfinderPreferences *preferences)
+{
+
+}
+
+
+
+static void
+xfce_appfinder_preferences_action_remove (GtkWidget                *button,
+                                          XfceAppfinderPreferences *preferences)
+{
+  GObject *object;
+
+  object = gtk_builder_get_object (GTK_BUILDER (preferences), "pattern");
+  if (xfce_dialog_confirm (GTK_WINDOW (gtk_widget_get_toplevel (button)),
+                           GTK_STOCK_DELETE, NULL,
+                           _("The custom action will be deleted permanently."),
+                           _("Are you sure you want to delete pattern \"%s\"?"),
+                           gtk_entry_get_text (GTK_ENTRY (object))))
+    {
+
+    }
+}
+
+
+
+typedef struct
+{
+  gint          unique_id;
+  const GValue *value;
+}
+UpdateContext;
+
+
+
+static gboolean
+xfce_appfinder_preferences_action_changed_func (GtkTreeModel *model,
+                                                GtkTreePath  *path,
+                                                GtkTreeIter  *iter,
+                                                gpointer      data)
+{
+  gint           unique_id;
+  UpdateContext *context = data;
+
+  gtk_tree_model_get (model, iter, COLUMN_UNIQUE_ID, &unique_id, -1);
+
+  if (context->unique_id == unique_id)
+    {
+      gtk_list_store_set (GTK_LIST_STORE (model), iter, COLUMN_PATTERN,
+                          g_value_get_string (context->value),
+                          -1);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+
+
+static void
+xfce_appfinder_preferences_action_changed (XfconfChannel            *channel,
+                                           const gchar              *prop_name,
+                                           const GValue             *value,
+                                           XfceAppfinderPreferences *preferences)
+{
+  gint           unique_id;
+  GObject       *store;
+  UpdateContext  context;
+
+  if (prop_name == NULL)
+    return;
+
+  if (strcmp (prop_name, "/actions") == 0)
+    {
+      xfce_appfinder_preferences_action_populate (preferences);
+    }
+  else if (sscanf (prop_name, "/actions/action-%d/pattern", &unique_id) == 1
+           && G_VALUE_HOLDS_STRING (value))
+    {
+      context.unique_id = unique_id;
+      context.value = value;
+
+      store = gtk_builder_get_object (GTK_BUILDER (preferences), "actions-store");
+      gtk_tree_model_foreach (GTK_TREE_MODEL (store),
+          xfce_appfinder_preferences_action_changed_func, &context);
+    }
+}
+
+
+static void
+xfce_appfinder_preferences_action_populate (XfceAppfinderPreferences *preferences)
+{
+  GPtrArray    *array;
+  GObject      *store;
+  const GValue *value;
+  gint          unique_id;
+  gchar         prop[32];
+  gchar        *pattern, *command;
+  guint         i;
+
+  store = gtk_builder_get_object (GTK_BUILDER (preferences), "actions-store");
+  appfinder_assert (GTK_IS_LIST_STORE (store));
+  gtk_list_store_clear (GTK_LIST_STORE (store));
+
+  array = xfconf_channel_get_arrayv (preferences->channel, "/actions");
+  if (G_LIKELY (array != NULL))
+    {
+      for (i = 0; i < array->len; i++)
+      {
+        value = g_ptr_array_index (array, i);
+        appfinder_assert (value != NULL);
+        unique_id = g_value_get_int (value);
+
+        g_snprintf (prop, sizeof (prop), "/actions/action-%d/pattern", unique_id);
+        pattern = xfconf_channel_get_string (preferences->channel, prop, NULL);
+
+        g_snprintf (prop, sizeof (prop), "/actions/action-%d/command", unique_id);
+        command = xfconf_channel_get_string (preferences->channel, prop, NULL);
+
+        gtk_list_store_insert_with_values (GTK_LIST_STORE (store), NULL, i,
+                                           COLUMN_UNIQUE_ID, unique_id,
+                                           COLUMN_PATTERN, pattern,
+                                           -1);
+
+        g_free (pattern);
+        g_free (command);
+      }
+
+      xfconf_array_free (array);
+    }
+}
+
+
+
+typedef struct
+{
+  const gchar *name;
+  const gchar *prop_name;
+  GType        prop_type;
+}
+dialog_object;
+
+
+
+static void
+xfce_appfinder_preferences_selection_changed (GtkTreeSelection         *selection,
+                                              XfceAppfinderPreferences *preferences)
+{
+  GtkTreeModel  *store;
+  GtkTreeIter    iter;
+  gint           unique_id;
+  GObject       *object;
+  guint          i;
+  gchar          prop[32];
+  dialog_object  objects[] =
+  {
+     { "type", "active", G_TYPE_INT },
+     { "pattern", "text", G_TYPE_STRING },
+     { "command", "text", G_TYPE_STRING }
+  };
+
+  appfinder_return_if_fail (GTK_IS_TREE_SELECTION (selection));
+  appfinder_return_if_fail (XFCE_IS_APPFINDER_PREFERENCES (preferences));
+  appfinder_return_if_fail (G_N_ELEMENTS (preferences->bindings) == G_N_ELEMENTS (objects));
+
+  /* drop old bindings */
+  for (i = 0; i < G_N_ELEMENTS (preferences->bindings); i++)
+    {
+      if (preferences->bindings[i] != 0)
+        {
+          xfconf_g_property_unbind (preferences->bindings[i]);
+          preferences->bindings[i] = 0;
+        }
+    }
+
+  if (gtk_tree_selection_get_selected (selection, &store, &iter))
+    gtk_tree_model_get (store, &iter, COLUMN_UNIQUE_ID, &unique_id, -1);
+  else
+    unique_id = -1;
+
+  for (i = 0; i < G_N_ELEMENTS (objects); i++)
+    {
+      object = gtk_builder_get_object (GTK_BUILDER (preferences), objects[i].name);
+      appfinder_return_if_fail (GTK_IS_WIDGET (object));
+      gtk_widget_set_sensitive (GTK_WIDGET (object), unique_id != -1);
+      if (unique_id > -1)
+        {
+          g_snprintf (prop, sizeof (prop), "/actions/action-%d/%s", unique_id, objects[i].name);
+          preferences->bindings[i] = xfconf_g_property_bind (preferences->channel, prop,
+                                                             objects[i].prop_type, object,
+                                                             objects[i].prop_name);
+        }
+    }
+
+  object = gtk_builder_get_object (GTK_BUILDER (preferences), "button-remove");
+  gtk_widget_set_sensitive (GTK_WIDGET (object), unique_id != -1);
+}
+
+
+
 void
 xfce_appfinder_preferences_show (GdkScreen *screen)
 {
diff --git a/src/appfinder-preferences.glade b/src/appfinder-preferences.glade
index a4dad9c..3eb3b02 100644
--- a/src/appfinder-preferences.glade
+++ b/src/appfinder-preferences.glade
@@ -3,14 +3,26 @@
   <requires lib="gtk+" version="2.20"/>
   <!-- interface-requires libxfce4ui 4.5 -->
   <!-- interface-naming-policy project-wide -->
-  <object class="GtkListStore" id="actions-store">
+  <object class="GtkListStore" id="action-types">
     <columns>
-      <!-- column-name icon-name -->
-      <column type="gchararray"/>
-      <!-- column-name prefix -->
+      <!-- column-name title -->
       <column type="gchararray"/>
-      <!-- column-name command -->
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes">Prefix</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Regular Expression</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkListStore" id="actions-store">
+    <columns>
+      <!-- column-name pattern -->
       <column type="gchararray"/>
+      <!-- column-name unique-id -->
+      <column type="gint"/>
     </columns>
   </object>
   <object class="XfceTitledDialog" id="dialog">
@@ -220,34 +232,12 @@
                             <property name="enable_search">False</property>
                             <property name="search_column">0</property>
                             <child>
-                              <object class="GtkTreeViewColumn" id="treeviewcolumn3">
-                                <property name="title" translatable="yes">Icon</property>
-                                <child>
-                                  <object class="GtkCellRendererPixbuf" id="cellrendererpixbuf1"/>
-                                  <attributes>
-                                    <attribute name="icon-name">0</attribute>
-                                  </attributes>
-                                </child>
-                              </object>
-                            </child>
-                            <child>
                               <object class="GtkTreeViewColumn" id="treeviewcolumn1">
-                                <property name="title" translatable="yes">Prefix</property>
+                                <property name="title" translatable="yes">Pattern</property>
                                 <child>
                                   <object class="GtkCellRendererText" id="cellrenderertext1"/>
                                   <attributes>
-                                    <attribute name="text">1</attribute>
-                                  </attributes>
-                                </child>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkTreeViewColumn" id="treeviewcolumn2">
-                                <property name="title" translatable="yes">Command</property>
-                                <child>
-                                  <object class="GtkCellRendererText" id="cellrenderertext2"/>
-                                  <attributes>
-                                    <attribute name="text">2</attribute>
+                                    <attribute name="text">0</attribute>
                                   </attributes>
                                 </child>
                               </object>
@@ -340,32 +330,40 @@
                     <property name="column_spacing">12</property>
                     <property name="row_spacing">6</property>
                     <child>
-                      <object class="GtkEntry" id="action-prefix">
+                      <object class="GtkEntry" id="command">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">•</property>
                         <property name="invisible_char_set">True</property>
                         <property name="primary_icon_activatable">False</property>
                         <property name="secondary_icon_activatable">False</property>
+                        <property name="tooltip_text" translatable="yes">If the type is set to prefix, %s will be replaced with the string after the pattern, %S with the complete entry text. For regular expressions you can use \0 and \<num>.</property>
                         <property name="primary_icon_sensitive">True</property>
                         <property name="secondary_icon_sensitive">True</property>
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
                         <property name="right_attach">2</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkLabel" id="label3">
+                      <object class="GtkEntry" id="pattern">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="xalign">0</property>
-                        <property name="label" translatable="yes">P_refix:</property>
-                        <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">action-prefix</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">•</property>
+                        <property name="invisible_char_set">True</property>
+                        <property name="primary_icon_activatable">False</property>
+                        <property name="secondary_icon_activatable">False</property>
+                        <property name="primary_icon_sensitive">True</property>
+                        <property name="secondary_icon_sensitive">True</property>
                       </object>
                       <packing>
-                        <property name="x_options">GTK_FILL</property>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
                       </packing>
                     </child>
                     <child>
@@ -375,63 +373,65 @@
                         <property name="xalign">0</property>
                         <property name="label" translatable="yes">Co_mmand:</property>
                         <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">action-cmd</property>
+                        <property name="mnemonic_widget">command</property>
                       </object>
                       <packing>
-                        <property name="top_attach">1</property>
-                        <property name="bottom_attach">2</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
                         <property name="x_options">GTK_FILL</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkLabel" id="label5">
+                      <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">_Icon:</property>
+                        <property name="label" translatable="yes">Patte_rn:</property>
                         <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">action-icon</property>
+                        <property name="mnemonic_widget">pattern</property>
                       </object>
                       <packing>
-                        <property name="top_attach">2</property>
-                        <property name="bottom_attach">3</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
                         <property name="x_options">GTK_FILL</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="action-cmd">
+                      <object class="GtkLabel" id="label8">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">•</property>
-                        <property name="invisible_char_set">True</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                        <property name="primary_icon_sensitive">True</property>
-                        <property name="secondary_icon_sensitive">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Type:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">type</property>
                       </object>
                       <packing>
-                        <property name="left_attach">1</property>
-                        <property name="right_attach">2</property>
-                        <property name="top_attach">1</property>
-                        <property name="bottom_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="action-icon">
+                      <object class="GtkAlignment" id="alignment4">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">•</property>
-                        <property name="invisible_char_set">True</property>
-                        <property name="primary_icon_activatable">False</property>
-                        <property name="secondary_icon_activatable">False</property>
-                        <property name="primary_icon_sensitive">True</property>
-                        <property name="secondary_icon_sensitive">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="xscale">0</property>
+                        <child>
+                          <object class="GtkComboBox" id="type">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="model">action-types</property>
+                            <child>
+                              <object class="GtkCellRendererText" id="cellrenderertext3"/>
+                              <attributes>
+                                <attribute name="text">0</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
                         <property name="right_attach">2</property>
-                        <property name="top_attach">2</property>
-                        <property name="bottom_attach">3</property>
                       </packing>
                     </child>
                   </object>
diff --git a/src/appfinder-window.c b/src/appfinder-window.c
index e78a3ac..5f50b73 100644
--- a/src/appfinder-window.c
+++ b/src/appfinder-window.c
@@ -33,6 +33,7 @@
 #include <src/appfinder-model.h>
 #include <src/appfinder-category-model.h>
 #include <src/appfinder-preferences.h>
+#include <src/appfinder-actions.h>
 #include <src/appfinder-private.h>
 
 
@@ -171,17 +172,17 @@ xfce_appfinder_window_init (XfceAppfinderWindow *window)
   gint                integer;
 
   window->channel = xfconf_channel_get ("xfce4-appfinder");
-  window->last_window_height = xfconf_channel_get_int (window->channel, "/LastWindowHeight", DEFAULT_WINDOW_HEIGHT);
+  window->last_window_height = xfconf_channel_get_int (window->channel, "/last/window-height", DEFAULT_WINDOW_HEIGHT);
 
   window->category_model = xfce_appfinder_category_model_new ();
   window->model = xfce_appfinder_model_get ();
 
   gtk_window_set_title (GTK_WINDOW (window), _("Application Finder"));
-  integer = xfconf_channel_get_int (window->channel, "/LastWindowWidth", DEFAULT_WINDOW_WIDTH);
+  integer = xfconf_channel_get_int (window->channel, "/last/window-width", DEFAULT_WINDOW_WIDTH);
   gtk_window_set_default_size (GTK_WINDOW (window), integer, -1);
   gtk_window_set_icon_name (GTK_WINDOW (window), GTK_STOCK_EXECUTE);
 
-  if (xfconf_channel_get_bool (window->channel, "/AlwaysCenter", FALSE))
+  if (xfconf_channel_get_bool (window->channel, "/always-center", FALSE))
     gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
 
   vbox = gtk_vbox_new (FALSE, 6);
@@ -233,7 +234,7 @@ xfce_appfinder_window_init (XfceAppfinderWindow *window)
 
   window->paned = pane = gtk_hpaned_new ();
   gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
-  integer = xfconf_channel_get_int (window->channel, "/LastPanedPosition", DEFAULT_PANED_POSITION);
+  integer = xfconf_channel_get_int (window->channel, "/last/pane-position", DEFAULT_PANED_POSITION);
   gtk_paned_set_position (GTK_PANED (pane), integer);
   g_object_set (G_OBJECT (pane), "position-set", TRUE, NULL);
 
@@ -399,9 +400,9 @@ xfce_appfinder_window_unmap (GtkWidget *widget)
 
   (*GTK_WIDGET_CLASS (xfce_appfinder_window_parent_class)->unmap) (widget);
 
-  xfconf_channel_set_int (window->channel, "/LastWindowHeight", height);
-  xfconf_channel_set_int (window->channel, "/LastWindowWidth", width);
-  xfconf_channel_set_int (window->channel, "/LastPanedPosition", position);
+  xfconf_channel_set_int (window->channel, "/last/window-height", height);
+  xfconf_channel_set_int (window->channel, "/last/window-width", width);
+  xfconf_channel_set_int (window->channel, "/last/pane-position", position);
 }
 
 
@@ -666,8 +667,8 @@ xfce_appfinder_window_category_changed (GtkTreeSelection    *selection,
           gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
 
           /* store last category */
-          if (xfconf_channel_get_bool (window->channel, "/RememberCategory", FALSE))
-            xfconf_channel_set_string (window->channel, "/LastCategory", name);
+          if (xfconf_channel_get_bool (window->channel, "/remember-category", FALSE))
+            xfconf_channel_set_string (window->channel, "/last/category", name);
         }
 
       g_free (name);
@@ -691,8 +692,8 @@ xfce_appfinder_window_category_set_categories (XfceAppfinderWindow *window)
   if (categories != NULL)
     xfce_appfinder_category_model_set_categories (window->category_model, categories);
 
-  if (xfconf_channel_get_bool (window->channel, "/RememberCategory", FALSE))
-    name = xfconf_channel_get_string (window->channel, "/LastCategory", NULL);
+  if (xfconf_channel_get_bool (window->channel, "/remember-category", FALSE))
+    name = xfconf_channel_get_string (window->channel, "/last/category", NULL);
   else
     name = NULL;
 
@@ -792,51 +793,17 @@ xfce_appfinder_window_execute_command (const gchar  *cmd,
                                        GdkScreen    *screen,
                                        GError      **error)
 {
-  gboolean          in_terminal;
-  gchar            *cmdline, *exo_open;
-  const gchar      *exo_open_prefix[] = { "file://", "http://", "https://" };
-  guint             i;
-  gboolean          result = FALSE;
+  XfceAppfinderActions *actions;
+  gboolean              succeed = FALSE;
 
-  if (g_str_has_prefix (cmd, "#"))
-    {
-      /* open manual page in the terminal */
-      cmdline = g_strconcat ("man ", cmd + 1, NULL);
-      in_terminal = TRUE;
-    }
-  else if (g_str_has_prefix (cmd, "$"))
-    {
-      /* open in the terminal */
-      cmdline = xfce_expand_variables (cmd + 1, NULL);
-      in_terminal = TRUE;
-    }
-  else
-    {
-      cmdline = xfce_expand_variables (cmd, NULL);
-      in_terminal = FALSE;
-    }
-
-  result = xfce_spawn_command_line_on_screen (screen, cmdline, in_terminal, FALSE, error);
-  if (!result)
-    {
-      /* TODO instead check the exo exit code */
-      /* check if this is something exo-open can handle */
-      for (i = 0; !result && i < G_N_ELEMENTS (exo_open_prefix); i++)
-        if (g_str_has_prefix (cmdline, exo_open_prefix[i]))
-          result = TRUE;
+  actions = xfce_appfinder_actions_get ();
 
-      if (result)
-        {
-          /* try to spawn again */
-          exo_open = g_strconcat ("exo-open ", cmdline, NULL);
-          result = xfce_spawn_command_line_on_screen (screen, exo_open, FALSE, FALSE, error);
-          g_free (exo_open);
-        }
-    }
+  if (xfce_appfinder_actions_execute (actions, cmd, screen, error) == XFCE_APPFINDER_ACTIONS_SUCCEED)
+    succeed = TRUE;
 
-  g_free (cmdline);
+  g_object_unref (G_OBJECT (actions));
 
-  return result;
+  return succeed;
 }
 
 


More information about the Xfce4-commits mailing list