# -*- coding: utf-8 -*-
"""
:File: EuljiroBible/gui/ui/tab_verse_ui.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.
Builds the TabVerse UI layout and handles basic input/output interactions.
"""
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton,
QComboBox, QTextEdit, QScrollArea,
QGridLayout, QSplitter, QMessageBox
)
from PySide6.QtGui import QStandardItemModel
from gui.ui.common import create_svg_text_button, LoadingIndicator
[docs]
class TabVerseUI:
"""
UI builder mixin for the verse tab.
This class constructs the widget layout and connects UI events for verse lookup,
including version selection checkboxes, book/chapter/verse inputs, navigation
buttons, and a read-only display panel for rendered verse output.
The owning tab class (TabVerse) is responsible for providing:
- selection_manager (TabVerseSelectionManager)
- logic (TabVerseLogic)
- handlers such as get_reference(), apply_output_text(), shift_verse(), clear_outputs()
Attributes:
version_scroll (QScrollArea): Scroll area containing the version checkbox grid.
version_widget (QWidget): Container widget for the version checkbox grid.
version_layout (QGridLayout): Grid layout holding version selection checkboxes.
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.
alias_toggle_btn (QPushButton): Toggle button switching alias/full version summary mode.
version_summary_label (QLabel): Label summarizing currently selected Bible versions.
book_label (QLabel): Label for the book selector.
chapter_label (QLabel): Label for the chapter selector.
verse_label (QLabel): Label for the verse input field.
book_combo (QComboBox): Editable dropdown for selecting a book name.
chapter_input (QComboBox): Editable dropdown for chapter selection.
verse_input (QLineEdit): Verse input field supporting single verses or ranges.
search_btn (QPushButton): Button that triggers verse display.
save_btn (QPushButton): Button that exports the currently formatted verse output.
clear_display_btn (QPushButton): Button that clears output destination and display.
prev_verse_btn (QPushButton): Button that navigates to the previous verse.
next_verse_btn (QPushButton): Button that navigates to the next verse.
input_layout (QHBoxLayout): Layout containing book/chapter/verse inputs and buttons.
button_layout (QHBoxLayout): Layout containing action/navigation buttons.
display_box (QTextEdit): Read-only display widget for rendered verse output.
loading_indicator (LoadingIndicator): Overlay indicator shown during long operations.
"""
[docs]
def init_ui(self, version_list):
"""
Initialize and assemble the full verse tab UI.
This builds:
- A scrollable grid of version selection checkboxes
- Alias/full-name toggle and selected-version summary label
- Book/chapter/verse input row (with editable combos)
- Action buttons for display/export/clear and verse navigation
- A read-only display box for rendered output
- A loading indicator overlay
Args:
version_list (List[str]): List of Bible versions used to create checkboxes.
"""
# Scrollable checkbox grid for Bible versions
self.version_scroll = QScrollArea()
self.version_scroll.setWidgetResizable(True)
self.version_widget = QWidget()
self.version_layout = QGridLayout(self.version_widget)
self.version_scroll.setWidget(self.version_widget)
# Add version checkboxes dynamically
for version in enumerate(version_list):
if isinstance(version, tuple):
_, version_name = version
else:
version_name = version
checkbox = self.selection_manager.create_version_checkbox(self, version_name)
self.version_layout.addWidget(checkbox)
# Adjust grid layout
self.selection_manager.update_grid_layout(self)
# Internal UI state
self.enter_state = 0
self.use_alias = False
# Toggle button for alias/full version names
self.alias_toggle_btn = QPushButton(self.tr("label_alias_full"))
self.alias_toggle_btn.setCheckable(True)
self.alias_toggle_btn.setChecked(False)
self.alias_toggle_btn.clicked.connect(self.toggle_alias_mode)
# Label summarizing selected versions
self.version_summary_label = QLabel(self.tr("msg_nothing"))
version_label_layout = QHBoxLayout()
version_label_layout.addWidget(self.alias_toggle_btn)
version_label_layout.addWidget(self.version_summary_label)
version_label_layout.addStretch()
# Labels for input fields
self.book_label = QLabel(self.tr("label_book"))
self.chapter_label = QLabel(self.tr("label_chapter"))
self.verse_label = QLabel(self.tr("label_verse"))
# Book dropdown with inline editing
self.book_combo = QComboBox()
self.book_combo.setModel(QStandardItemModel(self.book_combo))
self.book_combo.setEditable(True)
self.book_combo.setInsertPolicy(QComboBox.NoInsert)
# Chapter and verse inputs
self.chapter_input = QComboBox()
self.chapter_input.setEditable(True)
self.chapter_input.lineEdit().returnPressed.connect(self.handle_enter)
self.verse_input = QLineEdit()
self.verse_input.setPlaceholderText(self.tr("verse_input_hint"))
self.verse_input.returnPressed.connect(self.handle_enter)
# Action buttons
self.search_btn = create_svg_text_button(
"resources/svg/btn_search.svg",
self.tr("btn_search"),
30,
"Search",
self._on_display_verse
)
self.save_btn = create_svg_text_button(
"resources/svg/btn_output.svg",
self.tr("btn_output"),
30,
"Start slide show",
self._on_save_verse
)
self.clear_display_btn = create_svg_text_button(
"resources/svg/btn_clear.svg",
self.tr("btn_clear"),
30,
"Stop slide show",
self.clear_outputs
)
self.prev_verse_btn = create_svg_text_button(
"resources/svg/btn_prev.svg",
self.tr("btn_prev"),
30,
"Go to previous verse",
lambda: self.shift_verse(-1)
)
self.next_verse_btn = create_svg_text_button(
"resources/svg/btn_next.svg",
self.tr("btn_next"),
30,
"Go to next verse",
lambda: self.shift_verse(1)
)
# Layout for book/chapter/verse input
input_layout = QHBoxLayout()
self.input_layout = input_layout
input_layout.addWidget(self.book_label)
input_layout.addWidget(self.book_combo)
input_layout.addWidget(self.chapter_label)
input_layout.addWidget(self.chapter_input)
input_layout.addWidget(self.verse_label)
input_layout.addWidget(self.verse_input)
# Adjust input field stretch ratios
input_layout.setStretch(1, 2)
input_layout.setStretch(3, 1)
input_layout.setStretch(5, 1)
# Button row layout
button_layout = QHBoxLayout()
self.button_layout = button_layout
button_layout.addWidget(self.prev_verse_btn)
button_layout.addWidget(self.search_btn)
poll_enabled = self.settings.get("poll_enabled", False)
if poll_enabled:
button_layout.addWidget(self.save_btn)
button_layout.addWidget(self.next_verse_btn)
if poll_enabled:
button_layout.addWidget(self.clear_display_btn)
# Add button row to the input layout
input_layout.addLayout(button_layout)
# Display box for output
self.display_box = QTextEdit()
self.display_box.setReadOnly(True)
# Loading indicator overlay
self.loading_indicator = LoadingIndicator(self.display_box.viewport())
self.loading_indicator.hide()
# Lower panel combining input and display
bottom_widget = QWidget()
bottom_layout = QVBoxLayout(bottom_widget)
bottom_layout.addLayout(input_layout)
bottom_layout.addWidget(self.display_box)
# Splitter between version list and display panel
splitter = QSplitter(Qt.Vertical)
splitter.addWidget(self.version_scroll)
splitter.addWidget(bottom_widget)
splitter.setSizes([150, 400])
# Final layout
layout = QVBoxLayout()
layout.addLayout(version_label_layout)
layout.addWidget(splitter)
self.setLayout(layout)
# Add loading animation widget last
layout.addWidget(self.loading_indicator)
[docs]
def _on_display_verse(self):
"""
Display the current verse reference.
This is invoked by the Search button and delegates rendering to the logic layer
using the current reference resolver and output applicator.
"""
output = self.logic.display_verse(
self.get_reference,
self.verse_input,
self.apply_output_text
)
if output:
self.formatted_verse_text = output
[docs]
def _on_save_verse(self):
"""
Export the currently displayed verse output.
This is invoked by the Save button and delegates persistence to the logic layer.
A critical dialog is shown if saving fails.
"""
try:
self.logic.save_verse(self.formatted_verse_text)
except Exception as e:
QMessageBox.critical(
self,
self.tr("error_output_title"),
self.tr("error_output_msg").format(str(e))
)