widgets: Add UI for pad configuration

The UI is heavily inspired in the existing one for midi devices
and the such, as the restrictions are somewhat similar. Since there
is not enough information to introspect the device without the help
of libwacom (and the UI should work with tablets unsupported by
it, regardless) the list starts empty, and there exists a "grab
event" button to press pad buttons (or use rings/strips) and
create/focus a list item for the button/mode.

Double clicking on an action (or pressing the "edit" button) spawns
a different dialog with a GimpActionEditor to select an action.
And lastly, actions can be deleted with the "delete" button.

Pads may have different modes (e.g. leds in the tablet) that apply
to all pad features, the list will allow different actions to be
set on the same button in different modes. This basically multiplies
the amount of mappable actions by the number of available modes.
This commit is contained in:
Carlos Garnacho 2023-06-13 21:10:10 +02:00 committed by Jehan
parent 8182f7d683
commit 573d2f5380
2 changed files with 621 additions and 0 deletions

View File

@ -255,6 +255,7 @@ static const GimpDialogFactoryEntry entries[] =
FOREIGN ("gimp-controller-editor-dialog", FALSE, TRUE),
FOREIGN ("gimp-controller-action-dialog", FALSE, TRUE),
FOREIGN ("gimp-pad-action-dialog", FALSE, TRUE),
/* ordinary toplevels */
TOPLEVEL ("gimp-image-new-dialog",

View File

@ -28,11 +28,18 @@
#include "widgets-types.h"
#include "core/gimp.h"
#include "core/gimpcurve.h"
#include "core/gimppadactions.h"
#include "gimpactioneditor.h"
#include "gimpactionview.h"
#include "gimpcurveview.h"
#include "gimpdeviceinfo.h"
#include "gimpdeviceinfoeditor.h"
#include "gimpdialogfactory.h"
#include "gimphelp-ids.h"
#include "gimpviewabledialog.h"
#include "gimp-intl.h"
@ -71,6 +78,16 @@ enum
KEY_N_COLUMNS
};
enum
{
PAD_COLUMN_TYPE,
PAD_COLUMN_NUMBER,
PAD_COLUMN_MODE,
PAD_COLUMN_ACTION_ICON,
PAD_COLUMN_ACTION_NAME,
PAD_N_COLUMNS
};
typedef struct _GimpDeviceInfoEditorPrivate GimpDeviceInfoEditorPrivate;
@ -84,6 +101,16 @@ struct _GimpDeviceInfoEditorPrivate
GtkListStore *axis_store;
GtkListStore *pad_store;
GtkWidget *pad_action_view;
GtkWidget *pad_grab_button;
GtkWidget *pad_edit_button;
GtkWidget *pad_delete_button;
GtkWidget *pad_edit_dialog;
GtkTreeSelection *pad_sel;
GtkTreeSelection *pad_edit_sel;
GtkWidget *notebook;
};
@ -119,6 +146,38 @@ static gboolean gimp_device_info_editor_foreach (GtkTreeModel *model
GtkTreeIter *iter,
gpointer data);
static void gimp_device_info_pad_name_column_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *tree_model,
GtkTreeIter *iter,
gpointer data);
static void gimp_device_info_editor_sel_changed (GtkTreeSelection *sel,
GimpDeviceInfoEditor *editor);
static void gimp_device_info_editor_pad_row_edit (GtkTreeView *view,
GtkTreePath *path,
GtkTreeViewColumn *column,
GimpDeviceInfoEditor *editor);
static void gimp_device_info_editor_grab_toggled (GtkWidget *button,
GimpDeviceInfoEditor *editor);
static void gimp_device_info_editor_edit_clicked (GtkWidget *button,
GimpDeviceInfoEditor *editor);
static void gimp_device_info_editor_delete_clicked (GtkWidget *button,
GimpDeviceInfoEditor *editor);
static void gimp_device_info_editor_edit_activated (GtkTreeView *tv,
GtkTreePath *path,
GtkTreeViewColumn *column,
GimpDeviceInfoEditor *editor);
static void gimp_device_info_editor_edit_response (GtkWidget *dialog,
gint response_id,
GimpDeviceInfoEditor *editor);
static void gimp_device_info_editor_pad_action_foreach (GimpPadActions *pad_actions,
GimpPadActionType action_type,
guint number,
guint mode,
const gchar *action,
gpointer data);
G_DEFINE_TYPE_WITH_PRIVATE (GimpDeviceInfoEditor, gimp_device_info_editor,
GTK_TYPE_BOX)
@ -182,6 +241,7 @@ gimp_device_info_editor_constructed (GObject *object)
GtkWidget *combo;
GtkWidget *view;
GtkTreeSelection *sel;
GtkTreeViewColumn *column;
GtkCellRenderer *cell;
GtkTreeIter axis_iter;
gboolean has_axes = FALSE;
@ -470,6 +530,128 @@ gimp_device_info_editor_constructed (GObject *object)
if (has_axes)
gtk_tree_selection_select_iter (sel, &axis_iter);
}
if (gimp_device_info_get_source (private->info) == GDK_SOURCE_TABLET_PAD)
{
GimpPadActions *pad_actions;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *sw;
frame = gimp_frame_new (_("Pad Actions"));
gtk_box_pack_start (GTK_BOX (private->vbox), frame, TRUE, TRUE, 0);
gtk_widget_show (frame);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_add (GTK_CONTAINER (frame), vbox);
gtk_widget_show (vbox);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
gtk_widget_show (sw);
private->pad_action_view = gtk_tree_view_new ();
gtk_container_add (GTK_CONTAINER (sw), private->pad_action_view);
gtk_widget_show (private->pad_action_view);
g_signal_connect (private->pad_action_view, "row-activated",
G_CALLBACK (gimp_device_info_editor_pad_row_edit),
editor);
gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (private->pad_action_view),
-1, _("Event"),
gtk_cell_renderer_text_new (),
gimp_device_info_pad_name_column_func,
NULL, NULL);
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (private->pad_action_view),
-1, _("Mode"),
gtk_cell_renderer_text_new (),
"text", PAD_COLUMN_MODE,
NULL);
column = gtk_tree_view_column_new ();
gtk_tree_view_column_set_title (column, _("Action"));
gtk_tree_view_append_column (GTK_TREE_VIEW (private->pad_action_view),
column);
cell = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_column_pack_start (column, cell, FALSE);
gtk_tree_view_column_set_attributes (column, cell,
"icon-name", PAD_COLUMN_ACTION_ICON,
NULL);
cell = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start (column, cell, TRUE);
gtk_tree_view_column_set_attributes (column, cell,
"text", PAD_COLUMN_ACTION_NAME,
NULL);
private->pad_store = gtk_list_store_new (PAD_N_COLUMNS,
/* Pad feature type */
G_TYPE_UINT,
/* Number of pad feature */
G_TYPE_UINT,
/* Mode that applies. */
G_TYPE_UINT,
/* Action icon name. */
G_TYPE_STRING,
/* Action name. */
G_TYPE_STRING);
pad_actions = gimp_device_info_get_pad_actions (private->info);
gimp_pad_actions_foreach (pad_actions, gimp_device_info_editor_pad_action_foreach,
private->pad_store);
gtk_tree_view_set_model (GTK_TREE_VIEW (private->pad_action_view),
GTK_TREE_MODEL (private->pad_store));
private->pad_sel =
gtk_tree_view_get_selection (GTK_TREE_VIEW (private->pad_action_view));
g_signal_connect (private->pad_sel, "changed",
G_CALLBACK (gimp_device_info_editor_sel_changed),
editor);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
private->pad_grab_button = gtk_toggle_button_new_with_mnemonic (_("_Grab event"));
gtk_box_pack_start (GTK_BOX (hbox), private->pad_grab_button, TRUE, TRUE, 0);
gtk_widget_show (private->pad_grab_button);
g_signal_connect (private->pad_grab_button, "toggled",
G_CALLBACK (gimp_device_info_editor_grab_toggled),
editor);
gimp_help_set_help_data (private->pad_grab_button,
_("Select the next event arriving from "
"the pad"),
NULL);
private->pad_edit_button =
gtk_button_new_with_mnemonic (_("_Edit event"));
gtk_box_pack_start (GTK_BOX (hbox), private->pad_edit_button,
TRUE, TRUE, 0);
gtk_widget_show (private->pad_edit_button);
g_signal_connect (private->pad_edit_button, "clicked",
G_CALLBACK (gimp_device_info_editor_edit_clicked),
editor);
private->pad_delete_button =
gtk_button_new_with_mnemonic (_("_Clear event"));
gtk_box_pack_start (GTK_BOX (hbox), private->pad_delete_button,
TRUE, TRUE, 0);
gtk_widget_show (private->pad_delete_button);
g_signal_connect (private->pad_delete_button, "clicked",
G_CALLBACK (gimp_device_info_editor_delete_clicked),
editor);
gtk_widget_set_sensitive (private->pad_edit_button, FALSE);
gtk_widget_set_sensitive (private->pad_delete_button, FALSE);
}
}
static void
@ -682,6 +864,444 @@ gimp_device_info_editor_foreach (GtkTreeModel *model,
return FALSE;
}
static gchar *
gimp_device_info_get_pad_feature_name (GimpPadActionType type,
guint number)
{
gchar *str = NULL;
switch (type)
{
case GIMP_PAD_ACTION_BUTTON:
str = g_strdup_printf (_("Button %d"), number + 1);
break;
case GIMP_PAD_ACTION_RING:
str = g_strdup_printf (_("Ring %d"), number + 1);
break;
case GIMP_PAD_ACTION_STRIP:
str = g_strdup_printf (_("Strip %d"), number + 1);
break;
default:
break;
}
return str;
}
static void
gimp_device_info_pad_name_column_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *tree_model,
GtkTreeIter *iter,
gpointer data)
{
GimpPadActionType type;
guint number;
gchar *str = NULL;
gtk_tree_model_get (tree_model, iter,
PAD_COLUMN_TYPE, &type,
PAD_COLUMN_NUMBER, &number,
-1);
str = gimp_device_info_get_pad_feature_name (type, number);
g_object_set (G_OBJECT (cell), "text", str, NULL);
g_free (str);
}
static void
gimp_device_info_editor_sel_changed (GtkTreeSelection *sel,
GimpDeviceInfoEditor *editor)
{
GimpDeviceInfoEditorPrivate *private;
GtkTreeModel *model;
GtkTreeIter iter;
gchar *edit_help = NULL;
gchar *delete_help = NULL;
gboolean edit_sensitive = FALSE;
gboolean delete_sensitive = FALSE;
private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor);
if (gtk_tree_selection_get_selected (sel, &model, &iter))
{
GimpPadActionType type;
guint number;
gchar *action = NULL;
gchar *event;
gtk_tree_model_get (model, &iter,
PAD_COLUMN_TYPE, &type,
PAD_COLUMN_NUMBER, &number,
PAD_COLUMN_ACTION_NAME, &action,
-1);
event = gimp_device_info_get_pad_feature_name (type, number);
if (action)
{
g_free (action);
delete_sensitive = TRUE;
delete_help =
g_strdup_printf (_("Remove the action assigned to '%s'"), event);
}
edit_sensitive = TRUE;
edit_help = g_strdup_printf (_("Assign an action to '%s'"), event);
}
gimp_help_set_help_data (private->pad_edit_button, edit_help, NULL);
gtk_widget_set_sensitive (private->pad_edit_button, edit_sensitive);
g_free (edit_help);
gimp_help_set_help_data (private->pad_delete_button, delete_help, NULL);
gtk_widget_set_sensitive (private->pad_delete_button, delete_sensitive);
g_free (delete_help);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (private->pad_grab_button),
FALSE);
}
static void
gimp_device_info_editor_pad_row_edit (GtkTreeView *view,
GtkTreePath *path,
GtkTreeViewColumn *column,
GimpDeviceInfoEditor *editor)
{
GimpDeviceInfoEditorPrivate *private;
private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor);
if (gtk_widget_is_sensitive (private->pad_edit_button))
gtk_button_clicked (GTK_BUTTON (private->pad_edit_button));
}
static void
gimp_device_info_editor_pad_row_ensure (GimpDeviceInfoEditor *editor,
GimpPadActionType type,
guint number,
guint mode)
{
GimpDeviceInfoEditorPrivate *private;
GtkTreeView *view;
GtkTreeModel *tree_model;
GtkTreePath *path;
GtkTreeIter iter;
gint insert_pos = 0;
gboolean iter_valid;
private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor);
tree_model = GTK_TREE_MODEL (private->pad_store);
for (iter_valid = gtk_tree_model_get_iter_first (tree_model, &iter);
iter_valid;
iter_valid = gtk_tree_model_iter_next (tree_model, &iter))
{
guint row_type;
guint row_number;
guint row_mode;
gtk_tree_model_get (tree_model, &iter,
PAD_COLUMN_TYPE, &row_type,
PAD_COLUMN_NUMBER, &row_number,
PAD_COLUMN_MODE, &row_mode,
-1);
if (type == row_type && number == row_number && mode == row_mode)
goto select_item;
if (row_type > type ||
(row_type == type && row_number > number) ||
(row_type == type && row_number == number && row_mode > mode))
break;
insert_pos++;
}
gtk_list_store_insert_with_values (private->pad_store, &iter, insert_pos,
PAD_COLUMN_TYPE, type,
PAD_COLUMN_NUMBER, number,
PAD_COLUMN_MODE, mode,
-1);
select_item:
view = GTK_TREE_VIEW (private->pad_action_view);
path = gtk_tree_model_get_path (tree_model, &iter);
gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0.0, 0.0);
gtk_tree_view_set_cursor (view, path, NULL, FALSE);
gtk_tree_path_free (path);
gtk_widget_grab_focus (private->pad_action_view);
}
static gboolean
gimp_device_info_editor_pad_events (GtkWidget *widget,
GdkEvent *event,
GimpDeviceInfoEditor *editor)
{
switch (event->type)
{
case GDK_PAD_BUTTON_RELEASE:
gimp_device_info_editor_pad_row_ensure (editor,
GIMP_PAD_ACTION_BUTTON,
event->pad_button.button,
event->pad_button.mode);
break;
case GDK_PAD_RING:
gimp_device_info_editor_pad_row_ensure (editor,
GIMP_PAD_ACTION_RING,
event->pad_axis.index,
event->pad_axis.mode);
break;
case GDK_PAD_STRIP:
gimp_device_info_editor_pad_row_ensure (editor,
GIMP_PAD_ACTION_STRIP,
event->pad_axis.index,
event->pad_axis.mode);
break;
default:
break;
}
return GDK_EVENT_PROPAGATE;
}
static void
gimp_device_info_editor_grab_toggled (GtkWidget *button,
GimpDeviceInfoEditor *editor)
{
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
{
g_signal_connect (gtk_widget_get_toplevel (button), "event",
G_CALLBACK (gimp_device_info_editor_pad_events),
editor);
}
else
{
g_signal_handlers_disconnect_by_func (gtk_widget_get_toplevel (button),
gimp_device_info_editor_pad_events,
editor);
}
}
static void
gimp_device_info_editor_edit_clicked (GtkWidget *button,
GimpDeviceInfoEditor *editor)
{
GimpDeviceInfoEditorPrivate *private;
GtkTreeModel *model;
GtkTreeIter iter;
GtkWidget *view;
private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (private->pad_grab_button),
FALSE);
if (gtk_tree_selection_get_selected (private->pad_sel, &model, &iter))
{
GimpContext *user_context;
GimpPadActionType type;
guint number;
gchar *label;
gchar *title;
gchar *action_name;
gtk_tree_model_get (model, &iter,
PAD_COLUMN_TYPE, &type,
PAD_COLUMN_NUMBER, &number,
PAD_COLUMN_ACTION_NAME, &action_name,
-1);
label = gimp_device_info_get_pad_feature_name (type, number);
title = g_strdup_printf (_("Select Action for Event '%s'"),
label);
g_free (label);
user_context =
gimp_get_user_context (GIMP_TOOL_PRESET (private->info)->gimp);
g_set_weak_pointer
(&private->pad_edit_dialog,
gimp_viewable_dialog_new (g_list_prepend (NULL, NULL), user_context,
_("Select Pad Event Action"),
"gimp-pad-action-dialog",
GIMP_ICON_EDIT,
title,
gtk_widget_get_toplevel (GTK_WIDGET (editor)),
gimp_standard_help_func,
GIMP_HELP_PREFS_INPUT_DEVICES,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_OK"), GTK_RESPONSE_OK,
NULL));
g_free (title);
gimp_dialog_set_alternative_button_order (GTK_DIALOG (private->pad_edit_dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
gimp_dialog_factory_add_foreign (gimp_dialog_factory_get_singleton (),
"gimp-pad-action-dialog",
private->pad_edit_dialog,
gimp_widget_get_monitor (button));
g_signal_connect (private->pad_edit_dialog, "response",
G_CALLBACK (gimp_device_info_editor_edit_response),
editor);
view = gimp_action_editor_new (GIMP_TOOL_PRESET (private->info)->gimp,
action_name, FALSE);
gtk_container_set_border_width (GTK_CONTAINER (view), 12);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (private->pad_edit_dialog))),
view, TRUE, TRUE, 0);
gtk_widget_show (view);
g_signal_connect (GIMP_ACTION_EDITOR (view)->view, "row-activated",
G_CALLBACK (gimp_device_info_editor_edit_activated),
editor);
g_set_weak_pointer
(&private->pad_edit_sel,
gtk_tree_view_get_selection (GTK_TREE_VIEW (GIMP_ACTION_EDITOR (view)->view)));
gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
gtk_widget_show (private->pad_edit_dialog);
}
}
static void
gimp_device_info_editor_delete_clicked (GtkWidget *button,
GimpDeviceInfoEditor *editor)
{
GimpDeviceInfoEditorPrivate *private;
GtkTreeModel *model;
GtkTreeIter iter;
GimpPadActions *pad_actions;
private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (private->pad_grab_button),
FALSE);
if (gtk_tree_selection_get_selected (private->pad_sel, &model, &iter))
{
GimpPadActionType type;
guint number;
guint mode;
gtk_tree_model_get (model, &iter,
PAD_COLUMN_TYPE, &type,
PAD_COLUMN_NUMBER, &number,
PAD_COLUMN_MODE, &mode,
-1);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
PAD_COLUMN_ACTION_ICON, NULL,
PAD_COLUMN_ACTION_NAME, NULL,
-1);
pad_actions = gimp_device_info_get_pad_actions (private->info);
gimp_pad_actions_clear_action (pad_actions, type, number, mode);
}
gtk_widget_set_sensitive (button, FALSE);
}
static void
gimp_device_info_editor_edit_activated (GtkTreeView *tv,
GtkTreePath *path,
GtkTreeViewColumn *column,
GimpDeviceInfoEditor *editor)
{
GimpDeviceInfoEditorPrivate *private;
private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor);
gtk_dialog_response (GTK_DIALOG (private->pad_edit_dialog), GTK_RESPONSE_OK);
}
static void
gimp_device_info_editor_edit_response (GtkWidget *dialog,
gint response_id,
GimpDeviceInfoEditor *editor)
{
GimpDeviceInfoEditorPrivate *private;
private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor);
gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
if (response_id == GTK_RESPONSE_OK)
{
GtkTreeModel *model;
GtkTreeIter iter;
gchar *icon_name = NULL;
gchar *action_name = NULL;
if (gtk_tree_selection_get_selected (private->pad_edit_sel, &model, &iter))
gtk_tree_model_get (model, &iter,
GIMP_ACTION_VIEW_COLUMN_ICON_NAME, &icon_name,
GIMP_ACTION_VIEW_COLUMN_NAME, &action_name,
-1);
if (gtk_tree_selection_get_selected (private->pad_sel, &model, &iter))
{
if (action_name)
{
GimpPadActions *pad_actions;
GimpPadActionType type;
guint number;
guint mode;
gtk_tree_model_get (model, &iter,
PAD_COLUMN_TYPE, &type,
PAD_COLUMN_NUMBER, &number,
PAD_COLUMN_MODE, &mode,
-1);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
PAD_COLUMN_ACTION_ICON, icon_name,
PAD_COLUMN_ACTION_NAME, action_name,
-1);
pad_actions = gimp_device_info_get_pad_actions (private->info);
gimp_pad_actions_set_action (pad_actions, type, number, mode,
action_name);
}
}
g_free (icon_name);
g_free (action_name);
gimp_device_info_editor_sel_changed (private->pad_sel, editor);
}
gtk_widget_destroy (dialog);
}
static void
gimp_device_info_editor_pad_action_foreach (GimpPadActions *pad_actions,
GimpPadActionType action_type,
guint number,
guint mode,
const gchar *action,
gpointer data)
{
GtkTreeModel *tree_model = data;
gtk_list_store_insert_with_values (GTK_LIST_STORE (tree_model), NULL, -1,
PAD_COLUMN_TYPE, action_type,
PAD_COLUMN_NUMBER, number,
PAD_COLUMN_MODE, mode,
PAD_COLUMN_ACTION_NAME, action,
-1);
}
/* public functions */