Inkscape.org
Creating New Extensions Dynamic GUI example Inkscape extensions with GTK
  1. #1
    Nikki.Smith Nikki.Smith @Nikki.Smith
    *

    I've been writing my first Inkscape extension, and as part of the learning process I've made a handful of small examples that demonstrate some user interface widgets. I'd very much appreciate any comments, suggestions or bug reports as I'm new to both GTK and Python. Hopefully they'll also be useful to other developers looking to write an extension 😀

    Most extensions to Inkscape use an .inx file that describes a static user interface, then a Python script is run one time when the user clicks "Apply".

    To create a dynamic UI (that changes as the user clicks buttons, selects from dropdown menus, etc) you can build it in GTK using a Python script that runs immediately. Requires Inkscape v1.0 or newer.

    These short examples demonstrate a handful of functions in GTK+ 3. Note that the .inx file for each dynamic extension must use:

      <effect implements-custom-gui="true">

    Full details of inkex and Python GTK:

    How to install

    To manually install these extensions, download and unpack the archive file. Copy the 'dynamic_examples' folder to the location listed by the Inkscape menu:

      Edit > Preferences > System: User extensions

    For Windows users this will be something like:

      C:\Users\USERNAME\AppData\Roaming\inkscape\extensions

    Restart Inkscape and the new extensions will be available.

    Button (action) + Label (text)

    A minimal dynamic extension. Everything is wrapped inside the new class ButtonLabel(), derived from inkex.EffectExtension. The effect() method is called as soon as the extension is run.

    A window is created with Gtk.Window(), setting the title, and links a handler to exit if the close icon is clicked. See PyGTK introduction

    Layout uses a vertical Gtk.Box() to separate the text label and button. See PyGTK layout

    The script counts up the total number of shape elements in the current document and displays it as text with Gtk.Label(). See PyGTK label and Inkscape inkex.elements._base

    Finally a Gtk.Button() is created, with a click handler as another way to exit the script. See PyGTK button_widgets

    SpinButton (number) + RadioButton (options)

    Demo of the Gtk.SpinButton() widget for numeric entry, with up/down arrows. Uses Gtk.Adjustment() to set lower/upper bounds & step values. See PyGTK button_widgets

    Two Gtk.RadioButton() in a group are used to choose between metric and 'freedom' units of distance.

    Layout uses a vertical Gtk.Box(), and again for two horizontal rows.

    Some of the labels have simple HTML markup using Gtk.Label().set_markup()

    Nothing is read or changed in the Inkscape document.

    ColorButton + LinkButton (URL)

    Demo of the Gtk.ColorButton() for simple colour selection. See GTK ColorButton

    The script adds up the total length of path elements with that stroke colour and displays it as text. Just from the currently selected objects, or the full document. Objects like rectangles, circles & text are not included unless converted to paths first.

    Getting the actual length of each path is a bit involved, using built-in functions to convert into "a predictable list of cubic bezier curves". See Inkscape inkex.paths

    Note how the colour button returns Gdk.RGBA (red/green/blue channels are 0.0→1.0) whereas document colours are inkex.colors.Color (0→255).

    Also displays a clickable link using Gtk.LinkButton(). See PyGTK button_widgets

    ComboBoxes (dropdown menus)

    Demo of the Gtk.ComboBox() widget for two dropdown menus in a Country→Cities hierarchy. See PyGTK combobox

    The city dropdown is rebuilt whenever a different country is selected, and a (Unicode) text label is updated when a different city is selected.

    The dropdowns don't need to be hard-wired in the script: they could be read at runtime from a database or web API, for example.

    Layout uses Gtk.Grid() for a 2×2 grid to align the dropdown menus. See PyGTK layout

    There is a separate "Apply" button that runs an (empty) method. This is where an extension would normally perform some action to the document.

    Nothing is read or changed in the Inkscape document.

  2. #2
    inklinea inklinea @inklinea⛰️

    Very nice examples. 

    They are very well written and easy to read.

    Also you have linked to https://lazka.github.io/pgi-docs/Gtk-3.0/index.html in your code - which is great for python gtk.

    From reading the code, it seems you have a better grasp of python than me :)

    I can see you have hand coded the GTK elements.

    For my efforts I used an external glade file and and and external css file.

    It might be worth popping over to https://chat.inkscape.org/channel/inkscape_extensions

    I'm  sure they would be interested in adding any tutorials you would like to add to the extensions api docs - gtk examples are much needed !

    Well done.

  3. #3
    Nikki.Smith Nikki.Smith @Nikki.Smith
    *

    Thanks so much for your kind words! I really appreciate it I'll post about my examples on that chat channel.

    I found your Page Numbering extension and it was really helpful to get me started on my own extension.

    I've haven't tried using Glade yet, but looks like it would be a big time saver for more complex user interfaces? Unfortunately I think it's no longer being developed, with the last release v3.40 in 2022. [Glade - A User Interface Designer]

  4. #4
    Martin Owens Martin Owens @doctormo🌹🧀

    Interesting that you didn't use any of the bundled inkex.gui API for the interface examples. Was the API too restrictive?

  5. #5
    Nikki.Smith Nikki.Smith @Nikki.Smith

    What is inkex.gui for? Is it only needed if you want to load a GtkBuilder file created in Glade? 

    I didn't notice any signposting in the inkex tutorial pages that pushed me towards inkex.gui, or examples that made use of it.

     

  6. #6
    Nikki.Smith Nikki.Smith @Nikki.Smith

    A few minor improvements with this release:

    • box.add() used instead of pack_start(), to simplify the code where possible
    • color_link.py now supports shape objects (circle, ellipse, rectangle), by converting them into paths before calculating the length. Text objects are not supported
      svg.descendants() / selection.get() are used to get all elements, instead of svg.findall() / selection.filter() for just paths
    • color_link.py replaced color_equal() with rgba2color(), so now converts the colour, and then uses == to directly compare Color() objects
    • README.html added with documentation in HTML format
  7. #7
    Nikki.Smith Nikki.Smith @Nikki.Smith

    Some more small improvements with this release:

    • Now checks Inkscape version and gives a friendly error message if Inkscape is too old
    • Tested on Inkscapes before v1.3, fixed deprecation warnings with Gtk.Label() positional arguments
    • Accessibility improvements to README.html, as well as responsive design for mobile screens

    Dynamic extensions need at least Inkscape v1.1 to work, and users will see a long and incomprehensible Python traceback if they use Inkscape v1.0.x. To avoid this you can easily check if a certain feature exists in the inkex library, and give a friendly error message if it is not recent enough for your extension:

    # Friendly error message if run from older, incompatible version of Inkscape
    import inkex, gi, sys
    if "get_user_directory" not in dir(inkex.utils):
        inkex.utils.errormsg("This extension needs Inkscape v1.1 or later, please upgrade")
        sys.exit()
    

    Similar tests can be used to check for a different version of Inkscape if your extension requires that to work.

Inkscape Inkscape.org Inkscape Forum Creating New Extensions Dynamic GUI example Inkscape extensions with GTK