core.generator.ui.slide_generator

File:

EuljiroWorship/core/generator/ui/slide_generator.py

Author:

Benjamin Jaedon Choi - https://github.com/saintbenjamin

Affiliated Church:

The Eulji-ro Presbyterian Church [대한예수교장로회(통합) 을지로교회]

Address:

The Eulji-ro Presbyterian Church, 24-10, Eulji-ro 20-gil, Jung-gu, Seoul 04549, South Korea

Telephone:

+82-2-2266-3070

E-mail:

euljirochurch [at] G.M.A.I.L. (replace [at] with @ and G.M.A.I.L as you understood.)

License:

MIT License with Attribution Requirement (see LICENSE file for details); Copyright (c) 2025 The Eulji-ro Presbyterian Church.

Main window class for the EuljiroWorship slide generator GUI.

This module defines the main Qt window (core.generator.ui.slide_generator.SlideGenerator) that drives the slide authoring workflow:

  • Manage a table of slides (add/insert/delete/reorder).

  • Edit slides via a style-specific modal editor dialog.

  • Save/load a “session” JSON file for ongoing editing.

  • Export overlay-ready slide JSON for the live controller/overlay.

  • Open and apply persistent generator settings (fonts, paths, etc.).

The generator is typically launched from the project entry point (EuljiroWorship.py) and interacts with a slide controller process through exported JSON and the WebSocket-based overlay pipeline.

class core.generator.ui.slide_generator.SlideGenerator(*args, **kwargs)[source]

Bases: QMainWindow

Main window for the EuljiroWorship slide generator.

The generator provides a table-based slide session editor and supports:

  • Creating, inserting, deleting, and reordering slide rows

  • Editing each slide via a style-specific modal dialog (double-click)

  • Loading and saving slide sessions as JSON files

  • Exporting overlay-ready JSON (prepends a blank slide for a clean initial state)

  • Launching the slide controller for live output (if not already running)

Core collaborators (high-level):

Note

  • On startup, this window may show a file-open dialog to load an existing session. If the user cancels, a blank session is created.

  • Table cells are intentionally non-editable; edits are performed via the modal per-style editor dialog.

first_save_done

Tracks whether the first save action has completed in the current session. Used to choose between “save as” vs. save-to-last-path on Ctrl+S.

Type:

bool

reverse_style_aliases

Reverse mapping from the displayed style label (Korean UI text) to the internal style key (e.g., “가사” -> “lyrics”). Derived from style_map.REVERSE_ALIASES.

Type:

dict[str, str]

table

Main slide table with three columns: style, caption, headline. Rows represent slides in the current session.

Type:

QTableWidget

detail_widget

Right-side placeholder panel (reserved for future detail views).

Type:

QWidget

slide_controller_launcher

Helper that launches (or detects) the running slide controller.

Type:

SlideControllerLauncher

table_manager

Encapsulates row operations and table manipulation logic.

Type:

SlideTableManager

data_manager

Loads/saves session JSON and collects session data from the table.

Type:

SlideGeneratorDataManager

ui_builder

UI builder responsible for wiring menus, buttons, and header labels.

Type:

SlideGeneratorUIBuilder

worship_name

Session label derived from the loaded filename stem. None for a new unsaved session.

Type:

str | None

last_saved_path

Last known save path for the current session. When set, normal save operations write to this path without prompting.

Type:

str | None

__init__()[source]

Construct the main generator window and initialize the UI state.

This initializer:

  • Creates and configures the main slide table widget

  • Initializes core helper components (table manager, data manager, UI builder, launcher)

  • Prompts the user to load an existing slide session via an OS file dialog (if canceled, starts with a blank session)

Parameters:

None

Returns:

None

save_slides_to_file(show_message=False)[source]

Save the current slide session to the last saved path.

If no previous save path exists, a timestamp-based filename is generated in the current working directory. The session data is collected from the table via core.generator.utils.slide_generator_data_manager.SlideGeneratorDataManager.collect_slide_data() and written as UTF-8 JSON.

Parameters:

show_message (bool) – If True, shows a confirmation message box after saving.

Returns:

None

handle_ctrl_s()[source]

Handle the Ctrl+S shortcut for saving.

Behavior:

  • If this is the first save in the current session (or no prior save path exists), triggers a “save as” flow that prompts the user for a path.

  • Otherwise, saves to the last known path and shows a confirmation dialog.

Parameters:

None

Returns:

None

_get_default_load_directory()[source]

Return the initial directory used by the slide file open dialog.

Returns:

