What is the fastest and easiest way to create GUIs in Anki-Addons?

By dialogs I mean popups like the one used in @ijgnd 's great Add Table Addon

At the moment I am looking to build a multi line text edit with a checkbox and three radio buttons.

I started coding in Delphi 5 and it still seems extremely cumbersome to create GUIs in Python. :sweat_smile: Am I overlooking something?

If you just want some general info:

for more than basic dialogs I use the QtDesigner which allows to create the ui elements visually by dragging and dropping elements I want to use. I haven’t used Delphi but my understanding is that the qt designer is inspired by the gui creation process in Delphi.

After designing a dialog with your mouse you connect the signals and slots in your code. Anki itself has about 20 windows set up in the designer - see anki/qt/aqt/forms at main · ankitects/anki · GitHub

I bought the herrmann book for pyqt (25$, PyQt/Books - Python Wiki). It helped me. I liked that it was very short. It seems to be one of the two recent books. After getting some basic understanding from this I get along by using the official (good) qt documentation for the relevant classes (like this one) and stackexchange. For the basic stuff I use the qtdesigner is mostly self-explanatory.

I have some code public on my github. You know my coding skills … To build/“compile” the ui files made in the qtdesigner I use a small helper script that also copies them to my anki addons folder or builds them (see here). Glutanimate has a much more sophisticated build helper, see here which requires a different source folder layout (which is used by other add-on creators).

5 Likes

Thank you so much for the fast and informative response. I will check out QtDesigner as well as the book and your build script. Much appreciated!

I just mentioned the build scripts because building the dialogs manually after each change in an ui file is tedious so that many add-on creators seem to rely on build scripts and I just wanted to give two examples that came to my mind.

Instead of trying to understand my 300line build-script or the even longer glutanimate code you could also look at henrik’s approach, who seems to put the code into the addons folder and uses such a 5 line bash build script just for the ui.

1 Like

After looking into this more GUI creation still seems extremely complicated for something so simple and so useful.

However, I stumbled across a GUI library that makes creating GUIs extremely easy – if I can get it to work, that is. This library is PySimpleGUI. Let me post some example code to demonstrate how easy it is to create quite complex GUIs with it:

from aqt.utils import showInfo

from . import PySimpleGUIQt as sg     # Part 1 - The import

# Define the window's contents
layout = [                            # Part 2 - The Layout
  [sg.Text("Enter some text:")],
  [sg.Multiline(default_text = "Lorem ipsum dolor sit amet...", key="long_text", size=(500, 200))],
  [sg.Text("Select a priority:")],
  [sg.Radio("CODE RED", "prio", key="prio_1"), sg.Radio("Middle priority", "prio", key="prio_2"), sg.Radio("low priority", "prio", key="prio_3", default=True)],
  [sg.Checkbox("Oh look, a checkbox", key="the_only_checkbox")],
  [sg.Ok(), sg.Cancel()]
]

# Create the window
window = sg.Window('Random demo window', layout, element_padding=(7, 7))        # Part 3 - Window Defintion

# Display and interact with the Window
event, values = window.read()                   # Part 4 - Event loop or Window.read call

print(values)

# Finish up by removing from the screen
window.close()                                  # Part 5 - Close the Window

This produces the following dialogue:

image

Output:

{‘long_text’: ‘Lorem ipsum dolor sit amet…’, ‘prio_1’: False, ‘prio_2’: False, ‘prio_3’: True, ‘the_only_checkbox’: False}

Very elegant syntax, just one import statement and no building or compiling whatsoever! :tada:

The main problem I have at the moment is that it also uses Qt (version 5) and this doesn’t play nice when I use it inside of Anki, for example when I want to show a dialogue after a user clicks on a menu option in the browser. Using window.close() in this situation leads to Anki shutting down completely. Also window.read() is non blocking and returns directly.

My current progress as a very simple addon for debugging. This does 2 things:

  • Shows a dialogue right when Anki starts. This works fine.
  • Adds the menu option “TEST TEST TEST” to the right click menu for notes in the browser. If you select this option, it doesn’t work (fails as specified above).

Getting this to work could make GUI creation much easier for a lot of people, so I really hope these issues can be fixed. I will try to incentivise people to work on this by posting a bounty for it later today.

There are also Tkinter- (= main version) and PySide2-versions of PySimpleGUI as well as several others. Those could also be an option.

Depending on which part of Anki your add-on works in, you can also create UIs with HTML/JS.

If you truly need to display your UI in a separate window, you can still make it a QWebView and skip most of the cumbersome Python work.

2 Likes

This also seems like a great idea. Do you know of any addon that does this?

It would have to work in the card browser (popup is displayed when clicking on a menu option for notes).

It seems to me that PySimpleGUI is more suited to creating stand-alone apps than add-ons that integrate with Anki’s interface. After a brief look, I can’t find any way to create a dialog using something like QDialog. Support for PyQt5 was also removed and PyQt is not recommended to use.

