diff --git a/app/actions/dialogs-actions.c b/app/actions/dialogs-actions.c index 735825604c..c7686f871c 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 fecce137e5..f76330da0b 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"), "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 82d8a2fce6..1d4af8d46d 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,17 @@ 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); + + 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 7d94206beb..c7507e197d 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 ff5dff7df0..5fd934a80d 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 e7eed9f4fb..6cbe87a233 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 6c2acd0117..093a6e2229 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 9c0f1b1a24..b12629b814 100644 --- a/app/dialogs/action-search-dialog.c +++ b/app/dialogs/action-search-dialog.c @@ -18,244 +18,277 @@ #include "config.h" #include -#include - -#include +#include #include #include #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 +typedef struct +{ + GtkWidget *dialog; -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); + GimpGuiConfig *config; + GtkWidget *keyword_entry; + GtkWidget *results_list; + GtkWidget *list_view; -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); + gint x; + gint y; + gint width; + gint height; +} SearchDialog; -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); +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); -gboolean action_search_initializer (void); -void action_search_finalizer (void); -static void action_search_context_menu (void); +static void action_search_finalizer (SearchDialog *private); -static GtkWidget * action_search_dialog; -static GtkWidget * results_list; -static GtkWidget * keyword_entry; +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); -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 { +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))) + if (! private) { - gtk_widget_set_sensitive (PREF_UI.pos_x_hbox, TRUE); - gtk_widget_set_sensitive (PREF_UI.pos_y_hbox, TRUE); + 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); } - else + + /* 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; + + session_info = + gimp_dialog_factory_find_session_info (gimp_dialog_factory_get_singleton(), + "gimp-action-search-dialog"); + + if (session_info) { - gtk_widget_set_sensitive (PREF_UI.pos_x_hbox, FALSE); - gtk_widget_set_sensitive (PREF_UI.pos_y_hbox, FALSE); + 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); } - 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)))); + + if (private->width < 0) + { + private->width = parent_width / 2; + } + else if (private->width > screen_width) + { + private->width = parent_width; + } + + 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 ; + } + + gtk_window_set_default_size (GTK_WINDOW (private->dialog), private->width, 1); + gtk_widget_show (private->dialog); + + return private->dialog; } +/* Private Functions */ static void -action_search_clear_history_button_clicked (GtkButton *button, - gpointer user_data) +key_released (GtkWidget *widget, + GdkEventKey *event, + SearchDialog *private) { - action_search_clear_history (); -} + gchar *entry_text; + gint width; -static void -restore_defaults_button_clicked (GtkButton *button, - gpointer user_data) -{ - action_search_set_default_preferences (); - action_search_set_prefereces_ui_values (); -} - -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); - - modify_position_spins (); - - 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); - -} - -static gboolean -on_focus_out (GtkWidget *widget, - GdkEventFocus *event, - gpointer data) -{ - if (! gtk_widget_is_focus (GTK_WIDGET (data))) - action_search_finalizer (); - - return TRUE; -} - - -static void -key_released (GtkWidget *widget, - GdkEventKey *event, - gpointer func_data) -{ - const gchar *entry_text; - GtkWidget *list_view = GTK_WIDGET (func_data); - - 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_Escape: + { + action_search_finalizer (private); + return; + } + case GDK_Return: + { + action_search_run_selected (private); + return; + } } - case GDK_Return: - { - action_search_run_result_action (); - 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] = ""; + 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; - if (data == NULL || - strchr (label, '@') != NULL || - strchr (label, '&') != NULL) - return; + 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%s%s%s%s", + 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%s%s", - 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 ("")->data; - if (strcmp (keyword, "") == 0) + if (g_strcmp0 (keyword, "") == 0) return; - for (i = 0;iconfig); + /* 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); + const gchar *name; + GtkAction *action = list2->data; + gboolean is_redundant = FALSE; + gint section; + name = gtk_action_get_name (action); - if (strstr (name, "-menu") || - strstr (name, "-popup") || - strstr (name, "context") || - strstr (name, "edit-undo") || - name[0] == '<') + /* 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;idata)), 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); - } - } - g_list_free (actions); - } -} - -static void action_search_fill_history (void) -{ - GList *list; - GimpUIManager *manager; - gint i = 0; - manager = gimp_ui_managers_from_name ("")->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;i2 || strcmp (key, " ") == 0) + if (! matched) { - if (gtk_action_get_tooltip (action)!= NULL) - { - strcpy (tooltip, gtk_action_get_tooltip (action)); - for (i = 0;i 2) + { + gchar *tooltip = NULL; + + if (gtk_action_get_tooltip (action)!= NULL) + { + tooltip = action_search_normalize_string (gtk_action_get_tooltip (action)); + + if (strstr (tooltip, key)) + { + matched = TRUE; + if (section) + { + *section = 3; + } + } + } + + if (! matched && strchr (key, ' ')) + { + gchar *key_copy = g_strdup (key); + gchar *words = key_copy; + gchar *word; + + matched = TRUE; + if (section) + { + *section = 4; + } + + while ((word = strsep (&words, " ")) != NULL) + { + if (! strstr (label, word) && (! tooltip || ! strstr (tooltip, word))) + { + matched = FALSE; + break; + } + } + + g_free (key_copy); + } + + g_free (tooltip); + } + + if (! matched && match_fuzzy && action_fuzzy_match (label, key)) + { + matched = TRUE; + if (section) + { + *section = 5; + } } } g_free (label); g_free (key); - g_free (tooltip); + + return matched; +} + +void +action_search_finalizer (SearchDialog *private) +{ + if (GTK_IS_WIDGET (private->dialog)) + { + 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); + } +} + +static gboolean +window_configured (GtkWindow *window, + GdkEvent *event, + SearchDialog *private) +{ + if (event->type == GDK_CONFIGURE && + gtk_widget_get_visible (GTK_WIDGET (window))) + { + gint x, y, width, height; + + /* 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; + + gtk_window_get_size (GTK_WINDOW (private->dialog), &width, &height); + private->width = width; + + if (gtk_widget_get_visible (private->list_view)) + { + private->height = height; + } + } return FALSE; } static void -action_search_read_history (void) +window_shown (GtkWidget *widget, + SearchDialog *private) { - gint i; - FILE *fp; - cur_no_of_his_actions = 0; - - fp = fopen (history_file_path, "r"); - if (fp == NULL) - return; - - for (i = 0;icount - p->count); + gtk_window_move (GTK_WINDOW (widget), + private->x, private->y); } static void -action_search_update_history (GtkAction *action) -{ - gint i; - FILE *fp; - gboolean is_present = FALSE; - - fp = fopen (history_file_path, "w"); - if (fp == NULL) - { - g_message ("Unable to open history file to write"); - return; - } - - for (i = 0;i/dialogs/dialogs-action-search", 'd', 0, FALSE); - return TRUE; -} - -static void -action_search_clear_history (void) -{ - FILE *fp; - fp = fopen (history_file_path, "w"); - if (fp == NULL) - { - g_message ("file not created"); - return; - } - 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) -{ - 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 - { - 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"); - - 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 (); - - fclose (fp); -} - -static gboolean -context_menu_invoked (GtkWidget *widget, - GdkEvent *event, - gpointer user_data) -{ - action_search_context_menu (); - return TRUE; -} - -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); -} - -static void -action_search_context_menu (void) -{ - 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 ()); -} - -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 162efe436e..6bc2c40557 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 09e2448414..706d63847a 100644 --- a/app/dialogs/dialogs-constructors.c +++ b/app/dialogs/dialogs-constructors.c @@ -132,6 +132,15 @@ dialogs_file_export_new (GimpDialogFactory *factory, return file_save_dialog_new (context->gimp, TRUE); } +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, @@ -195,15 +204,6 @@ dialogs_about_get (GimpDialogFactory *factory, return about_dialog_create (context); } -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, diff --git a/app/dialogs/dialogs-constructors.h b/app/dialogs/dialogs-constructors.h index 53b8d53f61..bb0a526135 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 9a4f393bdd..42ec649396 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 be14267a47..efb8624487 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, @@ -645,6 +648,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 40e471e97c..7eb3d44982 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 80bb889bb7..394891078b 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 0000000000..4da02bda42 --- /dev/null +++ b/app/widgets/gimpaction-history.c @@ -0,0 +1,374 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 2013 Jehan + * + * 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 . + */ + +#include "config.h" + +#include +#include + +#include + +#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 ("")->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 0000000000..c0ca984a2e --- /dev/null +++ b/app/widgets/gimpaction-history.h @@ -0,0 +1,42 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 2013 Jehan + * + * 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 . + */ + +#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 ff3db4c290..ffc68fb0de 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; @@ -138,6 +141,16 @@ gimp_action_init (GimpAction *action) NULL); } +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) { diff --git a/menus/image-menu.xml.in b/menus/image-menu.xml.in index 5fffd97ade..4e6c790a03 100644 --- a/menus/image-menu.xml.in +++ b/menus/image-menu.xml.in @@ -750,7 +750,7 @@ - +