Skip to content

Commit c1f8791

Browse files
committed
Create configdialog.py
1 parent b75cbfa commit c1f8791

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed

qtapputils/widgets/configdialog.py

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# -*- coding: utf-8 -*-
2+
# -----------------------------------------------------------------------------
3+
# Copyright © QtAppUtils Project Contributors
4+
# https://github.com/jnsebgosselin/qtapputils
5+
#
6+
# This file is part of QtAppUtils.
7+
# Licensed under the terms of the MIT License.
8+
# -----------------------------------------------------------------------------
9+
10+
# ---- Standard library imports
11+
import sys
12+
13+
# ---- Third party imports
14+
from qtpy.QtCore import (
15+
Qt, Signal, Slot, QRect, QPoint)
16+
from qtpy.QtGui import QIcon, QPixmap
17+
from qtpy.QtWidgets import (
18+
QAbstractButton, QApplication, QStyle, QStylePainter,
19+
QDialog, QPushButton, QDialogButtonBox, QWidget, QTabWidget,
20+
QGridLayout, QTabBar, QStyleOptionTab)
21+
22+
23+
class HorizontalTabBar(QTabBar):
24+
"""
25+
A custom tabbar to show tabs on the side, while keeping the text
26+
orientation horitontal.
27+
"""
28+
# https://www.manongdao.com/q-367474.html
29+
30+
def tabSizeHint(self, index):
31+
s = QTabBar.tabSizeHint(self, index)
32+
s.transpose()
33+
return s
34+
35+
def paintEvent(self, event):
36+
painter = QStylePainter(self)
37+
opt = QStyleOptionTab()
38+
39+
for i in range(self.count()):
40+
self.initStyleOption(opt, i)
41+
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
42+
43+
s = opt.rect.size()
44+
s.transpose()
45+
r = QRect(QPoint(), s)
46+
r.moveCenter(opt.rect.center())
47+
opt.rect = r
48+
49+
# We are painting the text ourselves so to align it
50+
# horizontally to the left.
51+
text = opt.text
52+
opt.text = ''
53+
54+
# Set state to 'Enable' to avoid vertical flickering of the
55+
# icon when tab is selected.
56+
opt.state = QStyle.State_Enabled
57+
58+
c = self.tabRect(i).center()
59+
painter.save()
60+
painter.translate(c)
61+
painter.rotate(90)
62+
painter.translate(-c)
63+
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
64+
painter.restore()
65+
66+
# Draw text.
67+
rect = self.tabRect(i)
68+
hspacing = QApplication.instance().style().pixelMetric(
69+
QStyle.PM_ButtonMargin)
70+
if not opt.icon.isNull():
71+
hspacing += self.iconSize().width() + 8
72+
rect.translate(hspacing, 0)
73+
painter.drawItemText(
74+
rect, int(Qt.AlignLeft | Qt.AlignVCenter),
75+
self.palette(), True, text)
76+
77+
78+
class ConfDialog(QDialog):
79+
"""
80+
A dialog window to manage app preferences.
81+
"""
82+
83+
def __init__(self, main, icon: QIcon = None, resizable: bool = True):
84+
super().__init__(main)
85+
self.main = main
86+
87+
self.setWindowTitle('Preferences')
88+
if icon is not None:
89+
self.setWindowIcon(icon)
90+
self.setWindowFlags(
91+
self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
92+
self.setModal(True)
93+
self.setMinimumHeight(500)
94+
95+
self.confpages_tabwidget = QTabWidget()
96+
self.confpages_tabwidget.setTabBar(HorizontalTabBar())
97+
self.confpages_tabwidget.setTabPosition(QTabWidget.West)
98+
self._confpages = {}
99+
100+
# Setup the dialog button box.
101+
self.ok_button = QPushButton('OK')
102+
self.ok_button.setDefault(False)
103+
self.ok_button.setAutoDefault(False)
104+
self.apply_button = QPushButton('Apply')
105+
self.apply_button.setDefault(True)
106+
self.apply_button.setEnabled(False)
107+
self.cancel_button = QPushButton('Cancel')
108+
self.cancel_button.setDefault(False)
109+
self.cancel_button.setAutoDefault(False)
110+
111+
button_box = QDialogButtonBox()
112+
button_box.addButton(self.ok_button, button_box.ApplyRole)
113+
button_box.addButton(self.cancel_button, button_box.RejectRole)
114+
button_box.addButton(self.apply_button, button_box.ApplyRole)
115+
button_box.layout().insertSpacing(1, 100)
116+
button_box.clicked.connect(self._handle_button_click_event)
117+
118+
# Setup the main layout.
119+
main_layout = QGridLayout(self)
120+
main_layout.addWidget(self.confpages_tabwidget)
121+
main_layout.addWidget(button_box)
122+
main_layout.setRowStretch(0, 1)
123+
if resizable is False:
124+
main_layout.setSizeConstraint(main_layout.SetFixedSize)
125+
126+
def count(self):
127+
"Return the number of configuration pages added to this dialog."
128+
return len(self._confpages)
129+
130+
def get_confpage(self, confpage_name):
131+
"""Return the confpage corresponding to the given name."""
132+
return self._confpages.get(confpage_name, None)
133+
134+
def add_confpage(self, confpage):
135+
"""Add confpage to this config dialog."""
136+
self._confpages[confpage.name()] = confpage
137+
self.confpages_tabwidget.addTab(
138+
confpage, confpage.icon(), confpage.label())
139+
confpage.sig_configs_changed.connect(
140+
self._handle_confpage_configs_changed)
141+
142+
@Slot(QAbstractButton)
143+
def _handle_button_click_event(self, button):
144+
"""
145+
Handle when a button is clicked on the dialog button box.
146+
"""
147+
if button == self.cancel_button:
148+
self.close()
149+
elif button == self.apply_button:
150+
for confpage in self._confpages.values():
151+
confpage.apply_changes()
152+
elif button == self.ok_button:
153+
for confpage in self._confpages.values():
154+
confpage.apply_changes()
155+
self.close()
156+
self.apply_button.setEnabled(False)
157+
158+
def closeEvent(self, event):
159+
"""
160+
Override this QT to revert confpage configs to the value saved in
161+
the user configuration files.
162+
"""
163+
for confpage in self._confpages.values():
164+
if confpage.is_modified():
165+
confpage.load_configs_from_conf()
166+
self.apply_button.setEnabled(False)
167+
super().closeEvent(event)
168+
169+
def _handle_confpage_configs_changed(self):
170+
"""
171+
Handle when the configs in one of the registered pages changed.
172+
"""
173+
for confpage in self._confpages.values():
174+
if confpage.is_modified():
175+
self.apply_button.setEnabled(True)
176+
break
177+
else:
178+
self.apply_button.setEnabled(False)
179+
180+
181+
class ConfPageBase(QWidget):
182+
"""
183+
Basic functionality for app configuration pages.
184+
185+
WARNING: Don't override any methods or attributes present here unless you
186+
know what you are doing.
187+
"""
188+
sig_configs_changed = Signal()
189+
190+
def __init__(self, name: str, label: str, icon: QIcon = None):
191+
super().__init__()
192+
self._name = name
193+
self._label = label
194+
if icon is None:
195+
empty_pixmap = QPixmap(20, 20)
196+
empty_pixmap.fill(Qt.transparent)
197+
icon = QIcon(empty_pixmap)
198+
self._icon = QIcon() if icon is None else icon
199+
200+
self.setup_page()
201+
self.load_configs_from_conf()
202+
203+
def name(self):
204+
"""
205+
Return the name that will be used to reference this confpage
206+
in the code.
207+
"""
208+
return self._name
209+
210+
def label(self):
211+
"""
212+
Return the label that will be used to reference this confpage in the
213+
graphical interface.
214+
"""
215+
return self._label
216+
217+
def icon(self):
218+
"""Return configuration page icon"""
219+
return self._icon
220+
221+
def is_modified(self):
222+
return self.get_configs() != self.get_configs_from_conf()
223+
224+
def apply_changes(self):
225+
"""Apply changes."""
226+
self.save_configs_to_conf()
227+
228+
229+
class ConfPage(ConfPageBase):
230+
"""
231+
App configuration page class.
232+
233+
All configuration page *must* inherit this class and
234+
reimplement its interface.
235+
"""
236+
237+
def __init__(self, name: str, label: str, icon: QIcon = None):
238+
"""
239+
Parameters
240+
----------
241+
name: str
242+
The name that is used to reference this confpage in the code.
243+
label: str
244+
The label that is used to reference this confpage in the
245+
graphical interface.
246+
icon: QIcon
247+
The icon that appears in the tab for that confpage
248+
in the tab bar of the configuration dialog
249+
"""
250+
super().__init__(name, label, icon)
251+
252+
def setup_page(self):
253+
"""Setup configuration page widget"""
254+
raise NotImplementedError
255+
256+
def get_configs(self):
257+
"""Return the configs that are set in this configuration page."""
258+
raise NotImplementedError
259+
260+
def get_configs_from_conf(self):
261+
"""Get configs from the user configuration files."""
262+
raise NotImplementedError
263+
264+
def load_configs_from_conf(self):
265+
"""Load configs from the user configuration files."""
266+
raise NotImplementedError
267+
268+
def save_configs_to_conf(self):
269+
"""Save configs to the user configuration files."""
270+
raise NotImplementedError
271+
272+
273+
if __name__ == '__main__':
274+
app = QApplication(sys.argv)
275+
sys.exit(app.exec_())

0 commit comments

Comments
 (0)