which filters out some implementation details but mainly all the tool-specific options actions which only exist as redirect targets for the generic tool opaticy, size, aspect and angle actions. Use the new function from the shortcut editor and from action search so stuff is consistently hidden.
393 lines
12 KiB
C
393 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 <ctype.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
|
|
#include "widgets-types.h"
|
|
|
|
#include "config/gimpguiconfig.h"
|
|
|
|
#include "gimpuimanager.h"
|
|
#include "gimpaction.h"
|
|
#include "gimpaction-history.h"
|
|
|
|
|
|
#define GIMP_ACTION_HISTORY_FILENAME "action-history"
|
|
|
|
|
|
typedef struct
|
|
{
|
|
GtkAction *action;
|
|
gchar *name;
|
|
gint count;
|
|
} GimpActionHistoryItem;
|
|
|
|
static struct
|
|
{
|
|
GimpGuiConfig *config;
|
|
GList *items;
|
|
} history;
|
|
|
|
|
|
static void gimp_action_history_item_free (GimpActionHistoryItem *item);
|
|
|
|
static gint gimp_action_history_init_compare_func (GimpActionHistoryItem *a,
|
|
GimpActionHistoryItem *b);
|
|
static gint gimp_action_history_compare_func (GimpActionHistoryItem *a,
|
|
GimpActionHistoryItem *b);
|
|
|
|
static void gimp_action_history_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 = gimp_personal_rc_file (GIMP_ACTION_HISTORY_FILENAME);
|
|
|
|
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_history_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 = gimp_personal_rc_file (GIMP_ACTION_HISTORY_FILENAME);
|
|
|
|
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)
|
|
{
|
|
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;
|
|
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_is_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_history_insert (const gchar *action_name,
|
|
gint count)
|
|
{
|
|
GList *action_groups;
|
|
GimpUIManager *manager;
|
|
|
|
/* We do not insert some categories of actions. */
|
|
if (gimp_action_history_excluded_action (action_name))
|
|
return;
|
|
|
|
manager = gimp_ui_managers_from_name ("<Image>")->data;
|
|
|
|
for (action_groups = gtk_ui_manager_get_action_groups (GTK_UI_MANAGER (manager));
|
|
action_groups;
|
|
action_groups = g_list_next (action_groups))
|
|
{
|
|
GimpActionGroup *group = action_groups->data;
|
|
GList *actions;
|
|
GList *list2;
|
|
gboolean found = FALSE;
|
|
|
|
actions = gtk_action_group_list_actions (GTK_ACTION_GROUP (group));
|
|
actions = g_list_sort (actions, (GCompareFunc) gimp_action_name_compare);
|
|
|
|
for (list2 = actions; list2; list2 = g_list_next (list2))
|
|
{
|
|
GtkAction *action = list2->data;
|
|
gint unavailable_action = -1;
|
|
|
|
unavailable_action = strcmp (action_name, gtk_action_get_name (action));
|
|
|
|
if (unavailable_action == 0)
|
|
{
|
|
/* We found our action. */
|
|
GimpActionHistoryItem *new_action = g_new0 (GimpActionHistoryItem, 1);
|
|
|
|
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;
|
|
}
|
|
}
|