Monday, March 2, 2015

Reinventing the wheel, cont.

A translator will be a module of Python code that has a straightforward job to do. It will be given a file-like thing into which it dumps its output, and a generator that produces semantic units parsed from the input file. It does for item in generator to the end, producing output into its file-like thing. I've got that API worked out pretty clearly. The translator's logic may be complex but it should not need to include any Python modules except perhaps regex, or unicodedata.

In particular I do not want it to need any PyQt modules. It doesn't have a UI as such.

Well, yes, but it might. In sketching out the translators I will write, especially the ASCII one, I realized that there are questions the user needs to answer. The ASCII translator (what was "Reflow" in V1) needs to know user choices such as:

  • How to treat <i> markups: delete, change to underscore, leave as-is?
  • Similar questions about <b> and <sc> markups
  • Maximum and preferred line-length

These and other user-set parameters were presented as radio buttons and similar widgets in the Reflow panel of V1. How to present them in V2, when they are input to a translator module? Any translator, I figure, may have similar questions to ask.

So my solution is to provide as basic an API for a user dialog as I can devise. When PPQT comes up, it will look for translator modules. It will use the "import machinery" to load each one into a Python namespace. This is standard stuff; PyInstaller uses exactly this mechanism to load "hook" modules. Each namespace is an object. PPQT will look in the namespace for certain global names.

One of the names a translator may define is USER_DIALOG. This is a list of dicts, each dict describing a user-input widget. For example, a translator might start out like this:

import xlate_utilities as X
max_width = {
    X.UD_TYPE: X.O_NUMBER,
    X.UD_LABEL: "Max width",
    X.UD_TOOLTIP: "Enter the maximum line width for any text.",
    X.UD_RESULT: "75"
    }
i_markup = {
    X.UD_TYPE: X.UD_CHOICE,
    X.UD_LABEL: "Convert <i>",
    X.UD_TOOLTIP: "Choose what to do if the input contains <i>: \
omit it, convert it, or pass it on",
    X.UD_RESULT: "0",
    X.UD_CHOICES: [
        ("Omit","Just omit <i> and </i> from the output"),
        ("Use _","Replace <i> and </i> with a single underscore_"),
        ("Keep","Retain <i> and </i> in the output") ]
    }
USER_DIALOG = [ max_width, i_markup ]

This code describes a dialog with two widgets. The first is a numeric spinbox; the second is a radio set with three options. When the user wants to initiate a translator, PPQT will look at the namespace it made by loading that translator. If there is a USER_DIALOG list, PPQT will dynamically assemble a modal dialog as a stack of the widgets described in the list. It will display that dialog, with OK and Cancel buttons at the bottom. The widgets will be labelled as shown, with tooltip strings as specified, and initialized from the X.UD_RESULT items. In the example, the spinner is initialized to 75; and the first, zeroth, radio button checked. If the user clicks Cancel, the translator will not be run. When OK is clicked, the values from the widgets are stored back into the X.UD_RESULT members of their respective dicts. The translator code can query them when it is executed. Since these are globals, they will still be there if the translator is run again, so the dialog will have as initial values the user's selection from the prior time.

The reinventing the wheel part is that I'm basically reinventing QtQuickWidgets in a simplified form. I looked at QtQuick and QuickWidgets before getting into this. It's much too complicated and generalized. Anyway, I absolutely do not want to impose any requirement for Qt knowledge on the writer of a translator.

Using this API, the writer of a translator can get input as numbers, yes/no checkboxes, radio sets for multi-way choices, and literal strings. The interface is static, declarative; no executable code at all.

No comments: