-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a preferences window #2167
Add a preferences window #2167
Changes from all commits
91100ca
fbc6d7c
cf092fc
662dfa2
8793244
99f9f00
99a2f7c
5dda1dc
8202914
03fa2cd
cf58e5c
a6dff71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.. preferences_help.rst | ||
|
||
.. J Krzywon wrote initial draft August 2022 | ||
|
||
.. _Preferences: | ||
|
||
Preferences | ||
============ | ||
|
||
SasView has a number of user-settable options available. Each heading will give more information about a | ||
particular group of settings. Any preferences not held within this window are planned to move here in a future release. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import logging | ||
|
||
from PyQt5.QtWidgets import QComboBox, QDialog, QPushButton, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QLineEdit, QCheckBox | ||
from typing import Optional, Any, List, Union, Callable | ||
|
||
from sas.system.config.config import config | ||
from sas.qtgui.Utilities.UI.PreferencesUI import Ui_preferencesUI | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def set_config_value(attr: str, value: Any): | ||
"""Helper method to set any config value, regardless if it exists or not | ||
:param attr: The configuration attribute that will be set | ||
:param value: The value the attribute will be set to. This could be a str, int, bool, a class instance, or any other | ||
""" | ||
setattr(config, attr, value) | ||
|
||
|
||
def get_config_value(attr: str, default: Optional[Any] = None) -> Any: | ||
"""Helper method to get any config value, regardless if it exists or not | ||
:param attr: The configuration attribute that will be returned | ||
:param default: The assumed value, if the attribute cannot be found | ||
""" | ||
return getattr(config, attr, default) if hasattr(config, attr) else default | ||
|
||
|
||
def cb_replace_all_items_with_new(cb: QComboBox, new_items: List[str], default_item: Optional[str] = None): | ||
"""Helper method that removes existing ComboBox values, replaces them and sets a default item, if defined | ||
:param cb: A QComboBox object | ||
:param new_items: A list of strings that will be used to populate the QComboBox | ||
:param default_item: The value to set the QComboBox to, if set | ||
""" | ||
cb.clear() | ||
cb.addItems(new_items) | ||
index = cb.findText(default_item) if default_item and default_item in new_items else 0 | ||
cb.setCurrentIndex(index) | ||
|
||
|
||
class PreferencesPanel(QDialog, Ui_preferencesUI): | ||
"""A preferences panel to house all SasView related settings. The left side of the window is a listWidget with a | ||
options menus available. The right side of the window is a stackedWidget object that houses the options | ||
associated with each listWidget item. | ||
**Important Note** When adding new preference widgets, the index for the listWidget and stackedWidget *must* match | ||
Release notes: | ||
SasView v5.0.5: Added defaults for loaded data units and plotted units | ||
""" | ||
|
||
def __init__(self, parent=None): | ||
super(PreferencesPanel, self).__init__(parent) | ||
self.setupUi(self) | ||
self.parent = parent | ||
self.setWindowTitle("Preferences") | ||
self.warning = None | ||
# A list of callables used to restore the default values for each item in StackedWidget | ||
self.restoreDefaultMethods = [] | ||
# Set defaults values for the list and stacked widgets | ||
self.stackedWidget.setCurrentIndex(0) | ||
self.listWidget.setCurrentRow(0) | ||
# Add window actions | ||
self.listWidget.currentItemChanged.connect(self.prefMenuChanged) | ||
self.buttonBox.clicked.connect(self.onClick) | ||
|
||
def prefMenuChanged(self): | ||
"""When the preferences menu selection changes, change to the appropriate preferences widget """ | ||
row = self.listWidget.currentRow() | ||
self.stackedWidget.setCurrentIndex(row) | ||
|
||
def onClick(self, btn: QPushButton): | ||
"""Handle button click events in one area""" | ||
# Reset to the default preferences | ||
if btn.text() == 'Restore Defaults': | ||
self.restoreDefaultPreferences() | ||
elif btn.text() == 'OK': | ||
self.close() | ||
elif btn.text() == 'Help': | ||
self.help() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's probably better to create separate slots for all the buttons rather than compare with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. This was a placeholder. |
||
|
||
def restoreDefaultPreferences(self): | ||
"""Reset all preferences to their default preferences""" | ||
for method in self.restoreDefaultMethods: | ||
if callable(method): | ||
method() | ||
else: | ||
logger.warning(f'While restoring defaults, {str(method)} of type {type(method)}' | ||
+ ' was given. A callable object was expected.') | ||
|
||
def close(self): | ||
"""Save the configuration values when the preferences window is closed""" | ||
config.save() | ||
super(PreferencesPanel, self).close() | ||
|
||
def addWidget(self, widget: QWidget): | ||
self.stackedWidget.addWidget(widget) | ||
self.listWidget.addItem(widget.name) | ||
if widget.resetDefaults is not None and callable(widget.resetDefaults): | ||
self.restoreDefaultMethods.append(widget.resetDefaults) | ||
|
||
def help(self): | ||
"""Open the help window associated with the preferences window""" | ||
tree_location = "/user/qtgui/MainWindow/preferences_help.html" | ||
self.parent.showHelp(tree_location) | ||
|
||
|
||
class PreferencesWidget(QWidget): | ||
"""A helper class that bundles all values needed to add a new widget to the preferences panel | ||
""" | ||
# Name that will be added to the PreferencesPanel listWidget | ||
name = None # type: str | ||
|
||
def __init__(self, name: str, default_method: Optional[Callable] = None): | ||
super(PreferencesWidget, self).__init__() | ||
self.name = name | ||
self.resetDefaults = default_method | ||
self.horizontalLayout = QHBoxLayout() | ||
self.setLayout(self.horizontalLayout) | ||
self.adjustSize() | ||
|
||
def _createLayoutAndTitle(self, title: str): | ||
"""A private class method that creates a vertical layout to hold the title and interactive item. | ||
:param title: The title of the interactive item to be added to the preferences panel. | ||
:return: A QVBoxLayout instance with a title box already added | ||
""" | ||
layout = QVBoxLayout(self) | ||
label = QLabel(title + ": ", self) | ||
layout.addWidget(label) | ||
return layout | ||
|
||
def addComboBox(self, title: str, params: List[Union[str, int, float]], callback: Callable, | ||
default: Optional[str] = None): | ||
"""Add a title and combo box within the widget. | ||
:param title: The title of the combo box to be added to the preferences panel. | ||
:param params: A list of options to be added to the combo box. | ||
:param callback: A callback method called when the combobox value is changed. | ||
:param default: The default option to be selected in the combo box. The first item is selected if None. | ||
""" | ||
layout = self._createLayoutAndTitle(title) | ||
box = QComboBox(self) | ||
cb_replace_all_items_with_new(box, params, default) | ||
box.currentIndexChanged.connect(callback) | ||
layout.addWidget(box) | ||
self.horizontalLayout.addLayout(layout) | ||
|
||
def addTextInput(self, title: str, callback: Callable, default_text: Optional[str] = ""): | ||
"""Add a title and text box within the widget. | ||
:param title: The title of the text box to be added to the preferences panel. | ||
:param callback: A callback method called when the combobox value is changed. | ||
:param default_text: An optional value to be put within the text box as a default. Defaults to an empty string. | ||
""" | ||
layout = self._createLayoutAndTitle(title) | ||
text_box = QLineEdit(self) | ||
if default_text: | ||
text_box.setText(default_text) | ||
text_box.textChanged.connect(callback) | ||
layout.addWidget(text_box) | ||
self.horizontalLayout.addLayout(layout) | ||
|
||
def addCheckBox(self, title: str, callback: Callable, checked: Optional[bool] = False): | ||
"""Add a title and check box within the widget. | ||
:param title: The title of the check box to be added to the preferences panel. | ||
:param callback: A callback method called when the combobox value is changed. | ||
:param checked: An optional boolean value to specify if the check box is checked. Defaults to unchecked. | ||
""" | ||
layout = self._createLayoutAndTitle(title) | ||
check_box = QCheckBox(self) | ||
check_box.setChecked(checked) | ||
check_box.toggled.connect(callback) | ||
layout.addWidget(check_box) | ||
self.horizontalLayout.addLayout(layout) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ui version="4.0"> | ||
<class>preferencesUI</class> | ||
<widget class="QDialog" name="preferencesUI"> | ||
<property name="geometry"> | ||
<rect> | ||
<x>0</x> | ||
<y>0</y> | ||
<width>731</width> | ||
<height>463</height> | ||
</rect> | ||
</property> | ||
<property name="sizePolicy"> | ||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> | ||
<horstretch>0</horstretch> | ||
<verstretch>0</verstretch> | ||
</sizepolicy> | ||
</property> | ||
<property name="windowTitle"> | ||
<string>Preferences</string> | ||
</property> | ||
<property name="windowIcon"> | ||
<iconset> | ||
<normaloff>:/res/ball.ico</normaloff>:/res/ball.ico</iconset> | ||
</property> | ||
<layout class="QGridLayout" name="gridLayout_2"> | ||
<item row="18" column="0" rowspan="2"> | ||
<layout class="QHBoxLayout" name="horizontalLayout_2"> | ||
<property name="sizeConstraint"> | ||
<enum>QLayout::SetNoConstraint</enum> | ||
</property> | ||
<item> | ||
<widget class="QListWidget" name="listWidget"> | ||
<property name="enabled"> | ||
<bool>true</bool> | ||
</property> | ||
<property name="sizePolicy"> | ||
<sizepolicy hsizetype="Maximum" vsizetype="Expanding"> | ||
<horstretch>0</horstretch> | ||
<verstretch>0</verstretch> | ||
</sizepolicy> | ||
</property> | ||
<property name="maximumSize"> | ||
<size> | ||
<width>256</width> | ||
<height>16777215</height> | ||
</size> | ||
</property> | ||
<property name="currentRow"> | ||
<number>0</number> | ||
</property> | ||
</widget> | ||
</item> | ||
<item> | ||
<layout class="QVBoxLayout" name="verticalLayout"> | ||
<item> | ||
<widget class="QStackedWidget" name="stackedWidget"> | ||
<property name="enabled"> | ||
<bool>true</bool> | ||
</property> | ||
<property name="layoutDirection"> | ||
<enum>Qt::LeftToRight</enum> | ||
</property> | ||
<property name="frameShape"> | ||
<enum>QFrame::StyledPanel</enum> | ||
</property> | ||
<property name="frameShadow"> | ||
<enum>QFrame::Sunken</enum> | ||
</property> | ||
<property name="lineWidth"> | ||
<number>2</number> | ||
</property> | ||
<property name="midLineWidth"> | ||
<number>1</number> | ||
</property> | ||
<property name="currentIndex"> | ||
<number>0</number> | ||
</property> | ||
</widget> | ||
</item> | ||
</layout> | ||
</item> | ||
</layout> | ||
</item> | ||
<item row="22" column="0"> | ||
<widget class="QDialogButtonBox" name="buttonBox"> | ||
<property name="orientation"> | ||
<enum>Qt::Horizontal</enum> | ||
</property> | ||
<property name="standardButtons"> | ||
<set>QDialogButtonBox::Help|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set> | ||
</property> | ||
</widget> | ||
</item> | ||
</layout> | ||
</widget> | ||
<resources/> | ||
<connections> | ||
<connection> | ||
<sender>buttonBox</sender> | ||
<signal>accepted()</signal> | ||
<receiver>preferencesUI</receiver> | ||
<slot>accept()</slot> | ||
<hints> | ||
<hint type="sourcelabel"> | ||
<x>248</x> | ||
<y>254</y> | ||
</hint> | ||
<hint type="destinationlabel"> | ||
<x>157</x> | ||
<y>274</y> | ||
</hint> | ||
</hints> | ||
</connection> | ||
<connection> | ||
<sender>buttonBox</sender> | ||
<signal>rejected()</signal> | ||
<receiver>preferencesUI</receiver> | ||
<slot>reject()</slot> | ||
<hints> | ||
<hint type="sourcelabel"> | ||
<x>316</x> | ||
<y>260</y> | ||
</hint> | ||
<hint type="destinationlabel"> | ||
<x>286</x> | ||
<y>274</y> | ||
</hint> | ||
</hints> | ||
</connection> | ||
</connections> | ||
</ui> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this necessary in the
__init__
method? Aren't current indices/rows set to 0 anyway on instantiation?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not necessary, but a good practice to ensure the two widgets are at the same point.