GtkAction migration

From Inkscape Wiki
Jump to navigation Jump to search

Background: what are actions?

An "action" is a thing that Inkscape can do. In its simplest form, it contains two things:

  • The name of the action (e.g., "ObjectFlipHorizontally")
  • A pointer to some code that performs the action (e.g., a few lines that flip the object horizontally)

In the Inkscape application, we can attach actions to widgets so that when the user interacts with that widget (e.g. clicks on the "Flip horizontally" button in the toolbar), the action is triggered.

How is this different from GUI events?

GUI elements (buttons etc) provide their own signals (e.g, "clicked") that are emitted in response to user interaction. We then need to connect a callback function to handle that specific GUI event. If we have multiple GUI elements (e.g., a menu item, toolbar button etc) that all do the same thing, we need to hook up the callback function to each of the GUI elements.

With actions, it's a little different: the action has an "activated" signal. We now only need to connect the callback function to that one signal. Each GUI element is now just attached to the action rather than directly to the code that we want to run.

The advantages of actions are as follows:

  • Less code to write: we only need one signal handler for the "activate" signal; not multiple handlers for each GUI event
  • Easier look-up: Each action is identified by a unique text ID, and so we can access it by name.
  • It is trivial to allow actions (with or without arguments) to be called from the command line.
  • There is a built in DBus interface.

Background: Action implementations

There are a few different implementations of actions:

GAction or Gio::Action

These are probably the simplest implementation, and the way we "should" be doing this. The Gio::Action class [1] really just defines a name for the action, along with possible states and parameters.

These actions need to be listed in an action-map, which is essentially a table that lists each action and the function that handles its "activate" signal. There is an action map associated with the application (Gtk::Application or Gio::Application) and an action map associated with windows (Gtk::ApplicationWindow). The latter is useful for window dependent functions like zoom level where two different "views" of the same document can have different zoom levels. Another action map can be "manually" attached to a document to handle document level actions (such as snapping preferences).

Actions are defined in src/actions

Verb to Action Migration

Inkscape has a custom built Verb class that duplicates much of the functionality of Actions. This class has some problems, the biggest is that most Verbs require a desktop. This means that they cannot be used in a headless Inkscape. We are in the process of converting Verbs to Actions. This is a long term process as there are many verbs.

Actions are defined in the "src/actions" directory. For new actions, please follow the format of existing files. If possible, define all functionality in the actions-xxx.cpp file.

Best Practices

  • Tie an action to the appropriate map: Application (app), Window (win), or Document (doc). (We may add tool dependent maps in the future.) It is not always obvious which map an action should be tied to:
    • Application:
      • Global actions:
      • Actions that operate on selections. There is always a "current selection" that is active. Note: a single document may have multiple independent selections, one for each window. There will also be an active selection in command line mode.
    • Window actions:
      • Zoom level
    • Document actions:
      • Preferences stored inside an Inkscape SVG file, e.g. snapping (snapping preferences were removed from the document in Sept 2021 so are now Window level actions).
  • Actions should not rely on Window (desktop) unless absolutely necessary (e.g. scale by window pixels). This will allow a future GUI free Inkscape version.
  • If actions need to relay information to the user, use a standard (to be decided) mechanism to isolate dependency on desktop.
    • boolop_display_error_message();
    • selection_display_message();
    • InkErrorHandle (Pops up Error Box) (Derived from silly Inkscape::ErrorReporter class.)
    • Note, a selection does have a link to desktop (which can be nullptr).
  • Many actions work on a selection. A standard way to find the selection should be used. Selections are tied to windows with a special selection tied to the document in command line mode.
    • get_document_and_selection(app, &document, &selection);
    • app->get_active_selection();
    • At the moment we rely on INKSCAPE.action_context_for_document() to create a selection for a document in command-line mode. (INKSCAPE should disappear in the future.)--
  • When a Verb is converted to an action, remove any related code from "verb.cpp". Comment out the verb in "verbs.h" (so we can track progress in converting).
  • DocumentUndo uses verbs to provide an icon next to the operation in the History dialog. For the moment, one can change any removed verbs to "nullptr". In the future, we may allow one to directly specify an icon.

Selection-based actions

Selection-based actions need to deal with two different ways selections are integrated into our code:

  • In command-line mode, a selection is linked to the document.
  • In GUI mode, a selection is linked to the window (via SPDesktop).[1]

The current method for dealing with selection-based actions is to make them "application" level which depends on the application (InkscapeApplication) tracking which selection is active. This is a bit awkward, particularly due to the complicated interactions between SPDesktop, SPDesktopWindow, SPDocument, InkscapeApplication, etc. And it breaks down if a "stateful" action is needed as in the case of "arc type" in the Arc toolbar since all uses of that action would share the same state. What can be done about this?

  • We can keep selection-based actions tied to the application, introducing special "window" actions where we need to track state. This means some code duplication.
  • We can tie selection-based actions to selections. These seems that natural thing to do but would require having one special instance of Inkscape::Selection owned by the app (and not the document - which is the current case) so that actions from the command line work. (We need valid actions at the point of parsing the command line which may be before a document is opened.) This would be more feasible after we get rid of Inkscape::ActionContext.

Note that Gio::Application contains one action_group, other groups cannot be added so we would need code anywhere actions are used to find the correct selection and trigger actions in the linked ActionGroup. [1] Different windows showing the same document can have different selections.

Shortcuts

Shortcuts for actions can be added in "share/keys/inkscape.xml" or in the Inkscape Preferences dialog. Currently, only actions described in the "Extra Action Data" appear in the dialog. Note that shortcuts use the "Detailed Action Name" which consists of "map name" + "." + "action name" + "(" + argument + ")". Not all actions take arguments.

Keyboard shortcuts are a bit complicated in Inkscape. GTK normally passes key events to the GTK shortcut code first. We override this and pass it to our own hard-coded event handlers first (otherwise we wouldn't even be able to type in the text tool since we use single letter shortcuts). Each tool has its own handler code and keys can be tied to different actions for different tools. If the hard-coded event handlers don't handle the key event, it gets passed to GTK's shortcut handler and finally our Verb shortcut handler code. The shortcuts handled by GTK (via Gio::Actions) or by our Verb code are global to all tools (and only these can be modified in the Preferences dialog). It is possible to create different "ActionGroups" for each tool and then install/remove the group as the selected tool changes. This would allow one to use preferences to assign different tool dependent actions to different shortcuts. But that will require quite a bit of refactoring and won't solve all problems (actions are triggered once per key-press/key-release so any thing that depends on different actions for key-press vs key-release will still need to be hard coded).

Migrating widgets

Use the following replacements for old GtkAction-based widgets:

  • ege-adjustment-action => SpinButtonToolItem DONE
  • SPWidget => Nothing... just put your widget directly in the toolbar DONE

Containers

  • ConnectorToolbar
  • DropperToolbar

Known bugs

  • SpinButtonToolItem: Pressing enter or escape does nothing (it should drop focus to canvas)