import re
import functools
from PyQt6.QtCore import Qt, QObject, QThread, QMetaObject, QCoreApplication, pyqtSlot, pyqtSignal
from PyQt6.QtWidgets import QWidget, QHBoxLayout, QRadioButton, QButtonGroup, QVBoxLayout, QCheckBox, QScrollArea, QLabel
from qfluentwidgets import MessageBoxBase, SubtitleLabel, BodyLabel, LineEdit, PushButton, ProgressBar
from Scripts.datasets import os_data
_default_gui_handler = None
def set_default_gui_handler(handler):
global _default_gui_handler
_default_gui_handler = handler
class ThreadRunner(QObject):
def __init__(self, func, *args, **kwargs):
super().__init__()
self.func = func
self.args = args
self.kwargs = kwargs
self.result = None
self.exception = None
@pyqtSlot()
def run(self):
try:
self.result = self.func(*self.args, **self.kwargs)
except Exception as e:
self.exception = e
def ensure_main_thread(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if QThread.currentThread() == QCoreApplication.instance().thread():
return func(*args, **kwargs)
runner = ThreadRunner(func, *args, **kwargs)
runner.moveToThread(QCoreApplication.instance().thread())
QMetaObject.invokeMethod(runner, "run", Qt.ConnectionType.BlockingQueuedConnection)
if runner.exception:
raise runner.exception
return runner.result
return wrapper
class CustomMessageDialog(MessageBoxBase):
def __init__(self, title, content):
super().__init__(_default_gui_handler)
self.titleLabel = SubtitleLabel(title, self.widget)
self.contentLabel = BodyLabel(content, self.widget)
self.contentLabel.setWordWrap(True)
is_html = bool(re.search(r"<[^>]+>", content))
if is_html:
self.contentLabel.setTextFormat(Qt.TextFormat.RichText)
self.contentLabel.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
self.contentLabel.setOpenExternalLinks(True)
self.viewLayout.addWidget(self.titleLabel)
self.viewLayout.addWidget(self.contentLabel)
self.widget.setMinimumWidth(600)
self.custom_widget = None
self.input_field = None
self.button_group = None
def add_input(self, placeholder: str = "", default_value: str = ""):
self.input_field = LineEdit(self.widget)
if placeholder:
self.input_field.setPlaceholderText(placeholder)
if default_value:
self.input_field.setText(str(default_value))
self.viewLayout.addWidget(self.input_field)
self.input_field.setFocus()
return self.input_field
def add_custom_widget(self, widget: QWidget):
self.custom_widget = widget
self.viewLayout.addWidget(widget)
def add_radio_options(self, options, default_index=0):
self.button_group = QButtonGroup(self)
container = QWidget()
layout = QVBoxLayout(container)
layout.setContentsMargins(10, 5, 10, 5)
for i, option_text in enumerate(options):
is_html = bool(re.search(r"<[^>]+>", option_text))
if is_html:
row_widget = QWidget()
row_layout = QHBoxLayout(row_widget)
row_layout.setContentsMargins(0, 0, 0, 0)
row_layout.setSpacing(8)
radio = QRadioButton()
label = BodyLabel(option_text)
label.setTextFormat(Qt.TextFormat.RichText)
label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
label.setOpenExternalLinks(True)
label.setWordWrap(True)
row_layout.addWidget(radio)
row_layout.addWidget(label, 1)
layout.addWidget(row_widget)
else:
radio = QRadioButton(option_text)
layout.addWidget(radio)
self.button_group.addButton(radio, i)
if i == default_index:
radio.setChecked(True)
self.viewLayout.addWidget(container)
return self.button_group
def add_checklist(self, items, checked_indices=None):
if checked_indices is None:
checked_indices = []
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFixedHeight(400)
container = QWidget()
layout = QVBoxLayout(container)
checkboxes = []
current_category = None
for i, item in enumerate(items):
label_text = item
category = None
supported = True
if isinstance(item, dict):
label_text = item.get("label", "")
category = item.get("category")
supported = item.get("supported", True)
if category and category != current_category:
current_category = category
if i > 0:
layout.addSpacing(10)
header = QLabel("Category: {}".format(category))
header.setStyleSheet("font-weight: bold; color: #0078D4; padding-top: 5px; padding-bottom: 5px; border-bottom: 1px solid #E1DFDD;")
layout.addWidget(header)
cb = QCheckBox(label_text)
if i in checked_indices:
cb.setChecked(True)
if not supported:
cb.setStyleSheet("color: #A19F9D;")
layout.addWidget(cb)
checkboxes.append(cb)
layout.addStretch()
scroll.setWidget(container)
self.viewLayout.addWidget(scroll)
return checkboxes
def configure_buttons(self, yes_text: str = "OK", no_text: str = "Cancel", show_cancel: bool = True):
self.yesButton.setText(yes_text)
self.cancelButton.setText(no_text)
self.cancelButton.setVisible(show_cancel)
@ensure_main_thread
def show_info(title: str, content: str) -> None:
dialog = CustomMessageDialog(title, content)
dialog.configure_buttons(yes_text="OK", show_cancel=False)
dialog.exec()
@ensure_main_thread
def show_confirmation(title: str, content: str, yes_text="Yes", no_text="No") -> bool:
dialog = CustomMessageDialog(title, content)
dialog.configure_buttons(yes_text=yes_text, no_text=no_text, show_cancel=True)
return dialog.exec()
@ensure_main_thread
def show_options_dialog(title, content, options, default_index=0):
dialog = CustomMessageDialog(title, content)
dialog.add_radio_options(options, default_index)
dialog.configure_buttons(yes_text="OK", show_cancel=True)
if dialog.exec():
return dialog.button_group.checkedId()
return None
@ensure_main_thread
def show_checklist_dialog(title, content, items, checked_indices=None):
dialog = CustomMessageDialog(title, content)
checkboxes = dialog.add_checklist(items, checked_indices)
dialog.configure_buttons(yes_text="OK", show_cancel=True)
if dialog.exec():
return [i for i, cb in enumerate(checkboxes) if cb.isChecked()]
return None
@ensure_main_thread
def ask_network_count(total_networks):
content = (
"Found {} WiFi networks on this device.
"
"How many networks would you like to process?
"
"