Directory path derived from the last opened/saved slide file, or the current working directory if no usable record exists.

Return type:

str

_prompt_worship_title(default_title)[source]

Ask the user for the worship title that should back the session filename.

The dialog is pre-filled with the selected JSON filename stem. Empty values and filenames containing invalid characters are rejected and the user is prompted again until a valid value is entered.

If the user presses Cancel or closes the dialog, the original default title is used instead of aborting the load flow.

Parameters:

default_title (str) – Initial text shown in the input field.

Returns:

Validated title text.

Return type:

str | None

_resolve_selected_session_path(source_path)[source]

Resolve the actual path to load after a user picks a session JSON file.

Keeping the default title loads the original file. Changing the title creates a same-folder copy with the new filename and loads that copy.

Parameters:

source_path (str) – Path selected in the open-file dialog.

Returns:

Path that should be loaded into the generator, or None if the title step is canceled or the copy operation fails.

Return type:

str | None

prompt_load_from_file()[source]

Interactively select a slide JSON file and load it into the generator.

This wrapper owns the startup/manual-open flow that the user sees:

  1. Show the OS file-open dialog.

  2. Ask for the worship title using the filename stem as the default.

  3. If the title changes, duplicate the JSON file in the same folder.

  4. Load the chosen original/copy into the generator.

Returns:

Loaded file path, or None if the flow is canceled.

Return type:

str | None

load_from_file(path=None)[source]

Load a slide session JSON file into the generator.

If path is None, an OS file-open dialog is shown. The initial directory is derived from the last opened file record (if available); otherwise the current working directory is used.

After loading:

  • The table is populated via the data manager

  • The worship/session name label is updated from the filename stem

  • first_save_done is reset so the next Ctrl+S follows the intended flow

Parameters:

path (str | None) – Absolute or relative path to the JSON file. If None, a dialog is shown.

Returns:

The resolved path that was loaded, or None if the user canceled the dialog or no file was selected.

Return type:

str | None

save_as()[source]

Save the current session using an explicit “Save As” dialog.

This always forces the file-save dialog regardless of whether a previous save path exists.

Parameters:

None

Returns:

None

save_to_file(path=None, force_dialog=False)[source]

Save the current slide session as a JSON file.

Save destination selection rules:

  • If path is provided, saves directly to that path.

  • Else if force_dialog is False and self.last_saved_path exists, saves to self.last_saved_path without prompting.

  • Otherwise, opens an OS file-save dialog and saves to the chosen path.

The slide session is collected via core.generator.utils.slide_generator_data_manager.SlideGeneratorDataManager.collect_slide_data() and written as UTF-8 JSON.

Parameters:
  • path (str | None) – Destination file path. If None, follows the selection rules above.

  • force_dialog (bool) – If True, always shows the OS save dialog when path is None.

Returns:

None

_get_announcement_import_settings_path()[source]

Return the JSON path used to persist announcement import range settings.

This file is stored alongside the existing “last opened file” setting so that the announcement import feature can keep its own lightweight, dedicated configuration without affecting unrelated generator settings.

Parameters:

None

Returns:

Absolute path to the announcement import settings JSON file.

Return type:

str

_load_announcement_import_settings()[source]

Load persisted announcement import marker settings.

If the settings file does not exist or cannot be parsed, default marker values are returned.

Parameters:

None

Returns:

Dictionary containing:

  • start_headline (str):

    Headline text that marks the first slide of the import block.

  • end_headline (str):

    Headline text that marks the last slide of the import block. If empty, the block extends to the end of the file.

Return type:

dict

_save_announcement_import_settings(start_headline, end_headline)[source]

Save announcement import marker settings to a dedicated JSON file.

Parameters:
  • start_headline (str) – Headline text marking the first slide of the import block.

  • end_headline (str) – Headline text marking the last slide of the import block. If empty, the block extends to the end of the file.

Returns:

None

Return type:

None

_prompt_announcement_import_range(start_default, end_default)[source]

Open a modal dialog that asks the user for announcement import markers.

The dialog lets the user define the start and end headlines used when extracting and replacing the announcement block. If the end headline is left empty, the import block extends to the end of the file.

Parameters:
  • start_default (str) – Default text to pre-fill in the start marker field.

  • end_default (str) – Default text to pre-fill in the end marker field.

Returns:

Two-element tuple of (start_headline, end_headline).

  • If the user accepts the dialog, stripped string values are returned.

  • If the user cancels the dialog, (None, None) is returned.

Return type:

tuple[str | None, str | None]

_find_slide_index_by_headline(slides, headline, start_index=0)[source]

