[gimp/tito: 2/3] Bug 708174 - Improve the original search dialog patch.
- From: Jehan Pagès <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/tito: 2/3] Bug 708174 - Improve the original search dialog patch.
- Date: Wed, 29 Jan 2014 23:00:05 +0000 (UTC)
commit b63f0592531769659bfab64804abee7a4d5c1ba4
Author: Jehan <jehan girinstud io>
Date: Thu Sep 26 06:02:59 2013 +1200
Bug 708174 - Improve the original search dialog patch.
Fix various bugs, improve code design and efficiency, change feature
name, update the feature up to our standards (now uses GIMP preferences,
session management, less overwhelming settings...).
Also now action history is tightly tied to GimpAction and logs all
action activation (however it activates, and the show_unavailable
parameter also applies to history).
Search algorithm greatly improved with basic tokenization, better
ordering, filtering, etc.
app/actions/dialogs-actions.c | 8 +-
app/actions/help-actions.c | 8 +-
app/actions/help-commands.c | 16 +
app/actions/help-commands.h | 10 +-
app/config/gimpguiconfig.c | 23 +
app/config/gimpguiconfig.h | 5 +
app/config/gimprc-blurbs.h | 6 +
app/dialogs/action-search-dialog.c | 1381 +++++++++++++++---------------------
app/dialogs/action-search-dialog.h | 3 +-
app/dialogs/dialogs-constructors.c | 18 +-
app/dialogs/dialogs-constructors.h | 8 +-
app/dialogs/dialogs.c | 4 +-
app/dialogs/preferences-dialog.c | 29 +-
app/gui/gui.c | 3 +
app/widgets/Makefile.am | 2 +
app/widgets/gimpaction-history.c | 374 ++++++++++
app/widgets/gimpaction-history.h | 42 ++
app/widgets/gimpaction.c | 13 +
menus/image-menu.xml.in | 2 +-
19 files changed, 1116 insertions(+), 839 deletions(-)
---
diff --git a/app/actions/dialogs-actions.c b/app/actions/dialogs-actions.c
index 7358256..c7686f8 100644
--- a/app/actions/dialogs-actions.c
+++ b/app/actions/dialogs-actions.c
@@ -267,13 +267,7 @@ static const GimpStringActionEntry dialogs_toplevel_actions[] =
NULL,
NC_("dialogs-action", "About GIMP"),
"gimp-about-dialog",
- GIMP_HELP_ABOUT_DIALOG },
-
- { "dialogs-action-search", GTK_STOCK_FIND,
- NC_("dialogs-action", "_Search and Run a Command"), NULL,
- NC_("dialogs-action", "Search commands by keyword, and run them"),
- "gimp-action-search-dialog",
- GIMP_HELP_ACTION_SEARCH_DIALOG }
+ GIMP_HELP_ABOUT_DIALOG }
};
diff --git a/app/actions/help-actions.c b/app/actions/help-actions.c
index fecce13..f76330d 100644
--- a/app/actions/help-actions.c
+++ b/app/actions/help-actions.c
@@ -47,7 +47,13 @@ static const GimpActionEntry help_actions[] =
NC_("help-action", "_Context Help"), "<shift>F1",
NC_("help-action", "Show the help for a specific user interface item"),
G_CALLBACK (help_context_help_cmd_callback),
- GIMP_HELP_HELP_CONTEXT }
+ GIMP_HELP_HELP_CONTEXT },
+
+ { "help-action-search", GTK_STOCK_FIND,
+ NC_("help-action", "_Search and Run a Command"), "slash",
+ NC_("help-action", "Search commands by keyword, and run them"),
+ G_CALLBACK (help_search_actions_cmd_callback),
+ GIMP_HELP_ACTION_SEARCH_DIALOG }
};
diff --git a/app/actions/help-commands.c b/app/actions/help-commands.c
index 82d8a2f..47cf8b6 100644
--- a/app/actions/help-commands.c
+++ b/app/actions/help-commands.c
@@ -26,6 +26,7 @@
#include "core/gimpprogress.h"
+#include "widgets/gimpdialogfactory.h"
#include "widgets/gimphelp.h"
#include "actions.h"
@@ -53,3 +54,18 @@ help_context_help_cmd_callback (GtkAction *action,
gimp_context_help (widget);
}
+
+void
+help_search_actions_cmd_callback (GtkAction *action,
+ gpointer data)
+{
+ Gimp *gimp;
+ GtkWidget *widget;
+ return_if_no_widget (widget, data);
+ return_if_no_gimp (gimp, data);
+
+ gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
+ gtk_widget_get_screen (widget),
+ NULL,
+ "gimp-action-search-dialog", -1, TRUE);
+}
diff --git a/app/actions/help-commands.h b/app/actions/help-commands.h
index 7d94206..c7507e1 100644
--- a/app/actions/help-commands.h
+++ b/app/actions/help-commands.h
@@ -19,10 +19,12 @@
#define __HELP_COMMANDS_H__
-void help_help_cmd_callback (GtkAction *action,
- gpointer data);
-void help_context_help_cmd_callback (GtkAction *action,
- gpointer data);
+void help_help_cmd_callback (GtkAction *action,
+ gpointer data);
+void help_context_help_cmd_callback (GtkAction *action,
+ gpointer data);
+void help_search_actions_cmd_callback (GtkAction *action,
+ gpointer data);
#endif /* __HELP_COMMANDS_H__ */
diff --git a/app/config/gimpguiconfig.c b/app/config/gimpguiconfig.c
index ff5dff7..5fd934a 100644
--- a/app/config/gimpguiconfig.c
+++ b/app/config/gimpguiconfig.c
@@ -67,6 +67,8 @@ enum
PROP_SHOW_HELP_BUTTON,
PROP_HELP_LOCALES,
PROP_HELP_BROWSER,
+ PROP_SEARCH_SHOW_UNAVAILABLE_ACTIONS,
+ PROP_ACTION_HISTORY_SIZE,
PROP_USER_MANUAL_ONLINE,
PROP_USER_MANUAL_ONLINE_URI,
PROP_DOCK_WINDOW_HINT,
@@ -228,6 +230,15 @@ gimp_gui_config_class_init (GimpGuiConfigClass *klass)
GIMP_TYPE_HELP_BROWSER_TYPE,
DEFAULT_HELP_BROWSER,
GIMP_PARAM_STATIC_STRINGS);
+ /* As a default, we hide unavailable actions. */
+ GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_SEARCH_SHOW_UNAVAILABLE_ACTIONS,
+ "search-show-unavailable-actions", SEARCH_SHOW_UNAVAILABLE_BLURB,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_INSTALL_PROP_INT (object_class, PROP_ACTION_HISTORY_SIZE,
+ "action-history-size", ACTION_HISTORY_SIZE_BLURB,
+ 0, 1000, 100,
+ GIMP_PARAM_STATIC_STRINGS);
GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_USER_MANUAL_ONLINE,
"user-manual-online",
USER_MANUAL_ONLINE_BLURB,
@@ -432,6 +443,12 @@ gimp_gui_config_set_property (GObject *object,
case PROP_HELP_BROWSER:
gui_config->help_browser = g_value_get_enum (value);
break;
+ case PROP_SEARCH_SHOW_UNAVAILABLE_ACTIONS:
+ gui_config->search_show_unavailable = g_value_get_boolean (value);
+ break;
+ case PROP_ACTION_HISTORY_SIZE:
+ gui_config->action_history_size = g_value_get_int (value);
+ break;
case PROP_USER_MANUAL_ONLINE:
gui_config->user_manual_online = g_value_get_boolean (value);
break;
@@ -558,6 +575,12 @@ gimp_gui_config_get_property (GObject *object,
case PROP_HELP_BROWSER:
g_value_set_enum (value, gui_config->help_browser);
break;
+ case PROP_SEARCH_SHOW_UNAVAILABLE_ACTIONS:
+ g_value_set_boolean (value, gui_config->search_show_unavailable);
+ break;
+ case PROP_ACTION_HISTORY_SIZE:
+ g_value_set_int (value, gui_config->action_history_size);
+ break;
case PROP_USER_MANUAL_ONLINE:
g_value_set_boolean (value, gui_config->user_manual_online);
break;
diff --git a/app/config/gimpguiconfig.h b/app/config/gimpguiconfig.h
index e7eed9f..6cbe87a 100644
--- a/app/config/gimpguiconfig.h
+++ b/app/config/gimpguiconfig.h
@@ -61,6 +61,11 @@ struct _GimpGuiConfig
gboolean show_help_button;
gchar *help_locales;
GimpHelpBrowserType help_browser;
+
+ /* Action Search preferences. */
+ gboolean search_show_unavailable;
+ gint action_history_size;
+
gchar *web_browser;
gboolean user_manual_online;
gchar *user_manual_online_uri;
diff --git a/app/config/gimprc-blurbs.h b/app/config/gimprc-blurbs.h
index 6c2acd0..093a6e2 100644
--- a/app/config/gimprc-blurbs.h
+++ b/app/config/gimprc-blurbs.h
@@ -474,4 +474,10 @@ N_("When enabled, uses OpenCL for some operations.")
"Bugs in event history buffer are frequent so in case of cursor " \
"offset problems turning it off helps."
+#define SEARCH_SHOW_UNAVAILABLE_BLURB \
+"When enabled, a search of actions will also return inactive actions."
+
+#define ACTION_HISTORY_SIZE_BLURB \
+"The maximum number of actions saved in history."
+
#endif /* __GIMP_RC_BLURBS_H__ */
diff --git a/app/dialogs/action-search-dialog.c b/app/dialogs/action-search-dialog.c
index 9c0f1b1..b12629b 100644
--- a/app/dialogs/action-search-dialog.c
+++ b/app/dialogs/action-search-dialog.c
@@ -18,244 +18,277 @@
#include "config.h"
#include <ctype.h>
-#include <stdlib.h>
-
-#include <glib/gstdio.h>
+#include <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "libgimpbase/gimpbase.h"
+#include "libgimpwidgets/gimpwidgets.h"
#include "dialogs-types.h"
-#include "widgets/gimpuimanager.h"
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+
#include "widgets/gimpaction.h"
+#include "widgets/gimpaction-history.h"
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpsessioninfo.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpuimanager.h"
#include "action-search-dialog.h"
#include "gimp-intl.h"
-#define MAX_HISTORY_ACTIONS 20
-#define DEFAULT_HEIGHT 1
-
-gboolean action_search_run_result_action (void);
-static GtkWidget * action_search_setup_results_list (void);
-static gboolean action_search_search_dialog (void);
-static gboolean action_search_is_action_match (GtkAction *action,
- const gchar* keyword);
-static void action_search_add_to_results_list (const gchar *label,
- const gchar *tooltip,
- GtkAction* action);
-static void action_search_search_history_and_actions (const gchar *keyword);
-
-static void action_search_update_history (GtkAction *action);
-static void action_search_read_history (void);
-static void action_search_fill_history (void);
-static void action_search_clear_history (void);
-
-static void action_search_preferences_dialog (void);
-static void action_search_set_default_preferences (void);
-static void action_search_update_preferences (void);
-static void action_search_write_preferences (void);
-static void action_search_read_preferences (void);
-static void action_search_set_prefereces_ui_values (void);
-
-gboolean action_search_initializer (void);
-void action_search_finalizer (void);
-static void action_search_context_menu (void);
-
-static GtkWidget * action_search_dialog;
-static GtkWidget * results_list;
-static GtkWidget * keyword_entry;
-
-static gchar *history_file_path;
-static gchar *preference_file_path;
-static gint cur_no_of_his_actions;
-static gboolean first_time = TRUE;
-static gint tmp_x, tmp_y;
-static gint par_x, par_y;
-static gint par_height, par_width;
-
-enum RES_COL {
+typedef struct
+{
+ GtkWidget *dialog;
+
+ GimpGuiConfig *config;
+ GtkWidget *keyword_entry;
+ GtkWidget *results_list;
+ GtkWidget *list_view;
+
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+} SearchDialog;
+
+static void key_released (GtkWidget *widget,
+ GdkEventKey *event,
+ SearchDialog *private);
+static gboolean result_selected (GtkWidget *widget,
+ GdkEventKey *pKey,
+ SearchDialog *private);
+static void row_activated (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ SearchDialog *private);
+static gboolean action_search_view_accel_find_func (GtkAccelKey *key,
+ GClosure *closure,
+ gpointer data);
+static gchar* action_search_find_accel_label (GtkAction *action);
+static void action_search_add_to_results_list (GtkAction *action,
+ SearchDialog *private,
+ gint section);
+static void action_search_run_selected (SearchDialog *private);
+static void action_search_history_and_actions (const gchar *keyword,
+ SearchDialog *private);
+static gboolean action_fuzzy_match (gchar *string,
+ gchar *key);
+static gchar * action_search_normalize_string (const gchar *str);
+static gboolean action_search_match_keyword (GtkAction *action,
+ const gchar* keyword,
+ gint *section,
+ gboolean match_fuzzy);
+
+static void action_search_finalizer (SearchDialog *private);
+
+static gboolean window_configured (GtkWindow *window,
+ GdkEvent *event,
+ SearchDialog *private);
+static void window_shown (GtkWidget *widget,
+ SearchDialog *private);
+static void action_search_setup_results_list (GtkWidget **results_list,
+ GtkWidget **list_view);
+static void search_dialog_free (SearchDialog *private);
+
+enum ResultColumns {
RESULT_ICON,
RESULT_DATA,
RESULT_ACTION,
IS_SENSITIVE,
+ RESULT_SECTION,
N_COL
};
-static struct HISTORY {
- GtkAction *history_action;
- gint count;
-} history[MAX_HISTORY_ACTIONS];
-
-static struct HISTORY_ACTION_NAME {
- char *action_name;
- gint no;
-} name[MAX_HISTORY_ACTIONS];
-
-static struct PREFERENCES {
- gint POSITION;
- gfloat POSITION_X;
- gfloat POSITION_Y;
- gint NO_OF_RESULTS;
- gfloat WIDTH;
- gboolean SHOW_INSENSITIVE;
- gdouble OPACITY;
-} PREF;
-
-static struct ACTION_SEARCH_PREF_UI {
- GtkWidget *specify_radio;
- GtkWidget *pos_x_hbox;
- GtkWidget *pos_y_hbox;
- GtkWidget *right_top_radio;
- GtkWidget *middle_radio;
- GtkWidget *pos_x_spin_button;
- GtkWidget *pos_y_spin_button;
- GtkWidget *no_of_results_spin_button;
- GtkWidget *width_spin_button;
- GtkWidget *opacity_spin_button;
- GtkWidget *show_insensitive_check_button;
-} PREF_UI;
+/* Public Functions */
GtkWidget *
-action_search_dialog_create (void)
+action_search_dialog_create (Gimp *gimp)
{
- if (! action_search_initializer ())
- g_message ("Tito action_search_initializer failed");
+ static SearchDialog *private = NULL;
+ GimpSessionInfo *session_info = NULL;
+ GdkScreen *screen = gdk_screen_get_default ();
+ gint screen_width = gdk_screen_get_width (screen);
+ gint screen_height = gdk_screen_get_height (screen);
+ GdkWindow *par_window = gdk_screen_get_active_window (screen);
+ gint parent_height, parent_width;
+ gint parent_x, parent_y;
- action_search_search_dialog ();
- return action_search_dialog;
-}
+ gdk_window_get_geometry (par_window, &parent_x, &parent_y, &parent_width, &parent_height, NULL);
-static void
-modify_position_spins (void)
-{
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (PREF_UI.specify_radio)))
- {
- gtk_widget_set_sensitive (PREF_UI.pos_x_hbox, TRUE);
- gtk_widget_set_sensitive (PREF_UI.pos_y_hbox, TRUE);
- }
- else
+ if (! private)
{
- gtk_widget_set_sensitive (PREF_UI.pos_x_hbox, FALSE);
- gtk_widget_set_sensitive (PREF_UI.pos_y_hbox, FALSE);
+ GtkWidget *action_search_dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (gimp->config);
+ GtkWidget *main_vbox, *main_hbox;
+
+ private = g_slice_new0 (SearchDialog);
+ g_object_weak_ref (G_OBJECT (action_search_dialog),
+ (GWeakNotify) search_dialog_free, private);
+
+ private->dialog = action_search_dialog;
+ private->config = config;
+
+ gtk_window_set_role (GTK_WINDOW (action_search_dialog), "gimp-action-search-dialog");
+ gtk_window_set_title (GTK_WINDOW (action_search_dialog), _("Search Actions"));
+
+ main_vbox = gtk_vbox_new (FALSE, 2);
+ gtk_container_add (GTK_CONTAINER (action_search_dialog), main_vbox);
+ gtk_widget_show (main_vbox);
+
+ main_hbox = gtk_hbox_new (FALSE, 2);
+ gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, FALSE, TRUE, 0);
+ gtk_widget_show (main_hbox);
+
+ private->keyword_entry = gtk_entry_new ();
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (private->keyword_entry), GTK_ENTRY_ICON_PRIMARY,
GTK_STOCK_FIND);
+ gtk_widget_show (private->keyword_entry);
+ gtk_box_pack_start (GTK_BOX (main_hbox), private->keyword_entry, TRUE, TRUE, 0);
+
+ action_search_setup_results_list (&private->results_list, &private->list_view);
+ gtk_box_pack_start (GTK_BOX (main_vbox), private->list_view, TRUE, TRUE, 0);
+
+ gtk_widget_set_events (private->dialog,
+ GDK_KEY_RELEASE_MASK | GDK_KEY_PRESS_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_SCROLL_MASK);
+
+ g_signal_connect (private->results_list, "row-activated", (GCallback) row_activated, private);
+ g_signal_connect (private->keyword_entry, "key-release-event", G_CALLBACK (key_released), private);
+ g_signal_connect (private->results_list, "key_press_event", G_CALLBACK (result_selected), private);
+ g_signal_connect (private->dialog, "event", G_CALLBACK (window_configured), private);
+ g_signal_connect (private->dialog, "show", G_CALLBACK (window_shown), private);
+ g_signal_connect (private->dialog, "delete_event", G_CALLBACK (gtk_widget_hide_on_delete), NULL);
}
- gtk_spin_button_set_value (GTK_SPIN_BUTTON (PREF_UI.pos_x_spin_button),
- (gdouble) (PREF.POSITION_X/par_width * 100));
- gtk_spin_button_set_value (GTK_SPIN_BUTTON (PREF_UI.pos_y_spin_button),
- (gdouble) (PREF.POSITION_Y/par_height * 100));
- gtk_spin_button_set_range (GTK_SPIN_BUTTON (PREF_UI.pos_x_spin_button),
- (gdouble) 0,
- (gdouble) (100 - gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
(PREF_UI.width_spin_button))));
-}
-static void
-action_search_clear_history_button_clicked (GtkButton *button,
- gpointer user_data)
-{
- action_search_clear_history ();
-}
+ /* Move the window to the previous session's position using session management. */
+ private->x = -1;
+ private->y = -1;
+ private->width = -1;
+ /* Height is the only value not reused since it is too variable because of the result list. */
+ private->height = parent_height / 2;
-static void
-restore_defaults_button_clicked (GtkButton *button,
- gpointer user_data)
-{
- action_search_set_default_preferences ();
- action_search_set_prefereces_ui_values ();
-}
+ session_info =
+ gimp_dialog_factory_find_session_info (gimp_dialog_factory_get_singleton(),
+ "gimp-action-search-dialog");
-static void
-action_search_set_prefereces_ui_values (void)
-{
- if (PREF.POSITION == 0)
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (PREF_UI.right_top_radio), TRUE);
- else if (PREF.POSITION == 1)
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (PREF_UI.middle_radio), TRUE);
- else
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (PREF_UI.specify_radio), TRUE);
+ if (session_info)
+ {
+ private->x = gimp_session_info_get_x (session_info);
+ private->y = gimp_session_info_get_y (session_info);
+ private->width = gimp_session_info_get_width (session_info);
+ }
- modify_position_spins ();
+ if (private->width < 0)
+ {
+ private->width = parent_width / 2;
+ }
+ else if (private->width > screen_width)
+ {
+ private->width = parent_width;
+ }
- gtk_spin_button_set_value (GTK_SPIN_BUTTON (PREF_UI.no_of_results_spin_button), (gdouble)
PREF.NO_OF_RESULTS);
- gtk_spin_button_set_value (GTK_SPIN_BUTTON (PREF_UI.width_spin_button), (gdouble) PREF.WIDTH);
- gtk_spin_button_set_value (GTK_SPIN_BUTTON (PREF_UI.opacity_spin_button), (gdouble) PREF.OPACITY * 100);
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (PREF_UI.show_insensitive_check_button),
PREF.SHOW_INSENSITIVE);
+ if (private->x < 0 || private->x + private->width > screen_width)
+ {
+ private->x = parent_x + (parent_width - private->width) / 2;
+ }
-}
+ if (private->y < 0 || private->y + private->height > screen_height)
+ {
+ private->y = parent_y + (parent_height - private->height) / 2 ;
+ }
-static gboolean
-on_focus_out (GtkWidget *widget,
- GdkEventFocus *event,
- gpointer data)
-{
- if (! gtk_widget_is_focus (GTK_WIDGET (data)))
- action_search_finalizer ();
+ gtk_window_set_default_size (GTK_WINDOW (private->dialog), private->width, 1);
+ gtk_widget_show (private->dialog);
- return TRUE;
+ return private->dialog;
}
-
+/* Private Functions */
static void
-key_released (GtkWidget *widget,
- GdkEventKey *event,
- gpointer func_data)
+key_released (GtkWidget *widget,
+ GdkEventKey *event,
+ SearchDialog *private)
{
- const gchar *entry_text;
- GtkWidget *list_view = GTK_WIDGET (func_data);
+ gchar *entry_text;
+ gint width;
- entry_text = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1);
+ gtk_window_get_size (GTK_WINDOW (private->dialog), &width, NULL);
+ entry_text = g_strstrip (gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1));
switch (event->keyval)
- {
- case GDK_Escape:
- {
- action_search_finalizer ();
- return;
- }
- case GDK_Return:
{
- action_search_run_result_action ();
- return;
+ case GDK_Escape:
+ {
+ action_search_finalizer (private);
+ return;
+ }
+ case GDK_Return:
+ {
+ action_search_run_selected (private);
+ return;
+ }
}
- }
if (strcmp (entry_text, "") != 0)
{
- gtk_window_resize (GTK_WINDOW (action_search_dialog), (PREF.WIDTH * par_width) / 100,
- PREF.NO_OF_RESULTS * 40 + 100);
- gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (results_list))));
- gtk_widget_show_all (list_view);
- action_search_search_history_and_actions (entry_text);
- gtk_tree_selection_select_path (gtk_tree_view_get_selection (GTK_TREE_VIEW (results_list)),
+ gtk_window_resize (GTK_WINDOW (private->dialog), width,
+ private->height);
+ gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW
(private->results_list))));
+ gtk_widget_show_all (private->list_view);
+ action_search_history_and_actions (entry_text, private);
+ gtk_tree_selection_select_path (gtk_tree_view_get_selection (GTK_TREE_VIEW (private->results_list)),
gtk_tree_path_new_from_string ("0"));
}
else if (strcmp (entry_text, "") == 0 && (event->keyval == GDK_Down) )
{
- gtk_window_resize (GTK_WINDOW (action_search_dialog), (PREF.WIDTH * par_width) / 100,
- PREF.NO_OF_RESULTS * 40 + 100);
- gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (results_list))));
- gtk_widget_show_all (list_view);
- action_search_search_history_and_actions (" ");
- gtk_tree_selection_select_path (gtk_tree_view_get_selection (GTK_TREE_VIEW (results_list)),
+ gtk_window_resize (GTK_WINDOW (private->dialog), width,
+ private->height);
+ gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW
(private->results_list))));
+ gtk_widget_show_all (private->list_view);
+ action_search_history_and_actions (NULL, private);
+ gtk_tree_selection_select_path (gtk_tree_view_get_selection (GTK_TREE_VIEW (private->results_list)),
gtk_tree_path_new_from_string ("0"));
}
else
{
- gtk_widget_hide (list_view);
- gtk_window_resize (GTK_WINDOW (action_search_dialog),
- (PREF.WIDTH * par_width) / 100,
- DEFAULT_HEIGHT);
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->results_list));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_selection_unselect_path (selection, path);
+
+ gtk_tree_path_free (path);
+ }
+
+ gtk_widget_hide (private->list_view);
+ gtk_window_resize (GTK_WINDOW (private->dialog),
+ width, 1);
}
+
+ g_free (entry_text);
}
static gboolean
-result_selected (GtkWidget *widget,
- GdkEventKey *pKey,
- gpointer func_data)
+result_selected (GtkWidget *widget,
+ GdkEventKey *pKey,
+ SearchDialog *private)
{
if (pKey->type == GDK_KEY_PRESS)
{
@@ -263,22 +296,23 @@ result_selected (GtkWidget *widget,
{
case GDK_Return:
{
- action_search_run_result_action ();
+ action_search_run_selected (private);
break;
}
case GDK_Escape:
{
- action_search_finalizer ();
+ action_search_finalizer (private);
return TRUE;
}
case GDK_Up:
{
+ gboolean event_processed = FALSE;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
- selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (results_list));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->results_list));
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
if (gtk_tree_selection_get_selected (selection, &model, &iter))
@@ -287,36 +321,59 @@ result_selected (GtkWidget *widget,
if (strcmp (gtk_tree_path_to_string (path), "0") == 0)
{
- gtk_widget_grab_focus ((GTK_WIDGET (keyword_entry)));
- return TRUE;
+ gint start_pos;
+ gint end_pos;
+
+ gtk_editable_get_selection_bounds (GTK_EDITABLE (private->keyword_entry), &start_pos,
&end_pos);
+ gtk_widget_grab_focus ((GTK_WIDGET (private->keyword_entry)));
+ gtk_editable_select_region (GTK_EDITABLE (private->keyword_entry), start_pos, end_pos);
+
+ event_processed = TRUE;
}
+ gtk_tree_path_free (path);
}
+
+ return event_processed;
+ }
+ case GDK_Down:
+ {
+ return FALSE;
+ }
+ default:
+ {
+ gint start_pos;
+ gint end_pos;
+
+ gtk_editable_get_selection_bounds (GTK_EDITABLE (private->keyword_entry), &start_pos,
&end_pos);
+ gtk_widget_grab_focus ((GTK_WIDGET (private->keyword_entry)));
+ gtk_editable_select_region (GTK_EDITABLE (private->keyword_entry), start_pos, end_pos);
+ gtk_widget_event (GTK_WIDGET (private->keyword_entry), (GdkEvent*) pKey);
}
}
}
+
return FALSE;
}
-
static void
row_activated (GtkTreeView *treeview,
GtkTreePath *path,
GtkTreeViewColumn *col,
- gpointer userdata)
+ SearchDialog *private)
{
- action_search_run_result_action ();
+ action_search_run_selected (private);
}
static gboolean
-action_search_action_view_accel_find_func (GtkAccelKey *key,
- GClosure *closure,
- gpointer data)
+action_search_view_accel_find_func (GtkAccelKey *key,
+ GClosure *closure,
+ gpointer data)
{
return (GClosure *) data == closure;
}
static gchar*
-find_accel_label (GtkAction *action)
+action_search_find_accel_label (GtkAction *action)
{
guint accel_key = 0;
GdkModifierType accel_mask = 0;
@@ -332,8 +389,9 @@ find_accel_label (GtkAction *action)
if (accel_closure)
{
GtkAccelKey *key;
+
key = gtk_accel_group_find (accel_group,
- action_search_action_view_accel_find_func,
+ action_search_view_accel_find_func,
accel_closure);
if (key &&
key->accel_key &&
@@ -346,27 +404,44 @@ find_accel_label (GtkAction *action)
accel_string = gtk_accelerator_get_label (accel_key, accel_mask);
- return (strcmp (accel_string, "") == 0)? accel_string : NULL;
-}
+ if (strcmp (g_strstrip (accel_string), "") == 0)
+ {
+ /* The value returned by gtk_accelerator_get_label() must be freed after use. */
+ g_free (accel_string);
+ accel_string = NULL;
+ }
+ return accel_string;
+}
static void
-action_search_add_to_results_list (const gchar *label,
- const gchar *tooltip,
- GtkAction *action)
+action_search_add_to_results_list (GtkAction *action,
+ SearchDialog *private,
+ gint section)
{
GtkTreeIter iter;
+ GtkTreeIter next_section;
GtkListStore *store;
+ GtkTreeModel *model;
gchar *markuptxt;
- gchar *accel_string = find_accel_label (action);
- const gchar *stock_id = gtk_action_get_stock_id (action);
- char *data = g_new (char, 1024);
- char shortcut[1024] = "";
-
- if (data == NULL ||
- strchr (label, '@') != NULL ||
- strchr (label, '&') != NULL)
- return;
+ gchar *label;
+ gchar *escaped_label = NULL;
+ const gchar *stock_id;
+ gchar *accel_string;
+ gchar *escaped_accel = NULL;
+ gboolean has_shortcut = FALSE;
+ const gchar *tooltip;
+ gchar *escaped_tooltip = NULL;
+ gboolean has_tooltip = FALSE;
+
+ label = g_strstrip (gimp_strip_uline (gtk_action_get_label (action)));
+
+ if (! label || strlen (label) == 0)
+ {
+ g_free (label);
+ return;
+ }
+ escaped_label = g_markup_escape_text (label, -1);
if (GTK_IS_TOGGLE_ACTION (action))
{
@@ -375,49 +450,85 @@ action_search_add_to_results_list (const gchar *label,
else
stock_id = GTK_STOCK_NO;
}
-
- if (accel_string == NULL)
- strcpy (shortcut, "");
- else if (strchr (accel_string, '<') != NULL)
- strcpy (shortcut, "");
else
{
- strcpy (shortcut, " | ");
- strcat (shortcut, accel_string);
+ stock_id = gtk_action_get_stock_id (action);
}
- if (tooltip == NULL)
- strcpy (data, "");
- else if (strchr (tooltip, '<') != NULL)
- strcpy (data, "");
+ accel_string = action_search_find_accel_label (action);
+ if (accel_string != NULL)
+ {
+ escaped_accel = g_markup_escape_text (accel_string, -1);
+ has_shortcut = TRUE;
+ }
+
+ tooltip = gtk_action_get_tooltip (action);
+ if (tooltip != NULL)
+ {
+ escaped_tooltip = g_markup_escape_text (tooltip, -1);
+ has_tooltip = TRUE;
+ }
+
+ markuptxt = g_strdup_printf ("%s<small>%s%s%s<span weight='light'>%s</span></small>",
+ escaped_label,
+ has_shortcut ? " | " : "",
+ has_shortcut ? escaped_accel : "",
+ has_tooltip ? "\n" : "",
+ has_tooltip ? escaped_tooltip : "");
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (private->results_list));
+ store = GTK_LIST_STORE (model);
+ if (gtk_tree_model_get_iter_first (model, &next_section))
+ {
+ while (TRUE)
+ {
+ gint iter_section;
+
+ gtk_tree_model_get (model, &next_section,
+ RESULT_SECTION, &iter_section, -1);
+ if (iter_section > section)
+ {
+ gtk_list_store_insert_before (store, &iter, &next_section);
+ break;
+ }
+ else if (! gtk_tree_model_iter_next (model, &next_section))
+ {
+ gtk_list_store_append (store, &iter);
+ break;
+ }
+ }
+ }
else
{
- strcpy (data, "\n");
- strcat (data, tooltip);
+ gtk_list_store_append (store, &iter);
}
- markuptxt = g_strdup_printf ("%s<small>%s<span weight='light'>%s</span></small>",
- label, shortcut, data);
- store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (results_list)));
- gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter,
RESULT_ICON, stock_id,
RESULT_DATA, markuptxt,
RESULT_ACTION, action,
+ RESULT_SECTION, section,
IS_SENSITIVE, gtk_action_get_sensitive (action),
-1);
- g_free (data);
+
+ g_free (accel_string);
+ g_free (markuptxt);
+ g_free (label);
+ g_free (escaped_accel);
+ g_free (escaped_label);
+ g_free (escaped_tooltip);
}
-gboolean
-action_search_run_result_action (void)
+static void
+action_search_run_selected (SearchDialog *private)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
- selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (results_list));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->results_list));
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
if (gtk_tree_selection_get_selected (selection, &model, &iter))
{
GtkAction *action;
@@ -425,719 +536,373 @@ action_search_run_result_action (void)
gtk_tree_model_get (model, &iter, RESULT_ACTION, &action, -1);
if (! gtk_action_get_sensitive (action))
- return FALSE;
+ return;
- gtk_widget_hide (action_search_dialog);
+ action_search_finalizer (private);
gtk_action_activate (action);
- action_search_finalizer ();
- action_search_update_history (action);
}
- return TRUE;
+ return;
}
-
-void
-action_search_search_history_and_actions (const gchar *keyword)
+static void
+action_search_history_and_actions (const gchar *keyword,
+ SearchDialog *private)
{
GList *list;
GimpUIManager *manager;
- gint i = 0;
+ GList *history_actions = NULL;
manager = gimp_ui_managers_from_name ("<Image>")->data;
- if (strcmp (keyword, "") == 0)
+ if (g_strcmp0 (keyword, "") == 0)
return;
- for (i = 0;i<cur_no_of_his_actions;i++)
- {
- if (history[i].history_action != NULL)
- {
- if (action_search_is_action_match (history[i].history_action, keyword))
- action_search_add_to_results_list (gimp_strip_uline (gtk_action_get_label
(history[i].history_action)),
- gtk_action_get_tooltip (history[i].history_action),
- history[i].history_action );
- }
- }
+ history_actions = gimp_action_history_search (keyword,
+ action_search_match_keyword,
+ private->config);
+
+ /* First put on top of the list any matching action of user history. */
+ for (list = history_actions; list; list = g_list_next (list))
+ {
+ action_search_add_to_results_list (GTK_ACTION (list->data), private, 0);
+ }
+ /* Now check other actions. */
for (list = gtk_ui_manager_get_action_groups (GTK_UI_MANAGER (manager));
list;
list = g_list_next (list))
{
- GimpActionGroup *group = list->data;
- GList *actions;
GList *list2;
-
+ GimpActionGroup *group = list->data;
+ GList *actions = NULL;
actions = gtk_action_group_list_actions (GTK_ACTION_GROUP (group));
actions = g_list_sort (actions, (GCompareFunc) gimp_action_name_compare);
for (list2 = actions; list2; list2 = g_list_next (list2))
{
- GtkAction *action = list2->data;
- const gchar *name;
- gboolean is_redundant = FALSE;
- name = gtk_action_get_name (action);
-
-
- if (strstr (name, "-menu") ||
- strstr (name, "-popup") ||
- strstr (name, "context") ||
- strstr (name, "edit-undo") ||
- name[0] == '<')
+ const gchar *name;
+ GtkAction *action = list2->data;
+ gboolean is_redundant = FALSE;
+ gint section;
+
+ name = gtk_action_get_name (action);
+
+ /* The action search dialog don't show any non-historized
+ * action, with the exception of "plug-in-repeat/reshow"
+ * actions.
+ * Logging them is meaningless (they may mean a different
+ * actual action each time), but they are still interesting
+ * as a search result.
+ */
+ if (gimp_action_history_excluded_action (name) &&
+ g_strcmp0 (name, "plug-in-repeat") != 0 &&
+ g_strcmp0 (name, "plug-in-reshow") != 0)
continue;
- if (! gtk_action_get_sensitive (action) && ! (PREF.SHOW_INSENSITIVE) )
- continue;
+ if (! gtk_action_get_sensitive (action) && ! private->config->search_show_unavailable)
+ continue;
- for (i = 0;i<cur_no_of_his_actions;i++)
+ if (action_search_match_keyword (action, keyword, §ion, TRUE))
{
- if (history[i].history_action != NULL)
+ GList *list3;
+
+ /* A matching action. Check if we have not already added it as an history action. */
+ for (list3 = history_actions; list3; list3 = g_list_next (list3))
{
- if (strcmp (gtk_action_get_name (history[i].history_action), name) == 0)
+ if (strcmp (gtk_action_get_name (GTK_ACTION (list3->data)), name) == 0)
{
is_redundant = TRUE;
break;
}
}
- }
-
- if (is_redundant)
- continue;
- if (action_search_is_action_match (action, keyword))
- {
- action_search_add_to_results_list (gimp_strip_uline (gtk_action_get_label (action)),
- gtk_action_get_tooltip (action),
- action);
+ if (! is_redundant)
+ {
+ action_search_add_to_results_list (action, private, section);
+ }
}
}
+
g_list_free (actions);
}
-}
-
-static void action_search_fill_history (void)
-{
- GList *list;
- GimpUIManager *manager;
- gint i = 0;
- manager = gimp_ui_managers_from_name ("<Image>")->data;
- for (list = gtk_ui_manager_get_action_groups (GTK_UI_MANAGER (manager));
- list;
- list = g_list_next (list))
- {
- GimpActionGroup *group = list->data;
- GList *actions;
- GList *list2;
-
- actions = gtk_action_group_list_actions (GTK_ACTION_GROUP (group));
- actions = g_list_sort (actions, (GCompareFunc) gimp_action_name_compare);
-
- for (list2 = actions; list2; list2 = g_list_next (list2))
- {
- GtkAction *action = list2->data;
- const gchar *action_name;
- action_name = gtk_action_get_name (action);
-
- if (strstr (action_name, "-menu") ||
- strstr (action_name, "-popup") ||
- strstr (action_name, "context") ||
- action_name[0] == '<')
- continue;
-
- for (i = 0;i<cur_no_of_his_actions;i++)
- {
- if (name[i].action_name != NULL)
- {
- if (strcmp (name[i].action_name, action_name) == 0)
- {
- history[i].history_action = action;
- history[i].count = name[i].no;
- }
- }
- }
- }
- }
+ g_list_free_full (history_actions, (GDestroyNotify) g_object_unref);
}
+/**
+ * Fuzzy search matching.
+ * Returns: TRUE if all the letters of `key` are found in `string`,
+ * in the same order (even with intermediate letters).
+ */
static gboolean
-fuzzy_search (gchar *string,
- gchar *key)
+action_fuzzy_match (gchar *string,
+ gchar *key)
{
gchar *remaining_string = string;
+
if (strlen (key) == 0 )
- return TRUE;
+ return TRUE;
if ((remaining_string = strchr (string, key[0])) != NULL )
- return fuzzy_search (remaining_string+1, key+1 );
+ return action_fuzzy_match (remaining_string + 1,
+ key + 1);
else
return FALSE;
}
-static gboolean
-action_search_is_action_match (GtkAction *action,
- const gchar* keyword)
+/*
+ * Returns: a newly allocated lowercased string, which replaced any
+ * spacing characters into a single space and stripped out any leading
+ * and trailing space. */
+static gchar *
+action_search_normalize_string (const gchar *str)
{
- gchar *label, *tooltip, *key;
- gint i;
- gchar* space_pos;
- label = g_new (gchar, 1024);
- key = g_new (gchar, 1024);
- tooltip = g_new (gchar, 1024);
-
- strcpy (label, gimp_strip_uline (gtk_action_get_label (action)));
- strcpy (key, keyword);
-
- for (i = 0;i<strlen (label);i++)
- label[i] = tolower (label[i]);
- for (i = 0;i<strlen (key);i++)
- key[i] = tolower (key[i]);
- if (strlen (key) == 2)
- {
- space_pos = strchr (label, ' ');
- if (space_pos!= NULL)
- {
- space_pos++;
- if (key[0] == label[0] && key[1] == *space_pos)
- return TRUE;
- }
- }
+ GRegex *spaces_regex;
+ gchar *normalized_str;
+ gint i;
- if (strstr (label, key))
- return TRUE;
-
- if (fuzzy_search (label, key))
- return TRUE;
+ spaces_regex = g_regex_new ("[ \n\t\r]+", 0, 0, NULL);
+ normalized_str = g_regex_replace_literal (spaces_regex, str, -1, 0, " ", 0, NULL);
- if (strlen (key)>2 || strcmp (key, " ") == 0)
- {
- if (gtk_action_get_tooltip (action)!= NULL)
- {
- strcpy (tooltip, gtk_action_get_tooltip (action));
- for (i = 0;i<strlen (tooltip);i++)
- tooltip[i] = tolower (tooltip[i]);
+ g_regex_unref (spaces_regex);
- if (strstr (tooltip, key))
- return TRUE;
- }
- }
+ normalized_str = g_strstrip (normalized_str);
- g_free (label);
- g_free (key);
- g_free (tooltip);
+ for (i = 0 ; i < strlen (normalized_str); i++)
+ normalized_str[i] = tolower (normalized_str[i]);
- return FALSE;
+ return normalized_str;
}
-static void
-action_search_read_history (void)
+static gboolean
+action_search_match_keyword (GtkAction *action,
+ const gchar *keyword,
+ gint *section,
+ gboolean match_fuzzy)
{
- gint i;
- FILE *fp;
- cur_no_of_his_actions = 0;
-
- fp = fopen (history_file_path, "r");
- if (fp == NULL)
- return;
+ gboolean matched = FALSE;
+ gchar *key;
+ gchar *label;
+ gchar *tmp;
- for (i = 0;i<MAX_HISTORY_ACTIONS;i++)
+ if (keyword == NULL)
{
- if (fscanf (fp, "%s %d", name[i].action_name, &name[i].no) == EOF)
- break;
- cur_no_of_his_actions++;
+ /* As a special exception, a NULL keyword means
+ any action matches. */
+ if (section)
+ {
+ *section = 0;
+ }
+ return TRUE;
}
- fclose (fp);
- action_search_fill_history ();
-}
-
-static gint
-compare (const void * a,
- const void * b)
-{
- struct HISTORY *p = (struct HISTORY *)a;
- struct HISTORY *q = (struct HISTORY *)b;
- return (q->count - p->count);
-}
-
-static void
-action_search_update_history (GtkAction *action)
-{
- gint i;
- FILE *fp;
- gboolean is_present = FALSE;
+ key = action_search_normalize_string (keyword);
+ tmp = gimp_strip_uline (gtk_action_get_label (action));
+ label = action_search_normalize_string (tmp);
+ g_free (tmp);
- fp = fopen (history_file_path, "w");
- if (fp == NULL)
+ /* If keyword is two characters,
+ then match them with first letters of first and second word in the labels.
+ For instance 'gb' will list 'Gaussian Blur...' */
+ if (strlen (key) == 2)
{
- g_message ("Unable to open history file to write");
- return;
- }
+ gchar* space_pos;
- for (i = 0;i<cur_no_of_his_actions;i++)
- {
- if (strcmp (gtk_action_get_name (action), gtk_action_get_name (history[i].history_action)) == 0)
+ space_pos = strchr (label, ' ');
+
+ if (space_pos != NULL)
{
- history[i].count++;
- is_present = TRUE;
- break;
+ space_pos++;
+
+ if (key[0] == label[0] && key[1] == *space_pos)
+ {
+ matched = TRUE;
+ if (section)
+ {
+ *section = 1;
+ }
+ }
}
}
- if (! is_present)
+ if (! matched)
{
- if (cur_no_of_his_actions == MAX_HISTORY_ACTIONS)
+ gchar *substr;
+
+ substr = strstr (label, key);
+ if (substr)
{
- history[MAX_HISTORY_ACTIONS-1].history_action = action;
- history[MAX_HISTORY_ACTIONS-1].count = 1;
+ matched = TRUE;
+ if (section)
+ {
+ /* If the substring is the label start, this is a nicer match. */
+ *section = (substr == label)? 1 : 2;
+ }
}
- else
+ else if (strlen (key) > 2)
{
- history[cur_no_of_his_actions].history_action = action;
- history[cur_no_of_his_actions++].count = 1;
- }
- }
-
- qsort (history, cur_no_of_his_actions, sizeof (struct HISTORY), compare);
+ gchar *tooltip = NULL;
- for (i = 0;i<cur_no_of_his_actions;i++)
- {
- if (history[i].history_action != NULL)
- fprintf (fp, "%s %d \n", gtk_action_get_name (history[i].history_action),
- history[i].count);
- }
-
- fclose (fp);
-}
+ if (gtk_action_get_tooltip (action)!= NULL)
+ {
+ tooltip = action_search_normalize_string (gtk_action_get_tooltip (action));
-static void
-action_search_update_position (void)
-{
- if (PREF.POSITION == 0)
- {
- PREF.POSITION_X = (1-PREF.WIDTH/100)*par_width+par_x;
- PREF.POSITION_Y = 0.04*par_height+par_y;
- }
- else if (PREF.POSITION == 1)
- {
- PREF.POSITION_X = (par_width- PREF.WIDTH*par_width*.01)/2 + par_x;
- PREF.POSITION_Y = 0.2*par_height + par_y;
- }
- else
- {
- PREF.POSITION_X = tmp_x*par_width/100 + par_x;
- PREF.POSITION_Y = tmp_y*par_height/100 + par_y;
- }
- gtk_window_move (GTK_WINDOW (action_search_dialog), PREF.POSITION_X, PREF.POSITION_Y);
-}
+ if (strstr (tooltip, key))
+ {
+ matched = TRUE;
+ if (section)
+ {
+ *section = 3;
+ }
+ }
+ }
-void
-action_search_finalizer (void)
-{
- gtk_widget_destroy (action_search_dialog);
-}
+ if (! matched && strchr (key, ' '))
+ {
+ gchar *key_copy = g_strdup (key);
+ gchar *words = key_copy;
+ gchar *word;
-static void
-initialize_storage (void)
-{
- gchar *dir_filename = g_build_filename (gimp_directory (), "tito", NULL);
+ matched = TRUE;
+ if (section)
+ {
+ *section = 4;
+ }
- g_mkdir (dir_filename,
- S_IRUSR | S_IWUSR | S_IXUSR |
- S_IRGRP | S_IXGRP |
- S_IROTH | S_IXOTH);
+ while ((word = strsep (&words, " ")) != NULL)
+ {
+ if (! strstr (label, word) && (! tooltip || ! strstr (tooltip, word)))
+ {
+ matched = FALSE;
+ break;
+ }
+ }
- history_file_path = g_new (gchar, 1024);
- strcpy (history_file_path, dir_filename);
+ g_free (key_copy);
+ }
- preference_file_path = g_new (gchar, 1024);
- strcpy (preference_file_path, dir_filename);
+ g_free (tooltip);
+ }
- strcat (history_file_path, "/history");
- strcat (preference_file_path, "/preferences");
+ if (! matched && match_fuzzy && action_fuzzy_match (label, key))
+ {
+ matched = TRUE;
+ if (section)
+ {
+ *section = 5;
+ }
+ }
+ }
- g_free (dir_filename);
-}
+ g_free (label);
+ g_free (key);
-gboolean
-action_search_initializer (void)
-{
- gint i = 0;
- GdkWindow *par_window = gdk_screen_get_active_window (gdk_screen_get_default ());
-
- gdk_window_get_geometry (par_window, &par_x, &par_y, &par_width, &par_height, NULL);
-
- if (first_time)
- {
- initialize_storage ();
-
- for (i = 0;i<MAX_HISTORY_ACTIONS;i++)
- {
- name[i].action_name = g_new (char, 100);
- strcpy (name[i].action_name, "");
- name[i].no = 0;
- }
- first_time = FALSE;
- }
- action_search_read_preferences ();
- action_search_read_history ();
- gtk_accel_map_change_entry ("<Actions>/dialogs/dialogs-action-search", 'd', 0, FALSE);
- return TRUE;
+ return matched;
}
-static void
-action_search_clear_history (void)
+void
+action_search_finalizer (SearchDialog *private)
{
- FILE *fp;
- fp = fopen (history_file_path, "w");
- if (fp == NULL)
+ if (GTK_IS_WIDGET (private->dialog))
{
- g_message ("file not created");
- return;
+ gtk_entry_set_text (GTK_ENTRY (private->keyword_entry), "");
+ gtk_widget_hide (private->list_view);
+ gtk_window_resize (GTK_WINDOW (private->dialog),
+ private->width, 1);
+ gimp_dialog_factory_hide_dialog (private->dialog);
}
- fclose (fp);
-}
-
-static void
-action_search_set_default_preferences (void)
-{
- PREF.POSITION = 1;
- PREF.WIDTH = 40;
- PREF.POSITION_X = (1-0.4)*par_width+par_x;
- PREF.POSITION_Y = 0.04*par_height+par_y;
- PREF.NO_OF_RESULTS = 4;
- PREF.SHOW_INSENSITIVE = FALSE;
- PREF.OPACITY = 1;
- action_search_write_preferences ();
}
-static void
-action_search_update_preferences (void)
+static gboolean
+window_configured (GtkWindow *window,
+ GdkEvent *event,
+ SearchDialog *private)
{
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (PREF_UI.right_top_radio)))
- PREF.POSITION = 0;
- else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (PREF_UI.middle_radio)))
- PREF.POSITION = 1;
- else
+ if (event->type == GDK_CONFIGURE &&
+ gtk_widget_get_visible (GTK_WIDGET (window)))
{
- PREF.POSITION = 2;
- tmp_x = (gfloat) gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (PREF_UI.pos_x_spin_button));
- tmp_y = (gfloat) gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (PREF_UI.pos_y_spin_button));
- }
-
- PREF.NO_OF_RESULTS = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
(PREF_UI.no_of_results_spin_button));
- PREF.WIDTH = (gfloat)gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
(PREF_UI.width_spin_button));
- PREF.OPACITY = (gdouble)gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
(PREF_UI.opacity_spin_button))/100;
- PREF.SHOW_INSENSITIVE = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
(PREF_UI.show_insensitive_check_button));
-
- action_search_update_position ();
- action_search_write_preferences ();
- action_search_finalizer ();
-}
-
-static void
-action_search_write_preferences (void)
-{
- FILE *fp;
- fp = fopen (preference_file_path, "w");
-
- if (fp == NULL)
- {
- g_message ("Unable to open preferences file to write");
- return;
- }
-
- if (fp == NULL)
- return;
- fprintf (fp, "%d %f %f %d %f %d %lf", PREF.POSITION, PREF.POSITION_X, PREF.POSITION_Y,
- PREF.NO_OF_RESULTS, PREF.WIDTH, PREF.SHOW_INSENSITIVE, PREF.OPACITY);
- fclose (fp);
-}
-
-static void
-action_search_read_preferences (void)
-{
- FILE *fp;
-
- fp = fopen (preference_file_path, "r");
+ gint x, y, width, height;
- if (fp == NULL)
- {
- action_search_set_default_preferences ();
- return;
- }
-
- if (fscanf (fp, "%d %f %f %d %f %d %lf",
- &PREF.POSITION, &PREF.POSITION_X, &PREF.POSITION_Y,
- &PREF.NO_OF_RESULTS, &PREF.WIDTH, &PREF.SHOW_INSENSITIVE,
- &PREF.OPACITY) == 0)
- action_search_set_default_preferences ();
+ /* I don't use coordinates of GdkEventConfigure, because they are
+ relative to the parent window. */
+ gtk_window_get_position (window, &x, &y);
+ if (x < 0)
+ {
+ x = 0;
+ }
+ if (y < 0)
+ {
+ y = 0;
+ }
+ private->x = x;
+ private->y = y;
- fclose (fp);
-}
+ gtk_window_get_size (GTK_WINDOW (private->dialog), &width, &height);
+ private->width = width;
-static gboolean
-context_menu_invoked (GtkWidget *widget,
- GdkEvent *event,
- gpointer user_data)
-{
- action_search_context_menu ();
- return TRUE;
-}
+ if (gtk_widget_get_visible (private->list_view))
+ {
+ private->height = height;
+ }
+ }
-static void
-context_menu_handler (GtkMenuItem* menuitem,
- gpointer *data)
-{
- if (strchr (gtk_menu_item_get_label (menuitem), 'r') != NULL)
- action_search_preferences_dialog ();
- else
- gtk_widget_destroy (action_search_dialog);
+ return FALSE;
}
static void
-action_search_context_menu (void)
+window_shown (GtkWidget *widget,
+ SearchDialog *private)
{
- GtkWidget *preferences_menuitem;
- GtkWidget *close_menuitem;
- GtkWidget *context_menu;
-
- context_menu = gtk_menu_new ();
- preferences_menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PREFERENCES, NULL);
- close_menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLOSE, NULL);
-
- gtk_menu_shell_append (GTK_MENU_SHELL (context_menu), preferences_menuitem);
- gtk_menu_shell_append (GTK_MENU_SHELL (context_menu), close_menuitem);
-
- gtk_widget_show (context_menu);
- gtk_widget_show (preferences_menuitem);
- gtk_widget_show (close_menuitem);
-
- g_signal_connect (preferences_menuitem, "activate", G_CALLBACK (context_menu_handler), NULL);
- g_signal_connect (close_menuitem, "activate", G_CALLBACK (context_menu_handler), NULL);
-
- gtk_menu_popup (GTK_MENU (context_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
+ gtk_window_move (GTK_WINDOW (widget),
+ private->x, private->y);
}
static void
-action_search_preferences_dialog (void)
-{
- GtkWidget *pref_dialog;
- GtkWidget *top_hbox;
-
- GtkWidget *position_frame;
- GtkWidget *position_vbox;
- GtkWidget *pos_x_label;
- GtkWidget *pos_y_label;
- GtkWidget *specify_alignment_x;
- GtkWidget *specify_alignment_y;
-
- GtkWidget *display_frame;
- GtkWidget *display_vbox;
- GtkWidget *no_of_results_hbox;
- GtkWidget *width_hbox;
- GtkWidget *opacity_hbox;
- GtkWidget *no_of_results_label;
- GtkWidget *width_label;
- GtkWidget *opacity_label;
-
- GtkWidget *bottom_hbox;
- GtkWidget *action_search_clear_history_button;
- GtkWidget *restore_defaults_button;
-
- pref_dialog = gtk_dialog_new_with_buttons ("Tito preferences",
- NULL,
- GTK_DIALOG_MODAL,
- GTK_STOCK_OK,
- GTK_RESPONSE_ACCEPT,
- GTK_STOCK_CANCEL,
- GTK_RESPONSE_REJECT,
- NULL);
-
- gtk_window_set_position (GTK_WINDOW (pref_dialog), GTK_WIN_POS_CENTER_ALWAYS);
- top_hbox = gtk_hbox_new (FALSE, 10);
- gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (pref_dialog))), top_hbox, FALSE,
FALSE, 2);
-
- position_frame = gtk_frame_new ("Postion");
- position_vbox = gtk_vbox_new (TRUE, 2);
-
- gtk_frame_set_shadow_type (GTK_FRAME (position_frame), GTK_SHADOW_ETCHED_IN);
-
- PREF_UI.right_top_radio = gtk_radio_button_new_with_label (NULL, "Right-Top");
- PREF_UI.middle_radio = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON
(PREF_UI.right_top_radio), "Middle");
- PREF_UI.specify_radio = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON
(PREF_UI.right_top_radio), "Specify");
- PREF_UI.pos_x_hbox = gtk_hbox_new (FALSE, 1);
- PREF_UI.pos_y_hbox = gtk_hbox_new (FALSE, 1);
- specify_alignment_x = gtk_alignment_new (1, 0, 0, 0);
- specify_alignment_y = gtk_alignment_new (1, 0, 0, 0);
- pos_x_label = gtk_label_new ("x:");
- pos_y_label = gtk_label_new ("y:");
- PREF_UI.pos_x_spin_button = gtk_spin_button_new_with_range (0, 100-PREF.WIDTH, 1);
- PREF_UI.pos_y_spin_button = gtk_spin_button_new_with_range (0, 50, 1);
-
- gtk_box_pack_start (GTK_BOX (top_hbox), position_frame, FALSE, FALSE, 2);
- gtk_container_add (GTK_CONTAINER (position_frame), position_vbox);
- gtk_box_pack_start (GTK_BOX (position_vbox), PREF_UI.right_top_radio, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (position_vbox), PREF_UI.middle_radio, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (position_vbox), PREF_UI.specify_radio, TRUE, TRUE, 2);
-
- gtk_box_pack_start (GTK_BOX (position_vbox), specify_alignment_x, TRUE, TRUE, 1);
- gtk_container_add (GTK_CONTAINER (specify_alignment_x), PREF_UI.pos_x_hbox);
- gtk_box_pack_start (GTK_BOX (PREF_UI.pos_x_hbox), pos_x_label, TRUE, TRUE, 1);
- gtk_box_pack_start (GTK_BOX (PREF_UI.pos_x_hbox), PREF_UI.pos_x_spin_button, TRUE, TRUE, 1);
-
- gtk_box_pack_start (GTK_BOX (position_vbox), specify_alignment_y, TRUE, TRUE, 1);
- gtk_container_add (GTK_CONTAINER (specify_alignment_y), PREF_UI.pos_y_hbox);
- gtk_box_pack_start (GTK_BOX (PREF_UI.pos_y_hbox), pos_y_label, TRUE, TRUE, 1);
- gtk_box_pack_start (GTK_BOX (PREF_UI.pos_y_hbox), PREF_UI.pos_y_spin_button, TRUE, TRUE, 1);
-
- display_frame = gtk_frame_new ("Display");
- display_vbox = gtk_vbox_new (TRUE, 2);
-
- gtk_frame_set_shadow_type (GTK_FRAME (display_frame), GTK_SHADOW_ETCHED_IN);
-
- no_of_results_hbox = gtk_hbox_new (FALSE, 2);
- width_hbox = gtk_hbox_new (FALSE, 2);
- opacity_hbox = gtk_hbox_new (FALSE, 2);
- no_of_results_label = gtk_label_new ("Results height:");
- PREF_UI.no_of_results_spin_button = gtk_spin_button_new_with_range (2, 10, 1);
- width_label = gtk_label_new ("Tito Width:");
- PREF_UI.width_spin_button = gtk_spin_button_new_with_range (20, 60, 1);
- opacity_label = gtk_label_new ("Tito Opacity:");
- PREF_UI.opacity_spin_button = gtk_spin_button_new_with_range (40, 100, 10);
- PREF_UI.show_insensitive_check_button = gtk_check_button_new_with_label ("Show unavailable actions");
- action_search_clear_history_button = gtk_button_new_with_label ("Clear history");
- restore_defaults_button = gtk_button_new_with_label ("Restore defaults");
-
- gtk_box_pack_start (GTK_BOX (top_hbox), display_frame, FALSE, FALSE, 2);
- gtk_container_add (GTK_CONTAINER (display_frame), display_vbox);
- gtk_box_pack_start (GTK_BOX (display_vbox), no_of_results_hbox, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (no_of_results_hbox), no_of_results_label, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (no_of_results_hbox), PREF_UI.no_of_results_spin_button, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (display_vbox), width_hbox, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (width_hbox), width_label, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (width_hbox), PREF_UI.width_spin_button, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (display_vbox), opacity_hbox, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (opacity_hbox), opacity_label, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (opacity_hbox), PREF_UI.opacity_spin_button, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (display_vbox), PREF_UI.show_insensitive_check_button, TRUE, TRUE, 2);
-
- bottom_hbox = gtk_hbox_new (TRUE, 2);
- gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (pref_dialog))), bottom_hbox, FALSE,
FALSE, 2);
- gtk_box_pack_start (GTK_BOX (bottom_hbox), action_search_clear_history_button, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (bottom_hbox), restore_defaults_button, TRUE, TRUE, 2);
-
- action_search_set_prefereces_ui_values ();
- gtk_widget_show_all (pref_dialog);
-
- g_signal_connect (PREF_UI.right_top_radio, "toggled", G_CALLBACK (modify_position_spins), NULL);
- g_signal_connect (PREF_UI.middle_radio, "toggled", G_CALLBACK (modify_position_spins), NULL);
- g_signal_connect (PREF_UI.specify_radio, "toggled", G_CALLBACK (modify_position_spins), NULL);
- g_signal_connect (action_search_clear_history_button, "clicked", G_CALLBACK
(action_search_clear_history_button_clicked), NULL);
- g_signal_connect (restore_defaults_button, "clicked", G_CALLBACK (restore_defaults_button_clicked), NULL);
-
- if (gtk_dialog_run (GTK_DIALOG (pref_dialog)) == GTK_RESPONSE_ACCEPT)
- action_search_update_preferences ();
-
- gtk_widget_destroy (pref_dialog);
-}
-
-static GtkWidget*
-action_search_setup_results_list (void)
+action_search_setup_results_list (GtkWidget **results_list,
+ GtkWidget **list_view)
{
gint wid1 = 100;
- GtkWidget *sc_win;
GtkListStore *store;
GtkCellRenderer *cell1;
GtkCellRenderer *cell_renderer;
GtkTreeViewColumn *column1, *column2;
- sc_win = gtk_scrolled_window_new (NULL, NULL);
- store = gtk_list_store_new (N_COL, G_TYPE_STRING, G_TYPE_STRING, GTK_TYPE_ACTION, G_TYPE_BOOLEAN);
- results_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
- gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (results_list), FALSE);
+ *list_view = GTK_WIDGET (gtk_scrolled_window_new (NULL, NULL));
+ store = gtk_list_store_new (N_COL, G_TYPE_STRING, G_TYPE_STRING,
+ GTK_TYPE_ACTION, G_TYPE_BOOLEAN, G_TYPE_INT);
+ *results_list = GTK_WIDGET (gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (*results_list), FALSE);
cell1 = gtk_cell_renderer_pixbuf_new ();
column1 = gtk_tree_view_column_new_with_attributes (NULL,
- cell1,
- "stock_id", RESULT_ICON,
- NULL);
- gtk_tree_view_append_column (GTK_TREE_VIEW (results_list), column1);
+ cell1,
+ "stock_id", RESULT_ICON,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column1);
gtk_tree_view_column_add_attribute (column1, cell1, "sensitive", IS_SENSITIVE);
gtk_tree_view_column_set_min_width (column1, 22);
cell_renderer = gtk_cell_renderer_text_new ();
column2 = gtk_tree_view_column_new_with_attributes (NULL,
- cell_renderer,
- "markup", RESULT_DATA,
- NULL);
+ cell_renderer,
+ "markup", RESULT_DATA,
+ NULL);
gtk_tree_view_column_add_attribute (column2, cell_renderer, "sensitive", IS_SENSITIVE);
- gtk_tree_view_append_column (GTK_TREE_VIEW (results_list), column2);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column2);
gtk_tree_view_column_set_max_width (column2, wid1);
- gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sc_win),
- GTK_POLICY_NEVER,
- GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*list_view),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
- gtk_container_add (GTK_CONTAINER (sc_win), results_list);
+ gtk_container_add (GTK_CONTAINER (*list_view), *results_list);
g_object_unref (G_OBJECT (store));
-
- return sc_win;
}
-static gboolean
-action_search_search_dialog (void)
+static void
+search_dialog_free (SearchDialog *private)
{
- GtkWidget *main_vbox, *main_hbox;
- GtkWidget *preferences_image;
- GtkWidget *preferences_button;
- GtkWidget *list_view;
-
- action_search_dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-
- gtk_window_set_decorated (GTK_WINDOW (action_search_dialog), FALSE);
- gtk_window_set_default_size (GTK_WINDOW (action_search_dialog), (PREF.WIDTH/100)*par_width,
DEFAULT_HEIGHT);
- action_search_update_position ();
- gtk_window_set_opacity (GTK_WINDOW (action_search_dialog), PREF.OPACITY);
-
- main_vbox = gtk_vbox_new (FALSE, 2);
- gtk_container_add (GTK_CONTAINER (action_search_dialog), main_vbox);
- gtk_widget_show (main_vbox);
-
- main_hbox = gtk_hbox_new (FALSE, 2);
- gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, FALSE, TRUE, 0);
- gtk_widget_show (main_hbox);
-
- keyword_entry = gtk_entry_new ();
- gtk_entry_set_has_frame (GTK_ENTRY (keyword_entry), FALSE);
- gtk_entry_set_icon_from_stock (GTK_ENTRY (keyword_entry), GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND);
- gtk_widget_show (keyword_entry);
- gtk_box_pack_start (GTK_BOX (main_hbox), keyword_entry, TRUE, TRUE, 0);
-
- preferences_image = gtk_image_new_from_stock (GTK_STOCK_PROPERTIES, GTK_ICON_SIZE_MENU);
- preferences_button = gtk_button_new ();
- gtk_button_set_image (GTK_BUTTON (preferences_button), preferences_image);
- gtk_widget_show (preferences_image);
- gtk_widget_show (preferences_button);
- gtk_box_pack_end (GTK_BOX (main_hbox), preferences_button, FALSE, TRUE, 0);
-
- list_view = action_search_setup_results_list ();
- gtk_box_pack_start (GTK_BOX (main_vbox), list_view, TRUE, TRUE, 0);
-
-
- gtk_widget_set_events (action_search_dialog, GDK_KEY_RELEASE_MASK);
- gtk_widget_set_events (action_search_dialog, GDK_KEY_PRESS_MASK);
- gtk_widget_set_events (action_search_dialog, GDK_BUTTON_PRESS_MASK);
- gtk_widget_set_events (preferences_button, GDK_BUTTON_PRESS_MASK);
-
- g_signal_connect (results_list, "row-activated", (GCallback) row_activated, NULL);
- g_signal_connect (keyword_entry, "key-release-event", G_CALLBACK (key_released), list_view);
- g_signal_connect (results_list, "key_press_event", G_CALLBACK (result_selected), NULL);
- g_signal_connect (preferences_button, "clicked", G_CALLBACK (context_menu_invoked), NULL);
- g_signal_connect (action_search_dialog, "focus-out-event", G_CALLBACK (on_focus_out), preferences_button);
-
- gtk_widget_show (action_search_dialog);
-
- return TRUE;
+ g_slice_free (SearchDialog, private);
}
diff --git a/app/dialogs/action-search-dialog.h b/app/dialogs/action-search-dialog.h
index 162efe4..6bc2c40 100644
--- a/app/dialogs/action-search-dialog.h
+++ b/app/dialogs/action-search-dialog.h
@@ -18,7 +18,6 @@
#ifndef __ACTION_SEARCH_DIALOG_H__
#define __ACTION_SEARCH_DIALOG_H__
-
-GtkWidget * action_search_dialog_create (void);
+GtkWidget * action_search_dialog_create (Gimp *gimp);
#endif /* __ACTION_SEARCH_DIALOG_H__ */
diff --git a/app/dialogs/dialogs-constructors.c b/app/dialogs/dialogs-constructors.c
index 09e2448..706d638 100644
--- a/app/dialogs/dialogs-constructors.c
+++ b/app/dialogs/dialogs-constructors.c
@@ -133,6 +133,15 @@ dialogs_file_export_new (GimpDialogFactory *factory,
}
GtkWidget *
+dialogs_action_search_get (GimpDialogFactory *factory,
+ GimpContext *context,
+ GimpUIManager *ui_manager,
+ gint view_size)
+{
+ return action_search_dialog_create (context->gimp);
+}
+
+GtkWidget *
dialogs_preferences_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
@@ -196,15 +205,6 @@ dialogs_about_get (GimpDialogFactory *factory,
}
GtkWidget *
-dialogs_action_search_get (GimpDialogFactory *factory,
- GimpContext *context,
- GimpUIManager *ui_manager,
- gint view_size)
-{
- return action_search_dialog_create ();
-}
-
-GtkWidget *
dialogs_error_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
diff --git a/app/dialogs/dialogs-constructors.h b/app/dialogs/dialogs-constructors.h
index 53b8d53..bb0a526 100644
--- a/app/dialogs/dialogs-constructors.h
+++ b/app/dialogs/dialogs-constructors.h
@@ -41,6 +41,10 @@ GtkWidget * dialogs_file_export_new (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
gint view_size);
+GtkWidget * dialogs_action_search_get (GimpDialogFactory *factory,
+ GimpContext *context,
+ GimpUIManager *ui_manager,
+ gint view_size);
GtkWidget * dialogs_preferences_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
@@ -69,10 +73,6 @@ GtkWidget * dialogs_about_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
gint view_size);
-GtkWidget * dialogs_action_search_get (GimpDialogFactory *factory,
- GimpContext *context,
- GimpUIManager *ui_manager,
- gint view_size);
GtkWidget * dialogs_error_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
diff --git a/app/dialogs/dialogs.c b/app/dialogs/dialogs.c
index 9a4f393..42ec649 100644
--- a/app/dialogs/dialogs.c
+++ b/app/dialogs/dialogs.c
@@ -264,6 +264,8 @@ static const GimpDialogFactoryEntry entries[] =
dialogs_file_export_new, FALSE, TRUE, TRUE),
/* singleton toplevels */
+ TOPLEVEL ("gimp-action-search-dialog",
+ dialogs_action_search_get, TRUE, TRUE, TRUE),
TOPLEVEL ("gimp-preferences-dialog",
dialogs_preferences_get, TRUE, TRUE, FALSE),
TOPLEVEL ("gimp-input-devices-dialog",
@@ -278,8 +280,6 @@ static const GimpDialogFactoryEntry entries[] =
dialogs_tips_get, TRUE, FALSE, FALSE),
TOPLEVEL ("gimp-about-dialog",
dialogs_about_get, TRUE, FALSE, FALSE),
- TOPLEVEL ("gimp-action-search-dialog",
- dialogs_action_search_get, TRUE, FALSE, FALSE),
TOPLEVEL ("gimp-error-dialog",
dialogs_error_get, TRUE, FALSE, FALSE),
TOPLEVEL ("gimp-close-all-dialog",
diff --git a/app/dialogs/preferences-dialog.c b/app/dialogs/preferences-dialog.c
index be14267..efb8624 100644
--- a/app/dialogs/preferences-dialog.c
+++ b/app/dialogs/preferences-dialog.c
@@ -53,6 +53,7 @@
#include "widgets/gimptooleditor.h"
#include "widgets/gimpwidgets-constructors.h"
#include "widgets/gimpwidgets-utils.h"
+#include "widgets/gimpaction-history.h"
#include "menus/menus.h"
@@ -113,6 +114,8 @@ static void prefs_devices_save_callback (GtkWidget *widget,
Gimp *gimp);
static void prefs_devices_clear_callback (GtkWidget *widget,
Gimp *gimp);
+static void prefs_search_empty_callback (GtkWidget *widget,
+ gpointer user_data);
static void prefs_tool_options_save_callback (GtkWidget *widget,
Gimp *gimp);
static void prefs_tool_options_clear_callback (GtkWidget *widget,
@@ -646,6 +649,13 @@ prefs_devices_clear_callback (GtkWidget *widget,
}
static void
+prefs_search_empty_callback (GtkWidget *widget,
+ gpointer user_data)
+{
+ gimp_action_history_empty ();
+}
+
+static void
prefs_tool_options_save_callback (GtkWidget *widget,
Gimp *gimp)
{
@@ -1670,10 +1680,27 @@ prefs_dialog_new (Gimp *gimp,
_("H_elp browser to use:"),
GTK_TABLE (table), 0, size_group);
+ /* Action Search */
+ vbox2 = prefs_frame_new (_("Action Search"), GTK_CONTAINER (vbox), FALSE);
+ table = prefs_table_new (1, GTK_CONTAINER (vbox2));
+
+ prefs_check_button_add (object, "search-show-unavailable-actions",
+ _("Show _unavailable actions"),
+ GTK_BOX (vbox2));
+ prefs_spin_button_add (object, "action-history-size", 1.0, 10.0, 0,
+ _("Maximum History Size:"),
+ GTK_TABLE (table), 0, size_group);
+
+ button = prefs_button_add (GTK_STOCK_CLEAR,
+ _("Clear Action History"),
+ GTK_BOX (vbox2));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (prefs_search_empty_callback),
+ NULL);
+
g_object_unref (size_group);
size_group = NULL;
-
/******************/
/* Tool Options */
/******************/
diff --git a/app/gui/gui.c b/app/gui/gui.c
index 40e471e..7eb3d44 100644
--- a/app/gui/gui.c
+++ b/app/gui/gui.c
@@ -61,6 +61,7 @@
#include "widgets/gimpuimanager.h"
#include "widgets/gimpwidgets-utils.h"
#include "widgets/gimplanguagestore-parser.h"
+#include "widgets/gimpaction-history.h"
#include "actions/actions.h"
#include "actions/windows-commands.h"
@@ -492,6 +493,7 @@ gui_restore_after_callback (Gimp *gimp,
gimp,
gui_config->tearoff_menus);
gimp_ui_manager_update (image_ui_manager, gimp);
+ gimp_action_history_init (gui_config);
#ifdef GDK_WINDOWING_QUARTZ
{
@@ -660,6 +662,7 @@ gui_exit_after_callback (Gimp *gimp,
gui_show_tooltips_notify,
gimp);
+ gimp_action_history_exit (GIMP_GUI_CONFIG (gimp->config));
g_object_unref (image_ui_manager);
image_ui_manager = NULL;
diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am
index 80bb889..3948910 100644
--- a/app/widgets/Makefile.am
+++ b/app/widgets/Makefile.am
@@ -223,6 +223,8 @@ libappwidgets_a_sources = \
gimplanguagestore.h \
gimplanguagestore-parser.c \
gimplanguagestore-parser.h \
+ gimpaction-history.c \
+ gimpaction-history.h \
gimplayertreeview.c \
gimplayertreeview.h \
gimpmenudock.c \
diff --git a/app/widgets/gimpaction-history.c b/app/widgets/gimpaction-history.c
new file mode 100644
index 0000000..4da02bd
--- /dev/null
+++ b/app/widgets/gimpaction-history.c
@@ -0,0 +1,374 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 2013 Jehan <jehan at girinstud.io>
+ *
+ * gimpaction-history.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <ctype.h>
+
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "widgets-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "gimpuimanager.h"
+#include "gimpaction.h"
+#include "gimpaction-history.h"
+
+#define GIMP_ACTION_HISTORY_FILENAME "action_history"
+
+typedef struct {
+ GtkAction *action;
+ gchar *name;
+ gint count;
+} GimpActionHistoryItem;
+
+static struct {
+ GimpGuiConfig *config;
+ GList *items;
+} history;
+
+static void gimp_action_history_item_free (GimpActionHistoryItem *item);
+
+static gint gimp_action_history_init_compare_func (GimpActionHistoryItem *a,
+ GimpActionHistoryItem *b);
+static gint gimp_action_history_compare_func (GimpActionHistoryItem *a,
+ GimpActionHistoryItem *b);
+
+static void gimp_action_insert (const gchar *action_name,
+ gint count);
+
+/* public functions */
+
+void
+gimp_action_history_init (GimpGuiConfig *config)
+{
+ gchar *history_file_path;
+ gint count;
+ FILE *fp;
+ gint i;
+
+ if (history.items != NULL)
+ {
+ g_warning ("%s: must be run only once.", G_STRFUNC);
+ return;
+ }
+
+ history.config = config;
+ history_file_path = g_build_filename (gimp_directory (),
+ GIMP_ACTION_HISTORY_FILENAME,
+ NULL);
+ fp = fopen (history_file_path, "r");
+ if (fp == NULL)
+ /* Probably a first use case. Not necessarily an error. */
+ return;
+
+ for (i = 0; i < config->action_history_size; i++)
+ {
+ /* Let's assume an action name will never be more than 256 character. */
+ gchar action_name[256];
+
+ if (fscanf (fp, "%s %d", action_name, &count) == EOF)
+ break;
+
+ gimp_action_insert (action_name, count);
+ }
+
+ if (count > 1)
+ {
+ GList *actions = history.items;
+
+ for (; actions && i; actions = g_list_next (actions), i--)
+ {
+ GimpActionHistoryItem *action = actions->data;
+
+ action->count -= count - 1;
+ }
+ }
+
+ g_free (history_file_path);
+
+ fclose (fp);
+}
+
+void
+gimp_action_history_exit (GimpGuiConfig *config)
+{
+ GList *actions = history.items;
+ gchar *history_file_path;
+ gint min_count = 0;
+ FILE *fp;
+ gint i = config->action_history_size;
+
+ /* If we have more items than current history size, trim the history
+ and move down all count so that 1 is lower. */
+ for (; actions && i; actions = g_list_next (actions), i--)
+ ;
+ if (actions)
+ {
+ GimpActionHistoryItem *action = actions->data;
+
+ min_count = action->count - 1;
+ }
+
+ actions = history.items;
+ i = config->action_history_size;
+
+ history_file_path = g_build_filename (gimp_directory (),
+ GIMP_ACTION_HISTORY_FILENAME,
+ NULL);
+
+ fp = fopen (history_file_path, "w");
+ for (; actions && i; actions = g_list_next (actions), i--)
+ {
+ GimpActionHistoryItem *action = actions->data;
+
+ fprintf (fp, "%s %d \n", action->name, action->count - min_count);
+ }
+
+ gimp_action_history_empty ();
+ fclose (fp);
+ g_free (history_file_path);
+}
+
+/* gimp_action_history_excluded_action:
+ *
+ * Returns whether an action should be excluded from history.
+ */
+gboolean
+gimp_action_history_excluded_action (const gchar *action_name)
+{
+ return (g_str_has_suffix (action_name, "-menu") ||
+ g_str_has_suffix (action_name, "-popup") ||
+ g_str_has_suffix (action_name, "-set") ||
+ g_str_has_suffix (action_name, "-accel") ||
+ g_str_has_prefix (action_name, "context-") ||
+ g_str_has_prefix (action_name, "plug-in-recent-") ||
+ g_strcmp0 (action_name, "plug-in-repeat") == 0 ||
+ g_strcmp0 (action_name, "plug-in-reshow") == 0 ||
+ g_strcmp0 (action_name, "help-action-search") == 0);
+}
+
+/* Callback run on the `activate` signal of an action.
+ It allows us to log all used action. */
+void
+gimp_action_history_activate_callback (GtkAction *action,
+ gpointer user_data)
+{
+ GList *actions;
+ GimpActionHistoryItem *history_item;
+ const gchar *action_name;
+ gint previous_count = 0;
+
+ action_name = gtk_action_get_name (action);
+
+ /* Some specific actions are of no log interest. */
+ if (gimp_action_history_excluded_action (action_name))
+ return;
+
+ for (actions = history.items; actions; actions = g_list_next (actions))
+ {
+ history_item = actions->data;
+
+ if (g_strcmp0 (action_name, history_item->name) == 0)
+ {
+ GimpActionHistoryItem *next_history_item = g_list_next (actions) ?
+ g_list_next (actions)->data : NULL;
+
+ /* Is there any other item with the same count?
+ We don't want to leave any count gap to always accept new items.
+ This means that if we increment the only item with a given count,
+ we must decrement the next item.
+ Other consequence is that an item with higher count won't be
+ incremented at all if no other items have the same count. */
+ if (previous_count == history_item->count ||
+ (next_history_item && next_history_item->count == history_item->count))
+ {
+ history_item->count++;
+ /* Remove then reinsert to reorder. */
+ history.items = g_list_remove (history.items, history_item);
+ history.items = g_list_insert_sorted (history.items, history_item,
+ (GCompareFunc) gimp_action_history_compare_func);
+ }
+ else if (previous_count != 0 &&
+ previous_count != history_item->count)
+ {
+ GimpActionHistoryItem *previous_history_item = g_list_previous (actions)->data;
+
+ history_item->count++;
+ /* Remove then reinsert to reorder. */
+ history.items = g_list_remove (history.items, history_item);
+ history.items = g_list_insert_sorted (history.items, history_item,
+ (GCompareFunc) gimp_action_history_compare_func);
+
+ previous_history_item->count--;
+ /* Remove then reinsert to reorder. */
+ history.items = g_list_remove (history.items, previous_history_item);
+ history.items = g_list_insert_sorted (history.items, previous_history_item,
+ (GCompareFunc) gimp_action_history_compare_func);
+ }
+ return;
+ }
+
+ previous_count = history_item->count;
+ }
+
+ /* If we are here, this action is not logged yet. */
+ history_item = g_malloc0 (sizeof (GimpActionHistoryItem));
+
+ history_item->action = g_object_ref (action);
+ history_item->name = g_strdup (action_name);
+ history_item->count = 1;
+ history.items = g_list_insert_sorted (history.items,
+ history_item,
+ (GCompareFunc) gimp_action_history_compare_func);
+}
+
+void
+gimp_action_history_empty (void)
+{
+ g_list_free_full (history.items, (GDestroyNotify) gimp_action_history_item_free);
+ history.items = NULL;
+}
+
+/* Search all history actions which match "keyword"
+ with function match_func(action, keyword).
+
+ @return a list of GtkAction*, to free with:
+ g_list_free_full (result, (GDestroyNotify) g_object_unref);
+ */
+GList*
+gimp_action_history_search (const gchar *keyword,
+ GimpActionMatchFunc match_func,
+ GimpGuiConfig *config)
+{
+ GList *actions;
+ GimpActionHistoryItem *history_item;
+ GtkAction *action;
+ GList *search_result = NULL;
+ gint i = config->action_history_size;
+
+ for (actions = history.items; actions && i; actions = g_list_next (actions), i--)
+ {
+ history_item = actions->data;
+ action = history_item->action;
+
+ if (! gtk_action_get_sensitive (action) && ! config->search_show_unavailable)
+ continue;
+
+ if (match_func (action, keyword, NULL, FALSE))
+ search_result = g_list_prepend (search_result, g_object_ref (action));
+ }
+
+ return g_list_reverse (search_result);
+}
+
+/* private functions */
+
+static void
+gimp_action_history_item_free (GimpActionHistoryItem *item)
+{
+ g_object_unref (item->action);
+ g_free (item->name);
+ g_free (item);
+}
+
+/* Compare function used at list initialization.
+ We use a slightly different compare function as for runtime insert,
+ because we want to keep history file order for equal values. */
+static gint
+gimp_action_history_init_compare_func (GimpActionHistoryItem *a,
+ GimpActionHistoryItem *b)
+{
+ return (a->count <= b->count);
+}
+
+/* Compare function used when updating the list.
+ There is no equality case. If they have the same count,
+ I ensure that the first action (last inserted) will be before. */
+static gint
+gimp_action_history_compare_func (GimpActionHistoryItem *a,
+ GimpActionHistoryItem *b)
+{
+ return (a->count < b->count);
+}
+
+static void
+gimp_action_insert (const gchar *action_name,
+ gint count)
+{
+ GList *action_groups;
+ GimpUIManager *manager;
+
+ /* We do not insert some categories of actions. */
+ if (gimp_action_history_excluded_action (action_name))
+ return;
+
+ manager = gimp_ui_managers_from_name ("<Image>")->data;
+
+ for (action_groups = gtk_ui_manager_get_action_groups (GTK_UI_MANAGER (manager));
+ action_groups;
+ action_groups = g_list_next (action_groups))
+ {
+ GimpActionGroup *group = action_groups->data;
+ GList *actions;
+ GList *list2;
+ gboolean found = FALSE;
+
+ actions = gtk_action_group_list_actions (GTK_ACTION_GROUP (group));
+ actions = g_list_sort (actions, (GCompareFunc) gimp_action_name_compare);
+
+ for (list2 = actions; list2; list2 = g_list_next (list2))
+ {
+ GtkAction *action = list2->data;
+ gint unavailable_action = -1;
+
+ unavailable_action = strcmp (action_name, gtk_action_get_name (action));
+
+ if (unavailable_action == 0)
+ {
+ /* We found our action. */
+ GimpActionHistoryItem *new_action = g_malloc0 (sizeof (GimpActionHistoryItem));
+
+ new_action->action = g_object_ref (action);
+ new_action->name = g_strdup (action_name);
+ new_action->count = count;
+ history.items = g_list_insert_sorted (history.items,
+ new_action,
+ (GCompareFunc) gimp_action_history_init_compare_func);
+ found = TRUE;
+ break;
+ }
+ else if (unavailable_action < 0)
+ {
+ /* Since the actions list is sorted, it means we passed
+ all possible actions already and it is not in this group. */
+ break;
+ }
+
+ }
+ g_list_free (actions);
+
+ if (found)
+ break;
+ }
+}
diff --git a/app/widgets/gimpaction-history.h b/app/widgets/gimpaction-history.h
new file mode 100644
index 0000000..c0ca984
--- /dev/null
+++ b/app/widgets/gimpaction-history.h
@@ -0,0 +1,42 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 2013 Jehan <jehan at girinstud.io>
+ *
+ * gimpaction-history.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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 __GIMP_ACTION_HISTORY_H__
+#define __GIMP_ACTION_HISTORY_H__
+
+typedef gboolean (* GimpActionMatchFunc) (GtkAction *action,
+ const gchar *keyword,
+ gint *section,
+ gboolean match_fuzzy);
+
+void gimp_action_history_init (GimpGuiConfig *config);
+void gimp_action_history_exit (GimpGuiConfig *config);
+
+void gimp_action_history_activate_callback (GtkAction *action,
+ gpointer user_data);
+
+void gimp_action_history_empty (void);
+
+GList * gimp_action_history_search (const gchar *keyword,
+ GimpActionMatchFunc match_func,
+ GimpGuiConfig *config);
+
+gboolean gimp_action_history_excluded_action (const gchar *action_name);
+
+#endif /* __GIMP_ACTION_HISTORY_H__ */
diff --git a/app/widgets/gimpaction.c b/app/widgets/gimpaction.c
index ff3db4c..ffc68fb 100644
--- a/app/widgets/gimpaction.c
+++ b/app/widgets/gimpaction.c
@@ -37,6 +37,7 @@
#include "core/gimpviewable.h"
#include "gimpaction.h"
+#include "gimpaction-history.h"
#include "gimpview.h"
#include "gimpviewrenderer.h"
@@ -52,6 +53,7 @@ enum
};
+static void gimp_action_constructed (GObject *object);
static void gimp_action_finalize (GObject *object);
static void gimp_action_set_property (GObject *object,
guint prop_id,
@@ -85,6 +87,7 @@ gimp_action_class_init (GimpActionClass *klass)
GtkActionClass *action_class = GTK_ACTION_CLASS (klass);
GimpRGB black;
+ object_class->constructed = gimp_action_constructed;
object_class->finalize = gimp_action_finalize;
object_class->set_property = gimp_action_set_property;
object_class->get_property = gimp_action_get_property;
@@ -139,6 +142,16 @@ gimp_action_init (GimpAction *action)
}
static void
+gimp_action_constructed (GObject *object)
+{
+ GimpAction *action = GIMP_ACTION (object);
+
+ g_signal_connect (action, "activate",
+ (GCallback) gimp_action_history_activate_callback,
+ NULL);
+}
+
+static void
gimp_action_finalize (GObject *object)
{
GimpAction *action = GIMP_ACTION (object);
diff --git a/menus/image-menu.xml.in b/menus/image-menu.xml.in
index 5fffd97..4e6c790 100644
--- a/menus/image-menu.xml.in
+++ b/menus/image-menu.xml.in
@@ -750,7 +750,7 @@
<menuitem action="dialogs-tips" />
<menuitem action="dialogs-about" />
<separator />
- <menuitem action="dialogs-action-search" />
+ <menuitem action="help-action-search" />
<separator />
<placeholder name="Programming" />
<separator />
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]