GUI Tools

Plugins containing GUI tools will also require modifying the setup.py as follows:

from setuptools import setup
from plover_build_utils.setup import BuildPy, BuildUi

BuildPy.build_dependencies.append("build_ui")
BuildUi.hooks = ["plover_build_utils.pyqt:fix_icons"]
CMDCLASS = {
  "build_py": BuildPy,
  "build_ui": BuildUi,
}

setup(cmdclass=CMDCLASS)

By making these changes, you get commands to generate Python files from your Qt Designer UI and resource files:

python3 setup.py build_py build_ui

In addition, create a file named MANIFEST.in in your plugin directory as follows. Change the paths as needed, but make sure to only include the Qt Designer .ui files and resources, and not the generated Python files.

exclude plover_my_plugin/tool/*_rc.py
exclude plover_my_plugin/tool/*_ui.py
include plover_my_plugin/tool/*.ui
recursive-include plover_my_plugin/tool/resources *
[options.entry_points]
plover.gui.qt.tool =
  example_tool = plover_my_plugin.tool:Main

GUI tools are implemented as Qt widget classes inheriting from Tool:

# plover_my_plugin/tool.py

from plover.gui_qt.tool import Tool

# You will also want to import / inherit for your Python class generated by
# your .ui file if you are using Qt Designer for creating your UI rather
# than only from code
class Main(Tool):
  TITLE = 'Example Tool'
  ICON = ''
  ROLE = 'example_tool'

  def __init__(self, engine):
    super().__init__(engine)
    # If you are inheriting from your .ui generated class, also call
    # self.setupUi(self) before any additional setup code

Keep in mind that when you need to make changes to the UI, you will need to generate new Python files.

See the documentation on Tool for more information.

Just like extension plugins, GUI tools can interact with the engine through the StenoEngine API, but instead of using engine hooks directly, GUI tools should use Qt’s signals mechanism. Plover’s Engine class provides Qt signals to be used as hooks.

class StrokeLogger(Tool, Ui_StrokeLogger):
  def __init__(self, engine):
    super().__init__(engine)
    self.setupUi(self)

    # Instead of engine.hook_connect
    engine.signal_connect("stroked", self.on_stroked)

  def on_stroked(self, stroke):
    pass