2013-09-26 06:02:59 +12:00
|
|
|
/* GIMP - The GNU Image Manipulation Program
|
2014-02-18 23:24:48 +01:00
|
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
2013-09-26 06:02:59 +12:00
|
|
|
*
|
|
|
|
* gimpaction-history.c
|
2014-02-18 23:24:48 +01:00
|
|
|
* Copyright (C) 2013 Jehan <jehan at girinstud.io>
|
2013-09-26 06:02:59 +12:00
|
|
|
*
|
|
|
|
* 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
|
2018-07-11 23:27:07 +02:00
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2013-09-26 06:02:59 +12:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
2014-07-28 16:44:44 +02:00
|
|
|
#include "libgimpconfig/gimpconfig.h"
|
2018-02-17 08:02:29 -05:00
|
|
|
#include "libgimpmath/gimpmath.h"
|
2013-09-26 06:02:59 +12:00
|
|
|
|
|
|
|
#include "widgets-types.h"
|
|
|
|
|
|
|
|
#include "config/gimpguiconfig.h"
|
|
|
|
|
2014-07-29 12:28:18 +02:00
|
|
|
#include "core/gimp.h"
|
|
|
|
|
2013-09-26 06:02:59 +12:00
|
|
|
#include "gimpaction.h"
|
|
|
|
#include "gimpaction-history.h"
|
|
|
|
|
|
|
|
|
2014-02-18 23:24:48 +01:00
|
|
|
#define GIMP_ACTION_HISTORY_FILENAME "action-history"
|
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
/* History items are stored in a queue, sorted by frequency (number of times
|
2018-02-17 08:02:29 -05:00
|
|
|
* the action was activated), from most frequent to least frequent. Each item,
|
|
|
|
* in addition to the corresponding action name and its index in the queue,
|
|
|
|
* stores a "delta": the difference in frequency between it, and the next item
|
|
|
|
* in the queue; note that the frequency itself is not stored anywhere.
|
|
|
|
*
|
|
|
|
* To keep items from remaining at the top of the queue for too long, the delta
|
|
|
|
* is capped above, such the the maximal delta of the first item is MAX_DELTA,
|
|
|
|
* and the maximal delta of each subsequent item is the maximal delta of the
|
|
|
|
* previous item, times MAX_DELTA_FALLOFF.
|
|
|
|
*
|
2018-02-17 10:01:52 -05:00
|
|
|
* When an action is activated, its frequency grows by 1, meaning that the
|
2018-02-17 08:02:29 -05:00
|
|
|
* delta of the corresponding item is incremented (if below the maximum), and
|
|
|
|
* the delta of the previous item is decremented (if above 0). If the delta of
|
2018-02-17 10:01:52 -05:00
|
|
|
* the previous item is already 0, then, before the above, the current and
|
|
|
|
* previous items swap frequencies, and the current item is moved up the queue
|
|
|
|
* until the preceding item's frequency is greater than 0 (or until it reaches
|
|
|
|
* the front of the queue).
|
2018-02-17 08:02:29 -05:00
|
|
|
*/
|
2018-02-17 10:01:52 -05:00
|
|
|
#define MAX_DELTA 5
|
|
|
|
#define MAX_DELTA_FALLOFF 0.95
|
2018-02-17 08:02:29 -05:00
|
|
|
|
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
HISTORY_ITEM = 1
|
|
|
|
};
|
2014-02-18 23:24:48 +01:00
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
2016-05-16 12:19:24 +00:00
|
|
|
gchar *action_name;
|
2018-02-17 08:02:29 -05:00
|
|
|
gint index;
|
|
|
|
gint delta;
|
2013-09-26 06:02:59 +12:00
|
|
|
} GimpActionHistoryItem;
|
|
|
|
|
2014-02-18 23:24:48 +01:00
|
|
|
static struct
|
|
|
|
{
|
2018-02-17 08:02:29 -05:00
|
|
|
Gimp *gimp;
|
|
|
|
GQueue *items;
|
|
|
|
GHashTable *links;
|
2013-09-26 06:02:59 +12:00
|
|
|
} history;
|
|
|
|
|
2014-02-18 23:24:48 +01:00
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
static GimpActionHistoryItem * gimp_action_history_item_new (const gchar *action_name,
|
|
|
|
gint index,
|
|
|
|
gint delta);
|
|
|
|
static void gimp_action_history_item_free (GimpActionHistoryItem *item);
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
static gint gimp_action_history_item_max_delta (gint index);
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2014-02-18 23:24:48 +01:00
|
|
|
|
2013-09-26 06:02:59 +12:00
|
|
|
/* public functions */
|
|
|
|
|
|
|
|
void
|
2014-07-29 12:28:18 +02:00
|
|
|
gimp_action_history_init (Gimp *gimp)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2014-07-29 12:28:18 +02:00
|
|
|
GimpGuiConfig *config;
|
2014-07-28 16:44:44 +02:00
|
|
|
GFile *file;
|
|
|
|
GScanner *scanner;
|
|
|
|
GTokenType token;
|
2018-02-17 08:02:29 -05:00
|
|
|
gint delta = 0;
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2014-07-29 12:28:18 +02:00
|
|
|
g_return_if_fail (GIMP_IS_GIMP (gimp));
|
|
|
|
|
|
|
|
config = GIMP_GUI_CONFIG (gimp->config);
|
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
if (history.gimp != NULL)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
|
|
|
g_warning ("%s: must be run only once.", G_STRFUNC);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
history.gimp = gimp;
|
|
|
|
history.items = g_queue_new ();
|
|
|
|
history.links = g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
file = gimp_directory_file (GIMP_ACTION_HISTORY_FILENAME, NULL);
|
2014-07-29 18:13:57 +02:00
|
|
|
|
|
|
|
if (gimp->be_verbose)
|
|
|
|
g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
|
|
|
|
|
2019-09-21 12:53:38 +02:00
|
|
|
scanner = gimp_scanner_new_file (file, NULL);
|
2014-07-28 16:44:44 +02:00
|
|
|
g_object_unref (file);
|
2014-02-18 23:24:48 +01:00
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
if (! scanner)
|
2013-09-26 06:02:59 +12:00
|
|
|
return;
|
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
g_scanner_scope_add_symbol (scanner, 0, "history-item",
|
|
|
|
GINT_TO_POINTER (HISTORY_ITEM));
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
token = G_TOKEN_LEFT_PAREN;
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
while (g_scanner_peek_next_token (scanner) == token)
|
|
|
|
{
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
2014-07-28 11:22:20 +02:00
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
switch (token)
|
2014-07-28 11:22:20 +02:00
|
|
|
{
|
2014-07-28 16:44:44 +02:00
|
|
|
case G_TOKEN_LEFT_PAREN:
|
|
|
|
token = G_TOKEN_SYMBOL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case G_TOKEN_SYMBOL:
|
|
|
|
if (scanner->value.v_symbol == GINT_TO_POINTER (HISTORY_ITEM))
|
|
|
|
{
|
2016-05-16 12:19:24 +00:00
|
|
|
gchar *action_name;
|
2014-07-28 16:44:44 +02:00
|
|
|
|
|
|
|
token = G_TOKEN_STRING;
|
|
|
|
|
|
|
|
if (g_scanner_peek_next_token (scanner) != token)
|
|
|
|
break;
|
|
|
|
|
2016-05-16 12:19:24 +00:00
|
|
|
if (! gimp_scanner_parse_string (scanner, &action_name))
|
2014-07-28 16:44:44 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
token = G_TOKEN_INT;
|
|
|
|
|
2016-05-16 12:19:24 +00:00
|
|
|
if (g_scanner_peek_next_token (scanner) != token ||
|
2018-02-17 08:02:29 -05:00
|
|
|
! gimp_scanner_parse_int (scanner, &delta))
|
2016-05-16 12:19:24 +00:00
|
|
|
{
|
|
|
|
g_free (action_name);
|
|
|
|
break;
|
|
|
|
}
|
2014-07-28 16:44:44 +02:00
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
if (! gimp_action_history_is_excluded_action (action_name) &&
|
|
|
|
! g_hash_table_contains (history.links, action_name))
|
2018-01-13 21:26:27 +01:00
|
|
|
{
|
2018-02-17 08:02:29 -05:00
|
|
|
GimpActionHistoryItem *item;
|
|
|
|
|
|
|
|
item = gimp_action_history_item_new (
|
|
|
|
action_name,
|
|
|
|
g_queue_get_length (history.items),
|
|
|
|
delta);
|
|
|
|
|
|
|
|
g_queue_push_tail (history.items, item);
|
2014-07-28 16:44:44 +02:00
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
g_hash_table_insert (history.links,
|
|
|
|
item->action_name,
|
|
|
|
g_queue_peek_tail_link (history.items));
|
2018-01-13 21:26:27 +01:00
|
|
|
}
|
2014-07-28 16:44:44 +02:00
|
|
|
|
2016-05-16 12:19:24 +00:00
|
|
|
g_free (action_name);
|
2014-07-28 16:44:44 +02:00
|
|
|
}
|
|
|
|
token = G_TOKEN_RIGHT_PAREN;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case G_TOKEN_RIGHT_PAREN:
|
|
|
|
token = G_TOKEN_LEFT_PAREN;
|
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
if (g_queue_get_length (history.items) >= config->action_history_size)
|
2014-07-28 16:44:44 +02:00
|
|
|
goto done;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: /* do nothing */
|
|
|
|
break;
|
2014-07-28 11:22:20 +02:00
|
|
|
}
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
done:
|
2019-08-09 12:42:52 +02:00
|
|
|
gimp_scanner_unref (scanner);
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-07-29 12:28:18 +02:00
|
|
|
gimp_action_history_exit (Gimp *gimp)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2014-07-29 12:28:18 +02:00
|
|
|
GimpGuiConfig *config;
|
2014-07-28 11:22:20 +02:00
|
|
|
GimpActionHistoryItem *item;
|
|
|
|
GList *actions;
|
2014-07-28 16:44:44 +02:00
|
|
|
GFile *file;
|
|
|
|
GimpConfigWriter *writer;
|
2014-07-28 11:22:20 +02:00
|
|
|
gint i;
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2014-07-29 12:28:18 +02:00
|
|
|
g_return_if_fail (GIMP_IS_GIMP (gimp));
|
|
|
|
|
|
|
|
config = GIMP_GUI_CONFIG (gimp->config);
|
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
file = gimp_directory_file (GIMP_ACTION_HISTORY_FILENAME, NULL);
|
2014-07-29 18:13:57 +02:00
|
|
|
|
|
|
|
if (gimp->be_verbose)
|
|
|
|
g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
|
|
|
|
|
2019-09-21 12:53:38 +02:00
|
|
|
writer = gimp_config_writer_new_from_file (file, TRUE, "GIMP action-history",
|
|
|
|
NULL);
|
2014-07-28 16:44:44 +02:00
|
|
|
g_object_unref (file);
|
2014-07-28 11:22:20 +02:00
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
for (actions = history.items->head, i = 0;
|
2014-07-28 11:22:20 +02:00
|
|
|
actions && i < config->action_history_size;
|
|
|
|
actions = g_list_next (actions), i++)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2014-07-28 11:22:20 +02:00
|
|
|
item = actions->data;
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
gimp_config_writer_open (writer, "history-item");
|
2016-05-16 12:19:24 +00:00
|
|
|
gimp_config_writer_string (writer, item->action_name);
|
2018-02-17 08:02:29 -05:00
|
|
|
gimp_config_writer_printf (writer, "%d", item->delta);
|
2014-07-28 16:44:44 +02:00
|
|
|
gimp_config_writer_close (writer);
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
2014-07-28 16:44:44 +02:00
|
|
|
gimp_config_writer_finish (writer, "end of action-history", NULL);
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2014-07-29 12:28:18 +02:00
|
|
|
gimp_action_history_clear (gimp);
|
2018-02-17 08:02:29 -05:00
|
|
|
|
|
|
|
g_clear_pointer (&history.links, g_hash_table_unref);
|
|
|
|
g_clear_pointer (&history.items, g_queue_free);
|
|
|
|
history.gimp = NULL;
|
2014-07-29 12:28:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gimp_action_history_clear (Gimp *gimp)
|
|
|
|
{
|
2018-02-17 08:02:29 -05:00
|
|
|
GimpActionHistoryItem *item;
|
|
|
|
|
2014-07-29 12:28:18 +02:00
|
|
|
g_return_if_fail (GIMP_IS_GIMP (gimp));
|
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
g_hash_table_remove_all (history.links);
|
|
|
|
|
|
|
|
while ((item = g_queue_pop_head (history.items)))
|
|
|
|
gimp_action_history_item_free (item);
|
2014-07-29 12:28:18 +02:00
|
|
|
}
|
|
|
|
|
app: show unavailable actions in Action Search after available ones.
Some people had been complaining that they couldn't find some actions in
some case, which was only because they were in states where the actions
were non-sensitive. So it was "normal" (i.e. not a bug), yet I can see
how it can be disturbing especially when we don't realize that an action
is meant to be inactive in some given case.
Of course the option to show all actions already existed in the
Preferences. But as most options in Preferences, this is hardly
discoverable and many people only use default settings. Moreover showing
hidden action made the action search cluttered with non-sensitive
actions in the middle of sensitive ones.
This change gets rid of the "Show unavailable actions" settings and
always show all matching actions. In order not to clutter the list with
useless results, I simply updated the display logics to always show
non-sensitive action after sensitive ones. Note that even non-sensitive
actions will still be ordered in a better-match-on-top logics, yet they
will be after sensitive actions. So the top results will be the best
matches among sensitive actions (action in history), followed by various
levels of matches (actions with matching labels, tooltips, different
order matches, etc.); then they will be followed by best matches among
non-sensitive actions, followed by the same levels of matches as
sensitive ones.
This way, we still keep a very relevant result and there is no need to
have a settings for this.
2020-10-26 16:40:19 +01:00
|
|
|
/**
|
|
|
|
* gimp_action_history_search:
|
|
|
|
* @gimp:
|
|
|
|
* @match_func:
|
|
|
|
* @keyword:
|
2014-07-29 12:28:18 +02:00
|
|
|
*
|
app: show unavailable actions in Action Search after available ones.
Some people had been complaining that they couldn't find some actions in
some case, which was only because they were in states where the actions
were non-sensitive. So it was "normal" (i.e. not a bug), yet I can see
how it can be disturbing especially when we don't realize that an action
is meant to be inactive in some given case.
Of course the option to show all actions already existed in the
Preferences. But as most options in Preferences, this is hardly
discoverable and many people only use default settings. Moreover showing
hidden action made the action search cluttered with non-sensitive
actions in the middle of sensitive ones.
This change gets rid of the "Show unavailable actions" settings and
always show all matching actions. In order not to clutter the list with
useless results, I simply updated the display logics to always show
non-sensitive action after sensitive ones. Note that even non-sensitive
actions will still be ordered in a better-match-on-top logics, yet they
will be after sensitive actions. So the top results will be the best
matches among sensitive actions (action in history), followed by various
levels of matches (actions with matching labels, tooltips, different
order matches, etc.); then they will be followed by best matches among
non-sensitive actions, followed by the same levels of matches as
sensitive ones.
This way, we still keep a very relevant result and there is no need to
have a settings for this.
2020-10-26 16:40:19 +01:00
|
|
|
* Search all history #GimpAction which match @keyword with function
|
|
|
|
* @match_func(action, keyword).
|
|
|
|
* It will also return inactive actions, but will discard non-visible
|
|
|
|
* actions.
|
|
|
|
*
|
|
|
|
* returns: a #GList of #GimpAction, which must be freed with
|
|
|
|
* g_list_free_full (result, (GDestroyNotify) g_object_unref)
|
2014-07-29 12:28:18 +02:00
|
|
|
*/
|
|
|
|
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);
|
|
|
|
|
2016-05-16 12:19:24 +00:00
|
|
|
config = GIMP_GUI_CONFIG (gimp->config);
|
2014-07-29 12:28:18 +02:00
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
for (actions = history.items->head, i = 0;
|
2014-07-29 12:28:18 +02:00
|
|
|
actions && i < config->action_history_size;
|
|
|
|
actions = g_list_next (actions), i++)
|
|
|
|
{
|
2023-01-30 00:19:32 +01:00
|
|
|
GimpActionHistoryItem *item = actions->data;
|
|
|
|
GAction *action;
|
2016-05-16 12:19:24 +00:00
|
|
|
|
2023-01-30 00:19:32 +01:00
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (gimp->app), item->action_name);
|
2016-11-24 21:08:34 +01:00
|
|
|
if (action == NULL)
|
|
|
|
continue;
|
2014-07-29 12:28:18 +02:00
|
|
|
|
2023-01-30 00:19:32 +01:00
|
|
|
g_return_val_if_fail (GIMP_IS_ACTION (action), NULL);
|
|
|
|
if (! gimp_action_is_visible (GIMP_ACTION (action)))
|
2014-07-29 12:28:18 +02:00
|
|
|
continue;
|
|
|
|
|
2023-01-30 00:19:32 +01:00
|
|
|
if (match_func (GIMP_ACTION (action), keyword, NULL, gimp))
|
2014-07-29 12:28:18 +02:00
|
|
|
result = g_list_prepend (result, g_object_ref (action));
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_list_reverse (result);
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:27:24 -05:00
|
|
|
/* gimp_action_history_is_blacklisted_action:
|
2013-09-26 06:02:59 +12:00
|
|
|
*
|
2018-02-17 04:27:24 -05:00
|
|
|
* Returns whether an action should be excluded from both
|
|
|
|
* history and search results.
|
2013-09-26 06:02:59 +12:00
|
|
|
*/
|
|
|
|
gboolean
|
2018-02-17 04:27:24 -05:00
|
|
|
gimp_action_history_is_blacklisted_action (const gchar *action_name)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2014-04-20 15:57:57 +02:00
|
|
|
if (gimp_action_is_gui_blacklisted (action_name))
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
return (g_str_has_suffix (action_name, "-set") ||
|
2013-09-26 06:02:59 +12:00
|
|
|
g_str_has_prefix (action_name, "context-") ||
|
2024-04-17 17:10:18 +00:00
|
|
|
g_str_has_suffix (action_name, "-internal") ||
|
2016-11-03 22:37:13 +01:00
|
|
|
g_str_has_prefix (action_name, "filters-recent-") ||
|
2016-09-12 01:49:08 +02:00
|
|
|
g_strcmp0 (action_name, "dialogs-action-search") == 0);
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
2018-02-17 04:27:24 -05:00
|
|
|
/* gimp_action_history_is_excluded_action:
|
|
|
|
*
|
|
|
|
* Returns whether an action should be excluded from history.
|
|
|
|
*
|
|
|
|
* Some actions should not be logged in the history, but should
|
|
|
|
* otherwise appear in the search results, since they correspond
|
app: exclude undo/redo actions from history
The undo/redo actions' label changes based on context, and may
interfere with the labels of more relevant, but less frequent,
actions.
For example, after applying filter Foo, the label of edit-undo
becomes "Undo Foo", so searching for "Foo" results in both
edit-undo, and the action referring to the filter, with edit-undo
most likely appearing at the top of the list due to its frequency.
Excluding the undo/redo actions from the history is a simple, if
suboptimal, way to fix this.
2018-02-17 04:30:51 -05:00
|
|
|
* to different functions at different times, or since their
|
|
|
|
* label may interfere with more relevant, but less frequent,
|
|
|
|
* actions.
|
2018-02-17 04:27:24 -05:00
|
|
|
*/
|
|
|
|
gboolean
|
|
|
|
gimp_action_history_is_excluded_action (const gchar *action_name)
|
|
|
|
{
|
|
|
|
if (gimp_action_history_is_blacklisted_action (action_name))
|
|
|
|
return TRUE;
|
|
|
|
|
app: exclude undo/redo actions from history
The undo/redo actions' label changes based on context, and may
interfere with the labels of more relevant, but less frequent,
actions.
For example, after applying filter Foo, the label of edit-undo
becomes "Undo Foo", so searching for "Foo" results in both
edit-undo, and the action referring to the filter, with edit-undo
most likely appearing at the top of the list due to its frequency.
Excluding the undo/redo actions from the history is a simple, if
suboptimal, way to fix this.
2018-02-17 04:30:51 -05:00
|
|
|
return (g_strcmp0 (action_name, "edit-undo") == 0 ||
|
|
|
|
g_strcmp0 (action_name, "edit-strong-undo") == 0 ||
|
|
|
|
g_strcmp0 (action_name, "edit-redo") == 0 ||
|
|
|
|
g_strcmp0 (action_name, "edit-strong-redo") == 0 ||
|
|
|
|
g_strcmp0 (action_name, "filters-repeat") == 0 ||
|
2018-02-17 04:27:24 -05:00
|
|
|
g_strcmp0 (action_name, "filters-reshow") == 0);
|
|
|
|
}
|
|
|
|
|
2018-07-06 00:00:16 -04:00
|
|
|
/* Called whenever a GimpAction is activated.
|
|
|
|
* It allows us to log all used actions.
|
2014-02-18 23:24:48 +01:00
|
|
|
*/
|
2013-09-26 06:02:59 +12:00
|
|
|
void
|
2019-07-02 03:54:38 +02:00
|
|
|
gimp_action_history_action_activated (GimpAction *action)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2018-07-05 13:27:02 -04:00
|
|
|
GimpGuiConfig *config;
|
2018-02-17 08:02:29 -05:00
|
|
|
const gchar *action_name;
|
|
|
|
GList *link;
|
|
|
|
GimpActionHistoryItem *item;
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2023-10-26 22:36:16 +02:00
|
|
|
g_return_if_fail (GIMP_IS_ACTION (action));
|
|
|
|
|
2019-07-04 17:04:33 +02:00
|
|
|
/* Silently return when called at the wrong time, like when the
|
|
|
|
* activated action was "quit" and the history is already gone.
|
|
|
|
*/
|
|
|
|
if (! history.gimp)
|
|
|
|
return;
|
2018-07-05 13:27:02 -04:00
|
|
|
|
|
|
|
config = GIMP_GUI_CONFIG (history.gimp->config);
|
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
if (config->action_history_size == 0)
|
|
|
|
return;
|
|
|
|
|
2019-07-02 03:54:38 +02:00
|
|
|
action_name = gimp_action_get_name (action);
|
2013-09-26 06:02:59 +12:00
|
|
|
|
|
|
|
/* Some specific actions are of no log interest. */
|
2018-02-17 04:27:24 -05:00
|
|
|
if (gimp_action_history_is_excluded_action (action_name))
|
2013-09-26 06:02:59 +12:00
|
|
|
return;
|
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
g_return_if_fail (action_name != NULL);
|
|
|
|
|
|
|
|
/* Remove excessive items. */
|
|
|
|
while (g_queue_get_length (history.items) > config->action_history_size)
|
2018-02-17 10:01:52 -05:00
|
|
|
{
|
|
|
|
item = g_queue_pop_tail (history.items);
|
|
|
|
|
|
|
|
g_hash_table_remove (history.links, item->action_name);
|
|
|
|
|
|
|
|
gimp_action_history_item_free (item);
|
|
|
|
}
|
2018-02-17 08:02:29 -05:00
|
|
|
|
|
|
|
/* Look up the action in the history. */
|
|
|
|
link = g_hash_table_lookup (history.links, action_name);
|
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
/* If the action is not in the history, insert it
|
2018-03-24 16:49:01 -04:00
|
|
|
* at the back of the history queue, possibly
|
2018-02-17 10:01:52 -05:00
|
|
|
* replacing the last item.
|
2018-02-17 08:02:29 -05:00
|
|
|
*/
|
2018-02-17 10:01:52 -05:00
|
|
|
if (! link)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2018-02-17 10:01:52 -05:00
|
|
|
if (g_queue_get_length (history.items) == config->action_history_size)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2018-02-17 10:01:52 -05:00
|
|
|
item = g_queue_pop_tail (history.items);
|
2014-07-28 11:22:20 +02:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
g_hash_table_remove (history.links, item->action_name);
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
gimp_action_history_item_free (item);
|
|
|
|
}
|
2014-07-28 11:22:20 +02:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
item = gimp_action_history_item_new (
|
|
|
|
action_name,
|
|
|
|
g_queue_get_length (history.items),
|
|
|
|
0);
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
g_queue_push_tail (history.items, item);
|
|
|
|
link = g_queue_peek_tail_link (history.items);
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
g_hash_table_insert (history.links, item->action_name, link);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
item = link->data;
|
|
|
|
}
|
2014-07-28 11:22:20 +02:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
/* Update the history, according to the logic described
|
|
|
|
* in the comment at the beginning of the file.
|
|
|
|
*/
|
|
|
|
if (item->index > 0)
|
|
|
|
{
|
|
|
|
GList *prev_link = g_list_previous (link);
|
|
|
|
GimpActionHistoryItem *prev_item = prev_link->data;
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
if (prev_item->delta == 0)
|
|
|
|
{
|
|
|
|
for (; prev_link; prev_link = g_list_previous (prev_link))
|
|
|
|
{
|
|
|
|
prev_item = prev_link->data;
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
if (prev_item->delta > 0)
|
|
|
|
break;
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
prev_item->index++;
|
|
|
|
item->index--;
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
prev_item->delta = item->delta;
|
|
|
|
item->delta = 0;
|
|
|
|
}
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
g_queue_unlink (history.items, link);
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
if (prev_link)
|
|
|
|
{
|
|
|
|
link->prev = prev_link;
|
|
|
|
link->next = prev_link->next;
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
link->prev->next = link;
|
|
|
|
link->next->prev = link;
|
2018-02-17 08:02:29 -05:00
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
history.items->length++;
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
2018-02-17 08:02:29 -05:00
|
|
|
else
|
|
|
|
{
|
2018-02-17 10:01:52 -05:00
|
|
|
g_queue_push_head_link (history.items, link);
|
2018-02-17 08:02:29 -05:00
|
|
|
}
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
2018-02-17 10:01:52 -05:00
|
|
|
if (item->index > 0)
|
|
|
|
prev_item->delta--;
|
2018-02-17 08:02:29 -05:00
|
|
|
}
|
2018-02-17 10:01:52 -05:00
|
|
|
|
|
|
|
if (item->delta < gimp_action_history_item_max_delta (item->index))
|
|
|
|
item->delta++;
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
2014-07-28 11:22:20 +02:00
|
|
|
|
2013-09-26 06:02:59 +12:00
|
|
|
/* private functions */
|
|
|
|
|
2014-07-28 11:22:20 +02:00
|
|
|
static GimpActionHistoryItem *
|
2016-05-16 12:19:24 +00:00
|
|
|
gimp_action_history_item_new (const gchar *action_name,
|
2018-02-17 08:02:29 -05:00
|
|
|
gint index,
|
|
|
|
gint delta)
|
2014-07-28 11:22:20 +02:00
|
|
|
{
|
2018-02-17 08:02:29 -05:00
|
|
|
GimpActionHistoryItem *item = g_slice_new (GimpActionHistoryItem);
|
2014-07-28 11:22:20 +02:00
|
|
|
|
2016-05-16 12:19:24 +00:00
|
|
|
item->action_name = g_strdup (action_name);
|
2018-02-17 08:02:29 -05:00
|
|
|
item->index = index;
|
|
|
|
item->delta = CLAMP (delta, 0, gimp_action_history_item_max_delta (index));
|
2014-07-28 11:22:20 +02:00
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2013-09-26 06:02:59 +12:00
|
|
|
static void
|
|
|
|
gimp_action_history_item_free (GimpActionHistoryItem *item)
|
|
|
|
{
|
2016-05-16 12:19:24 +00:00
|
|
|
g_free (item->action_name);
|
2013-09-26 06:02:59 +12:00
|
|
|
|
2018-02-17 08:02:29 -05:00
|
|
|
g_slice_free (GimpActionHistoryItem, item);
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
static gint
|
2018-02-17 08:02:29 -05:00
|
|
|
gimp_action_history_item_max_delta (gint index)
|
2013-09-26 06:02:59 +12:00
|
|
|
{
|
2018-02-17 08:02:29 -05:00
|
|
|
return floor (MAX_DELTA * exp (log (MAX_DELTA_FALLOFF) * index));
|
2013-09-26 06:02:59 +12:00
|
|
|
}
|