# -*- coding: utf-8 -*-
# Licensed under EUPL v1.2
# © 2020 bicobus <bicobus@keemail.me>
"""Constructors for the setting window.
Some of the design and code were influenced by `Spyder Ide <https://www.spyder-ide.org/>`_. Spyder
IDE is released under MIT.
The setting window has two major elements:
1. A side bar listing the different pages
2. A content area with the selected page
+----------+---------------+
| | |
| Pages | Pages content |
| names | |
| | sections |
| | contains |
| | widgets |
| | |
+----------+---------------+
| Yes / No |
+--------------------------+
"""
from PyQt5.QtCore import QSize, Qt, pyqtSignal # , pyqtSlot
from PyQt5.QtWidgets import (
QComboBox,
QDialog,
QDialogButtonBox,
QFileDialog,
QHBoxLayout,
QLabel,
QLineEdit,
QListView,
QListWidget,
QListWidgetItem,
QMessageBox,
QPushButton,
QScrollArea,
QSplitter,
QStackedWidget,
QVBoxLayout,
QWidget,
)
from qmm.common import settings
from qmm.settings.validators import IsDirValidator, make_html_list
[docs]def make_layout(parent, align, *widgets):
layout = QVBoxLayout(parent) if align == Qt.Vertical else QHBoxLayout(parent)
for widget in widgets:
layout.addWidget(widget)
layout.setContentsMargins(0, 0, 0, 0)
return layout
[docs]class Page(QWidget):
NAME = None
ICON = None
show_this_page = pyqtSignal()
def __init__(self, parent):
super().__init__(parent)
self.main = parent
self.pages = {}
self.validators = {}
self.lineedits = {}
self.comboboxes = {}
self.init_page()
[docs] def init_page(self):
self.setup_ui()
self.load_configuration()
[docs] def setup_ui(self):
raise NotImplementedError()
[docs] def get_name(self):
return self.NAME
[docs] def get_icon(self):
return self.ICON
[docs] def load_configuration(self):
for widget, confkey in self.lineedits.items():
try:
text = settings[confkey]
except KeyError:
text = ""
widget.setText(text)
for combobox, confkey in self.comboboxes.items():
try:
data = settings[confkey]
except KeyError:
data = None
idx = combobox.findData(data)
if idx != -1:
combobox.setCurrentIndex(idx)
[docs] def save(self):
for lineedit, confkey in self.lineedits.items():
data = lineedit.text()
if data != settings[confkey]:
settings[confkey] = data
[docs] def validate(self):
# NOTE: validation might only work for text input, either QLineEdit or
# QPlainTextEdit
for widget, validators in self.validators.items():
msgs = []
if not isinstance(validators, list):
validators = [validators]
for validator in validators:
try:
validator(widget.text())
except ValueError as e:
msgs.append(e.args[0])
if msgs:
QMessageBox.critical(
self,
self.get_name(),
"{}:<br><b>{}</b>".format(widget.text(), make_html_list(msgs)),
)
return False
return True
[docs] def c_lineedit(self, text, confkey, **qtparams):
wordwrap = qtparams.setdefault("wordwrap", False)
alignment = qtparams.setdefault("alignment", Qt.Vertical)
tip = qtparams.setdefault("tip", None)
placeholder = qtparams.setdefault("placeholder", None)
label = QLabel(text)
label.setWordWrap(wordwrap)
edit = QLineEdit()
layout = make_layout(self, alignment, label, edit)
if tip:
edit.setToolTip(tip)
if placeholder:
edit.setPlaceholderText(placeholder)
self.lineedits[edit] = confkey
widget = QWidget(self)
widget.label = label
widget.textbox = edit
widget.setLayout(layout)
edit.label_text = text
return widget
[docs] def c_browsedir(self, text, confkey, tip=None, placeholder=None):
widget = self.c_lineedit(
text, confkey, tip=tip, alignment=Qt.Horizontal, placeholder=placeholder,
)
edit = None
for edit in self.lineedits:
if widget.isAncestorOf(edit):
break
if not edit:
return None
self.validators[edit] = [IsDirValidator]
button = QPushButton(self)
button.setText(_("Browse..."))
button.setToolTip(_("Select a directory"))
button.clicked.connect(lambda: self.select_directory(edit))
layout = make_layout(self, Qt.Horizontal, widget, button)
browsedir = QWidget(self)
browsedir.setLayout(layout)
return browsedir
[docs] def select_directory(self, lineedit):
path = lineedit.text()
value = QFileDialog.getExistingDirectory(
parent=self,
caption=_("Select a directory"), # caption := window title
directory=path,
)
lineedit.setText(value)
[docs] def c_combobox(self, text, choices, confkey, tip=None):
label = QLabel(text)
combobox = QComboBox()
if tip:
combobox.setToolTip(tip)
for name, key in choices:
if name and key:
combobox.addItem(name, key)
i = 0
for idx, item in enumerate(choices):
name, key = item
if not name and not key:
combobox.insertSeparator(idx + i)
i += 1
self.comboboxes[combobox] = confkey
layout = make_layout(self, Qt.Horizontal, label, combobox)
layout.addStretch(1)
widget = QWidget(self)
widget.label = label
widget.combobox = combobox
widget.setLayout(layout)
return widget
[docs]class PreferencesDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.main = parent
self.pages_widget = QStackedWidget()
self.pages_widget.setMinimumSize(600, 0)
self.contents_widget = QListWidget()
bbox = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel
)
self.apply_btn = bbox.button(QDialogButtonBox.Apply)
self.ok_btn = bbox.button(QDialogButtonBox.Ok)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowTitle(_("Settings"))
self.contents_widget.setMovement(QListView.Static)
self.contents_widget.setSpacing(1)
self.contents_widget.setCurrentRow(0)
self.contents_widget.setMinimumWidth(220)
self.contents_widget.setMinimumHeight(400)
hsplitter = QSplitter()
hsplitter.addWidget(self.contents_widget)
hsplitter.addWidget(self.pages_widget)
hsplitter.setStretchFactor(0, 1)
hsplitter.setStretchFactor(1, 2)
btnlayout = QHBoxLayout()
btnlayout.addStretch(1)
btnlayout.addWidget(bbox)
vlayout = QVBoxLayout()
vlayout.addWidget(hsplitter)
vlayout.addLayout(btnlayout)
self.setLayout(vlayout)
bbox.accepted.connect(self.accept)
bbox.rejected.connect(self.reject)
bbox.clicked.connect(self.button_clicked)
self.contents_widget.currentRowChanged.connect(self.pages_widget.setCurrentIndex)
[docs] def accept(self):
"""Go through all the pages and save everything."""
for idx in range(0, self.pages_widget.count()):
page = self.get_page(idx)
if not page.validate():
return
page.save()
super().accept()
[docs] def add_page(self, widget):
widget.show_this_page.connect(
lambda row=self.contents_widget.count(): self.contents_widget.setCurrentRow(row)
)
scrollarea = QScrollArea(self)
scrollarea.setWidgetResizable(True)
scrollarea.setWidget(widget)
self.pages_widget.addWidget(scrollarea)
page_row = QListWidgetItem(self.contents_widget)
page_row.setText(widget.get_name())
page_row.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
page_row.setSizeHint(QSize(0, 25))
[docs] def get_page(self, index=None):
if not index:
widget = self.pages_widget.currentWidget()
else:
widget = self.pages_widget.widget(index)
if widget:
return widget.widget()
return None