Find the first slide index whose headline matches the given text.

Matching is performed using stripped exact string comparison.

Parameters:
  • slides (list[dict]) – Slide dictionaries to search.

  • headline (str) – Target headline text to match.

  • start_index (int) – Row index from which to begin the search.

Returns:

Index of the first matching slide, or None if not found.

Return type:

int | None

_load_slide_list_into_table(slides)[source]

Load an in-memory slide list into the generator table.

This helper clears the current table contents, recreates the required rows, and writes each slide’s style/caption/headline into the visible UI.

Parameters:

slides (list[dict]) – Slide dictionaries to render into the generator table.

Returns:

None

Return type:

None

_compact_order_text(text)[source]

Remove whitespace from a worship-order string for loose comparisons.

Parameters:

text (str) – Source text to normalize for structural matching.

Returns:

Text with all whitespace removed.

Return type:

str

_split_choir_caption_parts(choir_name)[source]

Split a choir label like 시온찬양대 into ("시온", "찬양대").

Parameters:

choir_name (str) – Full choir label parsed from the worship-order source.

Returns:

Two-element tuple of (caption, caption_choir). If no standard suffix is found, returns (choir_name, "").

Return type:

tuple[str, str]

_get_default_bible_version_for_order_import()[source]

Choose the preferred Bible version for worship-order verse imports.

The selection prioritizes locally available Korean Revised Version files and falls back to the first available JSON Bible dataset when the preferred names are not present.

Parameters:

None

Returns:

Version name to pass to BibleDataLoader.

Return type:

str

_build_hymn_slide_from_number(number)[source]

Build a hymn slide payload from a hymn number dataset.

Parameters:

number (int) – Hymn number to load from data/hymns.

Returns:

Slide dictionary containing the hymn style, caption, and headline text for the requested hymn.

Return type:

dict

_build_respo_slide_from_number(number)[source]

Build a responsive-reading slide payload from a reading number dataset.

Parameters:

number (int) – Responsive reading number to load from data/respo.

Returns:

Slide dictionary containing the responsive-reading style, caption, and combined headline HTML text.

Return type:

dict

_build_verse_slide_from_reference(reference)[source]

Build a Bible-reading slide payload from a parsed reference string.

Parameters:

reference (str) – Scripture reference text extracted from the worship-order bulletin.

Returns:

Slide dictionary containing the verse style, caption, and resolved Bible text. If parsing fails, an empty verse headline is returned with the original caption preserved.

Return type:

dict

_classify_worship_order_slide(slides, index)[source]

Classify a slide into a worship-order slot category.

Parameters:
  • slides (list[dict]) – Full slide list currently being analyzed.

  • index (int) – Index of the slide to classify.

Returns:

Normalized worship-order kind such as hymn or sermon, or None when the slide does not match a managed category.

Return type:

str | None

_describe_worship_order_slide(slide)[source]

Produce a short human-readable description of a worship-order slide.

Parameters:

slide (dict) – Slide dictionary to summarize.

Returns:

Compact text summary used in removal-confirmation prompts.

Return type:

str

_prompt_remove_missing_worship_order(slide)[source]

Ask the user whether a slide missing from the imported order should be removed.

Parameters:

slide (dict) – Existing slide dictionary that is not present in the imported HWPX worship-order data.

Returns:

True if the user approves removal, otherwise False.

Return type:

bool

_remove_worship_order_block(slides, index)[source]

Remove a managed worship-order block from the working slide list.

Anthem blocks span two slides in this project: the anthem slide and its following lyrics slide. Other kinds are removed as a single slide.

Parameters:
  • slides (list[dict]) – Mutable slide list being updated.

  • index (int) – Index of the first slide in the block to remove.

Returns:

None

Return type:

None

_build_worship_order_blocks(slides)[source]

Group the current slide list into first-service worship-order blocks.

Parameters:

slides (list[dict]) – Existing slide dictionaries collected from the generator table.

Returns:

Ordered block dictionaries. Each block stores its classified kind and the slides that should move together during updates.

Return type:

list[dict]

_build_new_worship_order_block(entry)[source]

Build a new generic first-service slide block for a parsed entry.

Parameters:

entry (dict) – Parsed first-service worship-order entry dictionary.

Returns:

New slide dictionaries that represent the requested entry. The block may contain one or more slides depending on the kind.

Return type:

list[dict]

_update_worship_order_block(existing_slides, entry)[source]

Update an existing first-service block using a parsed entry.

