Dialog Provider

Overview

DialogProvider is the central abstraction for all user-facing dialogs in the WOLF viewer. Instead of calling wx dialog classes directly, every component that needs to interact with the user does so through a DialogProvider instance stored as self._dialogs.

Business logic            DialogProvider interface          wx back-end
─────────────────         ────────────────────────          ───────────
PyDraw / any view  ──►   self._dialogs.ask_yes_no()  ──►   wx.MessageDialog
                         self._dialogs.ask_file_open()──►  wx.FileDialog
                         …                                  …

This indirection has two benefits:

  • Testability — unit tests inject a MockDialogProvider that never opens a real window. Responses are scripted in advance, and every call is recorded for later assertion.

  • Portability — the wx dependency is confined to a single module. Replacing the back-end (e.g. Qt, CLI, web) only requires a new subclass.

Note

The convention self._dialogs.<method>(parent, ...) replaces every direct wx.<Dialog>(parent, ...) call site. Do not bypass it.

Class reference

DialogProvider

class wolfhece.dialog_provider.DialogProvider[source]

Wrapper around common wx dialogs. All methods accept an optional parent window that is passed directly to the underlying wx constructor.

File and directory dialogs

ask_file_open(message, wildcard='all (*.*)|*.*', default_path='', style=None, parent=None) str | None[source]

Open a File → Open dialog. Returns the selected path, or None if the user cancelled.

Parameters:
  • message – Dialog title / prompt shown to the user.

  • wildcard – Pipe-separated filter string (wx format), e.g. "Images (*.png)|*.png|All files (*.*)|*.*".

  • default_path – Directory shown when the dialog opens.

  • style – wx style flags; defaults to wx.FD_OPEN.

ask_file_open_with_filter(message, wildcard='all (*.*)|*.*', default_path='', style=None, parent=None) tuple[str, int] | None[source]

Same as ask_file_open(), but also returns the index of the selected wildcard filter. Returns (path, filter_index) or None on cancel.

ask_file_save(message, wildcard='all (*.*)|*.*', default_path='', default_file='', style=None, parent=None) str | None[source]

Open a File → Save dialog. Returns the path chosen by the user, or None on cancel. The default style is wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT.

ask_directory(message, default_path='', style=wx.DD_DEFAULT_STYLE, parent=None) str | None[source]

Open a directory-chooser dialog. Returns the selected directory path, or None on cancel.

Message dialogs

show_message(message, caption='', style=DialogStyles.OK_INFO, parent=None) int[source]

Show a non-blocking informational (or warning/error) dialog with an OK button. Returns the wx modal return code (wx.ID_OK).

ask_yes_no(message, caption='', style=DialogStyles.YES_NO_QUESTION, parent=None) bool[source]

Ask the user a yes/no question. Returns True if the user clicked Yes, False otherwise.

ask_ok_cancel(message, caption='', style=DialogStyles.OK_CANCEL, parent=None) bool[source]

Show an OK / Cancel dialog. Returns True if the user clicked OK.

ask_confirmation(message, caption='', default='yes', parent=None) bool[source]

Convenience wrapper around ask_yes_no() that sets the default focused button.

Parameters:

default"yes" (default) focuses the Yes button; "no" focuses the No button.

Choice dialogs

ask_single_choice(message, caption, choices, preselected=None, style=wx.CHOICEDLG_STYLE, parent=None) str | None[source]

Show a list of items and let the user pick exactly one. Returns the selected string, or None on cancel.

Parameters:
  • choices – Sequence of option strings.

  • preselected – Index of the item selected by default.

ask_multi_choice(message, caption, choices, preselected=None, style=wx.CHOICEDLG_STYLE, parent=None) list[int] | None[source]

Show a list of items and let the user pick multiple items. Returns a list of selected indices, or None on cancel.

Parameters:

preselected – Sequence of indices pre-checked when the dialog opens.

Text and number entry

ask_text(message, caption='', default='', style=DialogStyles.OK_CANCEL, parent=None) str | None[source]

Prompt the user for a text string. Returns the entered string, or None on cancel.

ask_float(message, caption='', default='', style=DialogStyles.OK_CANCEL, parent=None) float | None[source]

Prompt the user for a floating-point number (built on top of ask_text()). Returns the parsed float, or None on cancel or invalid input.

ask_integer(message, prompt='', caption='', default=0, min_value=0, max_value=0, parent=None) int | None[source]

Prompt the user for an integer using wx.NumberEntryDialog. min_value and max_value bound the accepted range. Returns the entered integer, or None on cancel.

Progress dialogs

create_progress(title, message, maximum, style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE, parent=None) ProgressHandle[source]

Create and display a determinate progress bar. Returns a ProgressHandle that wraps the underlying dialog.

Typical usage:

progress = self._dialogs.create_progress("Processing", "Loading…", 100)
for i, item in enumerate(items):
    process(item)
    if not progress.update(i + 1):
        break          # user clicked Cancel
progress.close()
show_busy(message, title='', parent=None)[source]

Context manager that displays an indeterminate progress (pulse) dialog while the with block executes. The dialog is automatically destroyed on exit.

with self._dialogs.show_busy("Recomputing…"):
    expensive_operation()

ProgressHandle

