# -*- coding: utf-8 -*-
"""
:File: EuljiroBible/gui/ui/tab_keyword_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.
Defines the UI layout and interaction hooks for the keyword-based search tab in EuljiroBible.
"""
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QComboBox,
QTextEdit, QSizePolicy, QTableView,
QSplitter, QRadioButton, QButtonGroup,
QHeaderView
)
from gui.ui.common import create_svg_text_button
[docs]
class TabKeywordUI:
"""
UI builder mixin for the keyword search tab.
This class constructs the widget layout and connects UI events for keyword
searching, including version selection, keyword input, search mode toggles,
result table display, summary output, and overlay export controls.
The actual business logic (search, export, clearing output) is delegated to the
owning tab class and its logic backend (e.g., TabKeyword and TabKeywordLogic).
Attributes:
get_polling_status (Callable[[], bool]): Callback used to determine whether
polling mode is enabled (controls button visibility).
get_always_show_setting (Callable[[], bool]): Callback used to determine
whether buttons should always be shown regardless of polling state.
version_box (QComboBox): Dropdown for selecting a Bible version.
keyword_input (QLineEdit): Text input for search keywords.
radio_and (QRadioButton): Search mode radio for "all words" style search.
radio_compact (QRadioButton): Search mode radio for "exact phrase" search.
radio_group (QButtonGroup): Radio button group for mutually exclusive modes.
search_button (QPushButton): Button triggering a keyword search.
select_button (QPushButton): Button exporting the selected verse.
clear_button (QPushButton): Button clearing overlay output.
table (QTableView): Result table view for search hits.
summary_title_label (QLabel): Label for the summary section.
summary_box (QTextEdit): Read-only summary output widget.
"""
[docs]
def init_ui(self, version_list, get_polling_status, get_always_show_setting):
"""
Initialize the UI layout and bind widget events.
Args:
version_list (List[str]): List of available Bible version strings.
get_polling_status (Callable[[], bool]): Callback for checking polling toggle state.
get_always_show_setting (Callable[[], bool]): Callback for checking the
"always show buttons" setting.
"""
self.get_polling_status = get_polling_status
self.get_always_show_setting = get_always_show_setting
layout = QVBoxLayout()
# 1. Version dropdown
self.version_box = QComboBox()
for version_key in version_list:
display_name = self.bible_data.get_version_display_name(version_key)
self.version_box.addItem(display_name, userData=version_key)
self.version_box.setItemData(
self.version_box.count() - 1,
version_key,
Qt.ToolTipRole
)
version_row = QHBoxLayout()
version_row.addWidget(self.version_box)
layout.addLayout(version_row)
# 2. Keyword input with search mode selection and button
self.keyword_input = QLineEdit()
self.keyword_input.returnPressed.connect(self.run_search)
self.radio_and = QRadioButton(self.tr("search_mode_all")) # "All words"
self.radio_compact = QRadioButton(self.tr("search_mode_compact")) # "Exact phrase"
self.radio_and.setChecked(True)
self.radio_group = QButtonGroup()
self.radio_group.addButton(self.radio_and)
self.radio_group.addButton(self.radio_compact)
self.search_button = create_svg_text_button(
"resources/svg/btn_search.svg", self.tr("btn_search"),
30, "Search", self.run_search
)
search_row = QHBoxLayout()
search_row.addWidget(self.radio_and)
search_row.addWidget(self.radio_compact)
search_row.addWidget(self.keyword_input)
search_row.addWidget(self.search_button)
layout.addLayout(search_row)
# 3. Output & Clear buttons
self.select_button = create_svg_text_button(
"resources/svg/btn_output.svg", self.tr("btn_output"),
30, "Start slide show", self.save_selected_verse
)
self.clear_button = create_svg_text_button(
"resources/svg/btn_clear.svg", self.tr("btn_clear"),
30, "Stop slide show", self.clear_outputs
)
btns = QHBoxLayout()
self.btns = btns
btns.addWidget(self.select_button)
btns.addWidget(self.clear_button)
layout.addLayout(btns)
# 4. Table and summary section
self.table = QTableView()
self.table.setAlternatingRowColors(True)
self.table.setWordWrap(True)
self.table.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
self.table.setEditTriggers(QTableView.EditTrigger.NoEditTriggers)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.table.setStyleSheet("QTableView::item { padding: 6px; }")
self.table.doubleClicked.connect(self.on_double_click_save)
self.summary_title_label = QLabel(self.tr("search_summary"))
self.summary_box = QTextEdit()
self.summary_box.setReadOnly(True)
self.summary_box.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
bottom_container = QWidget()
bottom_layout = QVBoxLayout()
bottom_layout.setContentsMargins(4, 0, 4, 0)
bottom_layout.setSpacing(4)
bottom_layout.addWidget(self.summary_title_label)
bottom_layout.addWidget(self.summary_box)
bottom_container.setLayout(bottom_layout)
splitter = QSplitter(Qt.Vertical)
splitter.addWidget(self.table)
splitter.addWidget(bottom_container)
splitter.setStretchFactor(0, 3)
splitter.setStretchFactor(1, 1)
layout.addWidget(splitter)
self.setLayout(layout)
[docs]
def on_double_click_save(self, index):
"""
Handle a double-click event on the result table.
If a valid column is clicked, this delegates saving/exporting logic to the
logic backend through the owning tab instance.
Args:
index (QModelIndex): Clicked table index.
"""
if index.column() < 0:
return
self.logic.save_selected_verse(self)