Parameters:
  • existing_slides (list[dict]) – Existing slide dictionaries that make up the matched block.

  • entry (dict) – Parsed first-service worship-order entry dictionary.

Returns:

Updated slide dictionaries for the block. When the existing block cannot be updated safely, a new generic block is returned.

Return type:

list[dict]

_apply_worship_order_entries(current_slides, order_entries)[source]

Apply imported worship-order entries to the current generator session.

This routine updates matching managed slide slots, prompts about removing obsolete items, and inserts selected special-order templates when needed. Sermon titles are updated conservatively so that imported text is preserved unless an exact preacher suffix can be removed using the current sermon slide caption as context.

Parameters:
  • current_slides (list[dict]) – Existing slide dictionaries collected from the generator table.

  • order_entries (list[dict]) – Parsed worship-order entry dictionaries extracted from HWPX.

Returns:

Updated slide list ready to be reloaded into the generator table.

Return type:

list[dict]

_split_music_group_parts(group_name)[source]

Split a music-group label into its main caption and suffix.

Parameters:

group_name (str) – Full group label such as 마리아찬양대 or 여호수아중창단.

Returns:

Two-element tuple of (caption, caption_choir). If no supported suffix is found, returns (group_name, "").

Return type:

tuple[str, str]

_classify_praise_service_slide(slides, index)[source]

Classify a slide into an afternoon praise-service slot category.

Parameters:
  • slides (list[dict]) – Full slide list currently being analyzed.

  • index (int) – Index of the slide to classify.

Returns:

Normalized afternoon-service kind such as hymn or special_praise, or None when the slide does not match a managed category.

Return type:

str | None

_build_praise_service_blocks(slides)[source]

Group the current slide list into afternoon-service update blocks.

Parameters:

slides (list[dict]) – Existing slide dictionaries collected from the generator table.

Returns:

Ordered block dictionaries. Each block stores its classified kind and the slides that should move together during updates.

Return type:

list[dict]

_remove_exact_caption_suffix_from_imported_text(imported_text, current_caption)[source]

Remove an exact trailing caption suffix from imported text when present.

Parameters:
  • imported_text (str) – Imported text that may include a glued leader or preacher suffix.

  • current_caption (str) – Existing slide caption used as the authoritative suffix to strip.

Returns:

Imported text with the exact compacted caption suffix removed from the end when a match exists. Otherwise returns the input text unchanged.

Return type:

str

_build_new_praise_service_block(entry)[source]

Build a new generic afternoon-service slide block for a parsed entry.

Parameters:

entry (dict) – Parsed afternoon praise-service entry dictionary.

Returns:

New slide dictionaries that represent the requested entry. The block may contain one or more slides depending on the kind.

Return type:

list[dict]

_update_praise_service_block(existing_slides, entry)[source]

Update an existing afternoon-service block using a parsed entry.

Parameters:
  • existing_slides (list[dict]) – Existing slide dictionaries that make up the matched block.

  • entry (dict) – Parsed afternoon praise-service entry dictionary.

Returns:

Updated slide dictionaries for the block. When the existing block cannot be updated safely, a new generic block is returned.

Return type:

list[dict]

_flatten_slide_blocks(blocks)[source]

Flatten a block list back into a single slide list.

Parameters:

blocks (list[dict]) – Block dictionaries that each contain a slides list.

Returns:

Flat slide dictionary list in block order.

Return type:

list[dict]

_apply_praise_service_order_entries(current_slides, order_entries)[source]

Apply imported afternoon praise-service entries to the current session.

Parameters:
  • current_slides (list[dict]) – Existing slide dictionaries collected from the generator table.

  • order_entries (list[dict]) – Parsed afternoon praise-service entry dictionaries extracted from HWPX.

Returns:

Updated slide list ready to be reloaded into the generator table.

Return type:

list[dict]

import_worship_order_from_hwpx()[source]

Import first-service worship-order information from a HWPX bulletin and conservatively update matching slots in the current generator session.

This first-pass implementation updates the existing session structure rather than rebuilding the whole file from scratch.

Parameters:

None

Returns:

None

import_praise_service_order_from_hwpx()[source]

Import afternoon praise-service order information from a HWPX bulletin and conservatively update matching slots in the current generator session.

Missing special-order items are inserted with generic blocks when needed, while unmatched existing afternoon-service blocks can be kept or removed through confirmation prompts.

Parameters:

None

Returns:

None

_replace_announcement_block_in_slides(slides, imported_slides, start_anchor='오늘 처음 오신 분들을 환영하고 축복합니다!', end_anchor='용서, 사랑의 시작입니다')[source]