Creating a basic GUI with PyQt may seem more involved initially compared to PySimpleGUI, but getting it to work with Anki is a breeze compared to getting PySimpleGUI to play nicely, since Anki itself uses PyQt. Using Qt Designer doesn’t require a complicated build process, depending on your needs. For a simple and clean example, see how the LPCG add-on does things:

  1. import_dialog.ui is a file generated by Qt Designer. You simply design your GUI visually and Qt Designer will generate the code for you.
  2. The add-on generates a Python file called import_dialog.py from import_dialog.ui using the command pyuic5 designer/import_dialog.ui > src/import_dialog.py.
  3. import_dialog.py is then used in lpcg_dialog.py, which contains a instance of QDialog (LPCGDialog). The critical details here are just these 3 lines at AnkiLPCG/lpcg_dialog.py at fa9765c256e6672fb21b1f0fbfdeb02948571a80 · sobjornstad/AnkiLPCG · GitHub
  4. The LPCGDialog dialog is then triggered from the Tools menu and shown using
dialog = LPCGDialog(aqt.mw)
dialog.exec_()

See

PyQt tutorials:

5 Likes

I think it is an option to create a widget without Qt Designer if it does not have a complex layout. I tried implementing the dialog in that image without using Qt Designer, so you can try it if you like.

from aqt import browser, gui_hooks, qt


class MyDialog(qt.QDialog):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)

        # text box
        self.text_box_label = qt.QLabel()
        self.text_box_label.setText("Enter some text:")
        self.text_edit = qt.QTextEdit()

        # radio buttons
        self.radio_button_label = qt.QLabel()
        self.radio_button_label.setText("Select a priority:")
        self.radio_1 = qt.QRadioButton("CODE RED")
        self.radio_2 = qt.QRadioButton("Middle priority")
        self.radio_3 = qt.QRadioButton("Low priority")
        # radio button group
        self.button_group = qt.QButtonGroup()
        self.button_group.addButton(self.radio_1, 1)
        self.button_group.addButton(self.radio_2, 2)
        self.button_group.addButton(self.radio_3, 3)
        # set "Low priority" as default
        self.button_group.button(1).setChecked(True)
        # horizontal layout for radio buttons
        self.radio_button_hbox = qt.QHBoxLayout()
        self.radio_button_hbox.addWidget(self.radio_1)
        self.radio_button_hbox.addWidget(self.radio_2)
        self.radio_button_hbox.addWidget(self.radio_3)

        # checkbox
        self.checkbox = qt.QCheckBox("Oh look, a checkbox")

        # OK/Cancel buttons
        self.button_box = qt.QDialogButtonBox(
            qt.QDialogButtonBox.StandardButton.Ok
            | qt.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        # layout
        layout = qt.QVBoxLayout()
        layout.addWidget(self.text_box_label)
        layout.addWidget(self.text_edit)
        layout.addWidget(self.radio_button_label)
        layout.addLayout(self.radio_button_hbox)
        layout.addWidget(self.checkbox)
        layout.addWidget(self.button_box)
        self.setLayout(layout)


def my_action() -> None:
    dialog = MyDialog()
    if dialog.exec():
        # when "OK" button is clicked
        print(f"Text: {dialog.text_edit.toPlainText()}")
        print(f"Priority: {dialog.button_group.checkedButton().text()}")
        checkbox_text = "checked" if dialog.checkbox.isChecked() else "unchecked"
        print(f"Checkbox: {checkbox_text}")
    else:
        print("Canceled!")


def on_browser_will_show_context_menu(browser: browser.Browser, menu: qt.QMenu) -> None:
    menu.addAction("My Dialog", my_action)


gui_hooks.browser_will_show_context_menu.append(on_browser_will_show_context_menu)

qdialog

6 Likes

Thank you for your input, @abdo. You’re right, at least PySimpleGUIQt doesn’t seem like a good match considering these points. I was just very impressed by how fast I was able to achieve what I had in mind with it.

I won’t give up hope completely that another version of PySimpleGUI could work in Anki (I’ll ask the developer of this library what he thinks) but for now my main obstacle is fixed by going the way you explained and @hkr kindly provided for me.

Thanks!

Thank you, @hkr , this is very kind of you. This works exactly as described. Do you have a paypal.me-address or some other way I can say thank you? Your Webview-Inspector-Addon has also been very helpful to me by the way.

Also, a general question for you @hkr , @abdo and @Rumo : Would any of you be open to helping me with issues like the one described here – in exchange for money of course? From time to time I stumble across some problem where it would be great if I could simply pick the brains of more experienced Addon-developers like you instead of searching around endlessly. If so please let me know what the best way to contact you would be. After all, I can’t expect @ijgnd to help me by writing such detailed answers all the time, haha.

1 Like

I’m happy to help out where I can. You can contact me here on the forums.

5 Likes

No problem! I’m glad to help.

Thanks for the offer, but I think I’ll pass.

I would also be happy to answer questions about add-on development on this forum here, if I can.

5 Likes

Thanks for the offer, but I think I’ll pass.

You left me no other choice so I donated to AnkiDroid instead. :slight_smile:

4 Likes