# -*- coding: utf-8 -*-
"""
:File: EuljiroBible/gui/ui/tab_verse.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.
Implements the :class:`gui.ui.tab_verse.TabVerse` class for verse lookup, navigation, display, and output.
"""
import traceback
from PySide6.QtWidgets import QWidget, QMessageBox, QGridLayout
from core.utils.bible_data_loader import BibleDataLoader
from core.utils.utils_output import save_to_files
from core.utils.verse_version_helper import VerseVersionHelper
from gui.ui.locale.message_loader import load_messages
from gui.ui.tab_verse_logic import TabVerseLogic
from gui.ui.tab_verse_selection_manager import TabVerseSelectionManager
from gui.ui.tab_verse_ui import TabVerseUI
from gui.utils.verse_output_handler import VerseOutputHandler
[docs]
class TabVerse(QWidget, TabVerseUI):
"""
GUI tab widget for Bible verse lookup, navigation, and export.
This tab allows users to select one or more Bible versions, choose a book and
chapter, enter verse ranges, preview formatted output, navigate between verses,
and export the currently displayed verse to the configured overlay output file.
The tab coordinates UI state, selection helpers, and backend logic components
(TabVerseLogic, VerseVersionHelper, TabVerseSelectionManager, VerseOutputHandler).
Attributes:
tr (Callable[[str], str]): Translation function for UI labels and messages.
settings (dict): Application-level settings dictionary.
bible_data (BibleDataLoader): Bible data loader instance used for verse retrieval.
version_layout (QGridLayout): Layout holding version selection checkboxes.
version_helper (VerseVersionHelper): Helper for version selection and sorting.
selection_manager (TabVerseSelectionManager): Helper for populating and syncing
book/chapter/version UI elements.
output_handler (VerseOutputHandler): Handler that applies formatted verse text
to the main display widget and manages output formatting behavior.
logic (TabVerseLogic): Backend verse display/export logic.
version_list (List[str]): Available Bible versions used to populate UI controls.
current_language (str): Current UI language code (e.g., "ko", "en").
formatted_verse_text (str): Most recently formatted verse output (used for Enter key flow).
enter_state (int): Enter-key state machine (0 = ready to display, 1 = ready to export).
use_alias (bool): Whether to display version aliases instead of full version names.
Note:
The following callables are intentionally omitted from ``Attributes`` to avoid duplicate autodoc entries, as they are already documented as class methods:
- get_polling_status (Callable[[], bool]): Callback returning the current polling toggle state.
- get_always_show_setting (Callable[[], bool]): Callback returning the current "always show buttons" toggle state.
"""
[docs]
def __init__(self, version_list, settings, tr, get_polling_status=None, get_always_show_setting=None):
"""
Initialize the TabVerse UI and connect backend helpers.
This creates Bible data access, builds the version selection UI, initializes
selection helpers and logic handlers, and installs UI signal connections for
resetting Enter-state behavior when inputs change.
Args:
version_list (List[str]): Available Bible version identifiers.
settings (dict): Loaded application settings.
tr (Callable[[str], str]): Translation function for UI labels.
get_polling_status (Callable[[], bool] | None): Optional callback that returns
whether polling is active.
get_always_show_setting (Callable[[], bool] | None): Optional callback that returns
whether export/clear buttons should always be shown.
"""
super().__init__()
self.tr = tr
self.initializing = False
self.settings = settings
self.formatted_verse_text = ""
self.get_polling_status = get_polling_status or self.get_polling_status
self.get_always_show_setting = get_always_show_setting or self.get_always_show_setting
# Load Bible data and UI components
self.bible_data = BibleDataLoader()
self.version_layout = QGridLayout()
self.version_helper = VerseVersionHelper(self.bible_data, self.version_layout)
self.selection_manager = TabVerseSelectionManager(self.bible_data, self.version_helper, self.tr)
self.version_list = version_list
self.init_ui(version_list)
# Re-assign layout after UI build
self.version_helper.version_layout = self.version_layout
# Sort versions and assign logic handlers
self.version_list = self.version_helper.sort_versions(version_list)
self.output_handler = VerseOutputHandler(self.display_box, self.settings)
self.current_language = settings.get("last_language", "ko")
self.logic = TabVerseLogic(self.bible_data, self.tr, self.settings, self.current_language)
self.book_combo.currentTextChanged.connect(lambda _: self.reset_enter_state())
self.chapter_input.currentIndexChanged.connect(lambda _: self.reset_enter_state())
self.verse_input.textChanged.connect(lambda _: self.reset_enter_state())
[docs]
def change_language(self, lang_code):
"""
Update UI labels and state to reflect a new language setting.
This updates visible text, version summary rendering, book dropdown population,
and button labels.
Args:
lang_code (str): Language code (e.g., "ko", "en").
"""
self.current_language = lang_code
self.messages = load_messages(lang_code)
selected_versions = self.version_helper.get_selected_versions()
summary = (
", ".join([self.bible_data.aliases_version.get(v, v) for v in selected_versions])
if selected_versions and self.use_alias else
", ".join(selected_versions) if selected_versions else
self.tr("msg_nothing")
)
self.version_summary_label.setText(summary)
# Update labels and placeholders
self.book_label.setText(self.tr("label_book"))
self.chapter_label.setText(self.tr("label_chapter"))
self.verse_label.setText(self.tr("label_verse"))
self.verse_input.setPlaceholderText(self.tr("verse_input_hint"))
self.selection_manager.populate_book_dropdown(self)
if hasattr(self, "alias_toggle_btn"):
self.alias_toggle_btn.setText(
self.tr("label_alias_short") if self.use_alias else self.tr("label_alias_full")
)
self.selection_manager.update_book_dropdown(self, self.current_language)
# Update button texts
self.prev_verse_btn.setText(self.tr("btn_prev"))
self.search_btn.setText(self.tr("btn_search"))
self.save_btn.setText(self.tr("btn_output"))
self.next_verse_btn.setText(self.tr("btn_next"))
self.clear_display_btn.setText(self.tr("btn_clear"))
[docs]
def resizeEvent(self, event):
"""
Handle resize events and recompute the version checkbox grid layout.
Args:
event (QResizeEvent): Resize event object.
"""
super().resizeEvent(event)
self.selection_manager.update_grid_layout(self)
[docs]
def get_polling_status(self):
"""
Return the current polling toggle state.
This method is designed to be overridden or replaced by an injected callback.
Returns:
bool: True if polling is enabled; otherwise False.
"""
return False # Default fallback, should be replaced by actual callback
[docs]
def get_always_show_setting(self):
"""
Return the current "always show buttons" setting.
This method is designed to be overridden or replaced by an injected callback.
Returns:
bool: True if export/clear buttons should always be visible; otherwise False.
"""
return False # Default fallback, should be replaced by actual callback
[docs]
def toggle_alias_mode(self):
"""
Toggle between alias names and full names for Bible version display.
This updates the alias mode flag, refreshes the toggle label, and updates the
version summary accordingly.
"""
self.use_alias = self.alias_toggle_btn.isChecked()
self.alias_toggle_btn.setText(
self.tr("label_alias_short") if self.use_alias else self.tr("label_alias_full")
)
self.update_version_summary(self)
[docs]
def handle_enter(self):
"""
Handle Enter-key behavior as a two-step flow: display then export.
If no formatted verse is currently staged, this displays the verse and caches
the formatted output. If a formatted verse is already staged, this exports it
to the output destination and resets the state machine.
"""
if not self.formatted_verse_text:
output = self.logic.display_verse(self.get_reference, self.verse_input, self.apply_output_text)
if output:
self.formatted_verse_text = output
self.enter_state = 1
else:
try:
self.logic.save_verse(self.formatted_verse_text)
except Exception as e:
print(traceback.format_exc())
QMessageBox.critical(
self,
self.tr("error_output_title"),
self.tr("error_output_msg").format(str(e))
)
self.enter_state = 0
[docs]
def get_reference(self):
"""
Build and return the resolved verse reference tuple from current UI inputs.
Returns:
tuple: (versions, book, chapter, (start_verse, end_verse)) as produced by
the shared reference resolver.
"""
from core.logic.verse_logic import resolve_reference
version_list = self.version_helper.get_selected_versions()
book_str = self.book_combo.currentText()
chapter_str = self.chapter_input.currentText()
verse_str = self.verse_input.text()
return resolve_reference(version_list, book_str, chapter_str, verse_str, self.bible_data, self.current_language)
[docs]
def apply_output_text(self, text: str):
"""
Apply formatted verse output text to the main display.
Args:
text (str): Formatted text to display.
"""
if text:
self.formatted_verse_text = text
self.output_handler.apply_output_text(text)
[docs]
def shift_verse(self, delta):
"""
Shift the current verse number by a delta and refresh the displayed output.
Args:
delta (int): +1 for next verse, -1 for previous verse.
"""
try:
self.logic.delta = delta
new_val = self.logic.shift_verse(self.get_reference, self.verse_input)
if new_val:
output = self.logic.display_verse(self.get_reference, self.verse_input, self.apply_output_text)
if output:
self.formatted_verse_text = output
except Exception:
QMessageBox.warning(
self,
self.tr("warn_jump_title"),
self.tr("warn_jump_msg")
)
[docs]
def reset_enter_state(self):
"""
Reset Enter-key state to the default "ready to display" mode.
This clears any staged formatted verse output so the next Enter press performs
a display operation.
"""
self.enter_state = 0
self.formatted_verse_text = ""
[docs]
def clear_outputs(self):
"""
Clear the verse display and clear the output destination.
This clears the UI display widget and writes an empty string to the configured
output file(s) to stop any active overlay display.
"""
self.display_box.clear()
save_to_files("", self.settings)