Replace the announcement block in a slide list using fixed headline anchors.

Parameters:
  • slides (list[dict]) – Working slide list that already contains the surrounding fixed welcome and closing slides.

  • imported_slides (list[dict]) – Announcement slides extracted from a source HWPX or JSON file.

  • start_anchor (str) – Headline text that marks the first fixed slide before the announcement block.

  • end_anchor (str) – Headline text that marks the first fixed slide after the announcement block.

Returns:

Updated slide list with the announcement block replaced, or None if the anchor range cannot be resolved safely.

Return type:

list[dict] | None

import_announcements_from_hwpx()[source]

Import announcements from a HWPX bulletin and replace the current announcement area between the fixed welcome slide and the fixed closing-verse slide.

Imported items are intentionally inserted as lyrics style only.

The source HWPX file is parsed into announcement slides, and the matching range in the current session is replaced using fixed headline anchors that mark the start and end of the announcement block.

Parameters:

None

Returns:

None

import_worship_order_and_announcements_from_hwpx()[source]

Import both first-service worship-order information and announcement slides from a single HWPX bulletin and apply them in one pass.

The existing session is first updated with the imported worship order, then the announcement block between the fixed anchors is replaced with announcement slides extracted from the same HWPX file.

Parameters:

None

Returns:

None

import_praise_service_order_and_announcements_from_hwpx()[source]

Import both afternoon praise-service order information and announcement slides from a single HWPX bulletin and apply them in one pass.

The existing session is first updated with the imported afternoon praise-service order, then the announcement block between the fixed anchors is replaced with announcement slides extracted from the same HWPX file.

Parameters:

None

Returns:

None

import_announcements()[source]

Import a configurable slide block from an external slide JSON file and replace the corresponding block in the current session.

This method opens a file-selection dialog, asks the user for the start and end headline markers that define the import range, persists those marker values to a dedicated settings JSON file, and then replaces the matching range in the currently loaded generator table.

The imported range is extracted from the selected source file and applied onto the current session using the same start/end markers.

Parameters:

None

Returns:

None

Raises:
  • None explicitly.

  • Any file I/O or JSON parsing errors are handled internally and

  • reported to the user via message dialogs.

Notes

  • Range boundaries are detected by exact headline matching after stripping leading and trailing whitespace.

  • If the end marker is empty, the import block extends to the end of the file.

  • If either the source file or the current session does not contain the requested start marker, no changes are applied.

  • This function modifies the generator table in place and does not automatically save the session file.

  • User-edited start/end marker values are persisted and reused as defaults in subsequent imports.

warn_if_controller_running()[source]

Warn the user if the slide controller is currently running.

If the controller is running, edits made in the generator may not be reflected in the live output until the controller is restarted. This method shows a warning dialog and signals whether editing should be blocked.

Parameters:

None

Returns:

True if the controller is running (warning shown), False otherwise.

Return type:

bool

export_slides_for_overlay()[source]

Export the current session into overlay-ready JSON and launch the controller.

Steps:

  1. Collect slide session data from the table.

  2. Prepend a blank slide to ensure a clean initial screen.

  3. Convert slides into overlay format via core.generator.utils.slide_exporter.SlideExporter.

  4. Write the exported JSON to core.config.paths.SLIDE_FILE (UTF-8).

  5. Launch the slide controller if it is not already running.

See also core.config.constants.MAX_CHARS.

Parameters:

None

Returns:

None

handle_table_double_click(row, column)[source]

Open the style-specific slide editor dialog for the selected table row.

This method:

  • Reads the current style/caption/headline values from the table row

  • Converts the displayed style label into an internal style key

  • Opens core.generator.ui.slide_generator_dialog.SlideGeneratorDialog as a modal editor

  • If the user accepts, writes the updated values back into the table and triggers a save flow

Parameters:
  • row (int) – Row index of the double-clicked table row.

  • column (int) – Column index of the double-click event. (Currently unused.)

Returns:

None

open_settings_dialog()[source]

Open the generator settings dialog and apply changes if accepted.

If the dialog is accepted:

  • Settings are persisted via the dialog’s save routine

  • Font settings are (intended to be) applied to the generator UI

Parameters:

None

Returns:

None

apply_generator_font_settings()[source]

Apply the current persistent font settings to the generator UI.

Reads the generator settings and constructs a QFont using:

  • font_family (default: “Malgun Gothic”)

  • font_size (default: 24)

  • font_weight (default: “Normal”; “Bold” enables bold)

Parameters:

None

Returns:

None