Files
gimp/app/widgets/gimpaction-history.c
Jehan b9a9169656 app: update action search to use glib API.
I now normalize with g_str_tokenize_and_fold() which uses standard
Unicode normalization.
I don't use g_str_match_string() directly though, because I want to
run additional checks to order the results by relevance. For instance
I still want actions whose labels starts with the search string to be
at the top, and results with same order as search token before those
with a different order. Then results with match in the tooltip. Finally
I also returns results with partial match in the label, and the rest in
the tooltip, though at the bottom of the list.
Other than that, this returns the same results as g_str_match_string()
with a similar algorithm. In particular now we only match the start of
tokens (a substring in the middle of a token won't match anymore).
I kept the small 2-character trick matching the first letters of the
first 2 words of the label, but I got rid of the fuzzy search (that none
really found ever relevant anyway).
2014-09-16 04:03:43 +02:00

426 lines
12 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpaction-history.c
* Copyright (C) 2013 Jehan <jehan at girinstud.io>
*
* 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 <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"
#include "widgets-types.h"
#include "config/gimpguiconfig.h"
#include "core/gimp.h"
#include "gimpuimanager.h"
#include "gimpaction.h"
#include "gimpaction-history.h"
#define GIMP_ACTION_HISTORY_FILENAME "action-history"
enum
{
HISTORY_ITEM = 1
};
typedef struct
{
GtkAction *action;
const gchar *name;
gint count;
} GimpActionHistoryItem;
static struct
{
GList *items;
} history;
static GimpActionHistoryItem *
gimp_action_history_item_new (GtkAction *action,
gint count);
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);
/* public functions */
void
gimp_action_history_init (Gimp *gimp)
{
GimpGuiConfig *config;
GimpUIManager *manager;
GFile *file;
GScanner *scanner;
GTokenType token;
gint count = 0;
gint n_items = 0;
g_return_if_fail (GIMP_IS_GIMP (gimp));
config = GIMP_GUI_CONFIG (gimp->config);
if (history.items != NULL)
{
g_warning ("%s: must be run only once.", G_STRFUNC);
return;
}
file = gimp_directory_file (GIMP_ACTION_HISTORY_FILENAME, NULL);
if (gimp->be_verbose)
g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
scanner = gimp_scanner_new_gfile (file, NULL);
g_object_unref (file);
if (! scanner)
return;
manager = gimp_ui_managers_from_name ("<Image>")->data;
g_scanner_scope_add_symbol (scanner, 0, "history-item",
GINT_TO_POINTER (HISTORY_ITEM));
token = G_TOKEN_LEFT_PAREN;
while (g_scanner_peek_next_token (scanner) == token)
{
token = g_scanner_get_next_token (scanner);
switch (token)
{
case G_TOKEN_LEFT_PAREN:
token = G_TOKEN_SYMBOL;
break;
case G_TOKEN_SYMBOL:
if (scanner->value.v_symbol == GINT_TO_POINTER (HISTORY_ITEM))
{
GtkAction *action;
gchar *name;
token = G_TOKEN_STRING;
if (g_scanner_peek_next_token (scanner) != token)
break;
if (! gimp_scanner_parse_string (scanner, &name))
break;
action = gimp_ui_manager_find_action (manager, NULL, name);
g_free (name);
token = G_TOKEN_INT;
if (g_scanner_peek_next_token (scanner) != token)
break;
if (! gimp_scanner_parse_int (scanner, &count))
break;
if (action)
{
history.items =
g_list_insert_sorted (history.items,
gimp_action_history_item_new (action, count),
(GCompareFunc) gimp_action_history_init_compare_func);
n_items++;
}
}
token = G_TOKEN_RIGHT_PAREN;
break;
case G_TOKEN_RIGHT_PAREN:
token = G_TOKEN_LEFT_PAREN;
if (n_items >= config->action_history_size)
goto done;
break;
default: /* do nothing */
break;
}
}
done:
gimp_scanner_destroy (scanner);
if (count > 1)
{
GList *actions;
gint i;
for (actions = history.items, i = 0;
actions && i < config->action_history_size;
actions = g_list_next (actions), i++)
{
GimpActionHistoryItem *action = actions->data;
action->count -= count - 1;
}
}
}
void
gimp_action_history_exit (Gimp *gimp)
{
GimpGuiConfig *config;
GimpActionHistoryItem *item;
GList *actions;
GFile *file;
GimpConfigWriter *writer;
gint min_count = 0;
gint i;
g_return_if_fail (GIMP_IS_GIMP (gimp));
config = GIMP_GUI_CONFIG (gimp->config);
/* If we have more items than current history size, trim the history
* and move down all count so that 1 is lower.
*/
item = g_list_nth_data (history.items, config->action_history_size);
if (item)
min_count = item->count - 1;
file = gimp_directory_file (GIMP_ACTION_HISTORY_FILENAME, NULL);
if (gimp->be_verbose)
g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
writer = gimp_config_writer_new_gfile (file, TRUE, "GIMP action-history",
NULL);
g_object_unref (file);
for (actions = history.items, i = 0;
actions && i < config->action_history_size;
actions = g_list_next (actions), i++)
{
item = actions->data;
gimp_config_writer_open (writer, "history-item");
gimp_config_writer_string (writer, item->name);
gimp_config_writer_printf (writer, "%d", item->count - min_count);
gimp_config_writer_close (writer);
}
gimp_config_writer_finish (writer, "end of action-history", NULL);
gimp_action_history_clear (gimp);
}
void
gimp_action_history_clear (Gimp *gimp)
{
g_return_if_fail (GIMP_IS_GIMP (gimp));
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 (Gimp *gimp,
GimpActionMatchFunc match_func,
const gchar *keyword)
{
GimpGuiConfig *config;
GList *actions;
GList *result = NULL;
gint i;
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
g_return_val_if_fail (match_func != NULL, NULL);
config = GIMP_GUI_CONFIG (gimp->config);
for (actions = history.items, i = 0;
actions && i < config->action_history_size;
actions = g_list_next (actions), i++)
{
GimpActionHistoryItem *item = actions->data;
GtkAction *action = item->action;
if (! gtk_action_is_sensitive (action) &&
! config->search_show_unavailable)
continue;
if (match_func (action, keyword, NULL, gimp))
result = g_list_prepend (result, g_object_ref (action));
}
return g_list_reverse (result);
}
/* gimp_action_history_excluded_action:
*
* Returns whether an action should be excluded from history.
*/
gboolean
gimp_action_history_excluded_action (const gchar *action_name)
{
if (gimp_action_is_gui_blacklisted (action_name))
return TRUE;
return (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;
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))
{
GimpActionHistoryItem *item = actions->data;
if (g_strcmp0 (action_name, item->name) == 0)
{
GimpActionHistoryItem *next_item;
next_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 == item->count ||
(next_item && next_item->count == item->count))
{
item->count++;
history.items = g_list_remove (history.items, item);
history.items = g_list_insert_sorted (history.items, item,
(GCompareFunc) gimp_action_history_compare_func);
}
else if (previous_count != 0 &&
previous_count != item->count)
{
GimpActionHistoryItem *previous_item = g_list_previous (actions)->data;
item->count++;
history.items = g_list_remove (history.items, item);
history.items = g_list_insert_sorted (history.items, item,
(GCompareFunc) gimp_action_history_compare_func);
previous_item->count--;
history.items = g_list_remove (history.items, previous_item);
history.items = g_list_insert_sorted (history.items, previous_item,
(GCompareFunc) gimp_action_history_compare_func);
}
return;
}
previous_count = item->count;
}
/* If we are here, this action is not logged yet. */
history.items =
g_list_insert_sorted (history.items,
gimp_action_history_item_new (action, 1),
(GCompareFunc) gimp_action_history_compare_func);
}
/* private functions */
static GimpActionHistoryItem *
gimp_action_history_item_new (GtkAction *action,
gint count)
{
GimpActionHistoryItem *item = g_new0 (GimpActionHistoryItem, 1);
item->action = g_object_ref (action);
item->name = gtk_action_get_name (action);
item->count = count;
return item;
}
static void
gimp_action_history_item_free (GimpActionHistoryItem *item)
{
g_object_unref (item->action);
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);
}