class wolfhece.dialog_provider.ProgressHandle[source]

Thin wrapper returned by DialogProvider.create_progress().

update(value, message=None) bool[source]

Advance the bar to value. Pass message to change the displayed text. Returns False if the user clicked Cancel, True otherwise.

value() int[source]

Return the current progress value.

close()[source]

Destroy the underlying wx.ProgressDialog.

DialogStyles

class wolfhece.dialog_provider.DialogStyles[source]

Named constants that map to common wx style flag combinations. Use these instead of raw wx flags for clarity and to decouple call sites from the wx namespace.

Constant

Equivalent wx flags

OK

wx.OK

CANCEL

wx.CANCEL

OK_CANCEL

wx.OK | wx.CANCEL

ICON_INFORMATION

wx.ICON_INFORMATION

ICON_QUESTION

wx.ICON_QUESTION

ICON_WARNING

wx.ICON_WARNING

ICON_ERROR

wx.ICON_ERROR

YES_NO

wx.YES_NO

YES_NO_QUESTION

wx.YES_NO | wx.ICON_QUESTION

YES_NO_DEFAULT_YES

wx.YES_NO | wx.YES_DEFAULT

YES_NO_DEFAULT_NO

wx.YES_NO | wx.NO_DEFAULT

YES_NO_QUESTION_DEFAULT_YES

wx.YES_NO | wx.ICON_QUESTION | wx.YES_DEFAULT

YES_NO_QUESTION_DEFAULT_NO

wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT

OK_INFO

wx.OK | wx.ICON_INFORMATION

OK_CANCEL_WARNING

wx.OK | wx.CANCEL | wx.ICON_WARNING

MockDialogProvider (testing)

class tests.mock_dialog_provider.MockDialogProvider

Scriptable subclass of DialogProvider intended exclusively for unit tests. It never opens any real window.

All dialog methods are overridden to pull scripted answers from a per-method FIFO queue. If a method is called but the queue is empty, an AssertionError is raised immediately, making test failures explicit.

Every call is appended to calls for post-call assertion.

calls : list[dict]

Ordered list of every dialog call recorded since the mock was created. Each entry is a dict with a "method" key and one key per argument passed to the method.

push(method_name, *responses)

Queue one or more scripted responses for the named method. Responses are consumed in FIFO order.

Parameters:
  • method_name – Name of the DialogProvider method to script (e.g. "ask_yes_no").

  • responses – Values to return, consumed one per call.

mock = MockDialogProvider()
mock.push("ask_yes_no", True)          # first call → True
mock.push("ask_yes_no", False, True)   # second → False, third → True

show_busy is also overridden: it records the call and yields None without displaying anything.

Usage in tests

A typical test:

  1. Creates a MockDialogProvider and pushes scripted answers.

  2. Injects the mock as component._dialogs.

  3. Calls the component method under test.

  4. Asserts on the result and optionally inspects mock.calls.

from tests.mock_dialog_provider import MockDialogProvider

def test_save_aborts_on_cancel(viewer):
    mock = MockDialogProvider()
    mock.push("ask_file_save", None)      # simulate Cancel
    viewer._dialogs = mock

    viewer.save_results()

    assert mock.calls[0]["method"] == "ask_file_save"
    # no file was written
    assert not output_path.exists()


def test_overwrite_confirmed(viewer, tmp_path):
    mock = MockDialogProvider()
    mock.push("ask_file_save", str(tmp_path / "out.bin"))
    mock.push("ask_yes_no", True)         # confirm overwrite
    viewer._dialogs = mock

    viewer.save_results()

    assert (tmp_path / "out.bin").exists()

Tip

Always call push() for every dialog that the code path under test will trigger. An uncaught AssertionError from the mock is a strong signal that the production code opened an unexpected dialog.

Migration guide

When converting a direct wx call to use DialogProvider:

  1. Identify the wx dialog call and its return value usage.

  2. Replace it with the matching self._dialogs.<method>(...) call.

  3. Add a test that pushes a scripted response and verifies behaviour.

  4. Do not change any business logic during the replacement.

Before

After

wx.FileDialog(parent, msg, style=wx.FD_OPEN)

self._dialogs.ask_file_open(msg, parent=parent)

wx.FileDialog(parent, msg, style=wx.FD_SAVE)

self._dialogs.ask_file_save(msg, parent=parent)

wx.DirDialog(parent, msg)

self._dialogs.ask_directory(msg, parent=parent)

wx.MessageDialog(parent, msg, caption, style=wx.YES_NO)

self._dialogs.ask_yes_no(msg, caption, parent=parent)

wx.MessageDialog(parent, msg, caption, style=wx.OK)

self._dialogs.show_message(msg, caption, parent=parent)

wx.TextEntryDialog(parent, msg, caption)

self._dialogs.ask_text(msg, caption, parent=parent)

wx.SingleChoiceDialog(parent, msg, caption, choices)

self._dialogs.ask_single_choice(msg, caption, choices, parent=parent)

wx.NumberEntryDialog(parent, msg, prompt, caption, default, lo, hi)

self._dialogs.ask_integer(msg, prompt, caption, default, lo, hi, parent=parent)

wx.ProgressDialog(title, msg, maximum=n)

self._dialogs.create_progress(title, msg, n)