"""Large and simple widget to indicate settings/Settings.
"""
from logging import getLogger
from PyQt5.QtCore import QSettings, Qt
from PyQt5.QtWidgets import (QDialogButtonBox,
QDialog,
QFormLayout,
QGroupBox,
QHBoxLayout,
QListWidget,
QSplitter,
QStackedWidget,
QTextEdit,
QVBoxLayout,
QWidget,
)
from .utils import FormInt, FormList, FormStr
lg = getLogger(__name__)
settings = QSettings("wonambi", "scroll_data")
# DO NOT DUPLICATE NAMES
DEFAULTS = {}
DEFAULTS['channels'] = {'hp': .5,
'lp': 45,
'notch': 0,
'color': 'black',
'scale': 1,
'demean': True,
}
DEFAULTS['overview'] = {'timestamp_steps': 60 * 60,
'overview_scale': 30,
}
DEFAULTS['spectrum'] = {'x_min': 0.,
'x_max': 30.,
'x_tick': 10.,
'y_min': -5.,
'y_max': 5.,
'y_tick': 5.,
'log': True,
}
DEFAULTS['notes'] = {'marker_show': True,
'marker_color': 'darkBlue',
'annot_show': True,
'annot_bookmark_color': 'darkMagenta',
'min_marker_dur': .1,
'min_marker_display_dur': .1,
'scoring_window': 30,
}
DEFAULTS['traces'] = {'n_time_labels': 3,
'y_distance': 50,
'y_scale': 1,
'label_ratio': 0.05,
'max_s_freq': 30000,
'window_start': 0,
'window_length': 30,
'window_step': 5,
'grid_x': True,
'grid_xtick': 1, # in seconds
'grid_y': True,
'grid_ytick': 35,
}
DEFAULTS['settings'] = {'max_dataset_history': 20,
'y_distance_presets': [20., 30., 40., 50., 100., 200.],
'y_scale_presets': [.1, .2, .5, 1, 2, 5, 10],
'window_length_presets': [1., 5., 10., 20., 30., 60.],
'recording_dir': '/home/gio/recordings',
}
DEFAULTS['video'] = {}
# The type of the numbers in DEFAULT is assumed to be float, excluded those below here:
MUST_BE_INT = [
'n_time_labels',
'timestamp_steps',
'overview_scale',
'scoring_window',
'max_dataset_history',
'window_step',
]
[docs]class Settings(QDialog):
"""Window showing the Settings/settings.
Parameters
----------
parent : instance of QMainWindow
the main window
"""
def __init__(self, parent):
super().__init__(None, Qt.WindowSystemMenuHint | Qt.WindowTitleHint)
self.parent = parent
self.config = ConfigUtils(self.parent.refresh)
self.setWindowTitle('Settings')
self.create_settings()
[docs] def create_settings(self):
"""Create the widget, organized in two parts.
Notes
-----
When you add widgets in config, remember to update show_settings too
"""
bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply |
QDialogButtonBox.Cancel)
self.idx_ok = bbox.button(QDialogButtonBox.Ok)
self.idx_apply = bbox.button(QDialogButtonBox.Apply)
self.idx_cancel = bbox.button(QDialogButtonBox.Cancel)
bbox.clicked.connect(self.button_clicked)
page_list = QListWidget()
page_list.setSpacing(1)
page_list.currentRowChanged.connect(self.change_widget)
pages = ['General', 'Overview', 'Signals', 'Channels', 'Spectrum',
'Notes', 'Video']
for one_page in pages:
page_list.addItem(one_page)
self.stacked = QStackedWidget()
self.stacked.addWidget(self.config)
self.stacked.addWidget(self.parent.overview.config)
self.stacked.addWidget(self.parent.traces.config)
self.stacked.addWidget(self.parent.channels.config)
self.stacked.addWidget(self.parent.spectrum.config)
self.stacked.addWidget(self.parent.notes.config)
self.stacked.addWidget(self.parent.video.config)
hsplitter = QSplitter()
hsplitter.addWidget(page_list)
hsplitter.addWidget(self.stacked)
btnlayout = QHBoxLayout()
btnlayout.addStretch(1)
btnlayout.addWidget(bbox)
vlayout = QVBoxLayout()
vlayout.addWidget(hsplitter)
vlayout.addLayout(btnlayout)
self.setLayout(vlayout)
[docs]class Config(QWidget):
"""Base class for widgets used in the Settings.
Parameters
----------
widget : str
name of the widget
update_widget : function
function to run to update the main window with new values
Attributes
----------
modified : bool
if the preference widget has been changed
value : dict
dictionary with the actual current values
index : dict
dictionary with the instances of the small widgets
Notes
-----
You'll need to implement create_config with the QGroupBox and layouts
"""
def __init__(self, widget, update_widget):
super().__init__()
self.modified = False
self.widget = widget
value_names = list(DEFAULTS[widget].keys())
self.value = self.create_values(value_names)
self.index = self.create_indices(value_names)
self.create_config()
self.put_values()
self.update_widget = update_widget
[docs] def create_values(self, value_names):
"""Read original values from the settings or the defaults.
Parameters
----------
value_names : list of str
list of value names to read
Returns
-------
dict
dictionary with the value names as keys
"""
output = {}
for value_name in value_names:
output[value_name] = read_settings(self.widget, value_name)
return output
[docs] def create_indices(self, value_names):
"""Create empty indices as None. They'll be created by create_config.
"""
return dict(zip(value_names, [None] * len(value_names)))
[docs] def get_values(self):
"""Get values from the GUI and save them in preference file."""
for value_name, widget in self.index.items():
self.value[value_name] = widget.get_value(self.value[value_name])
setting_name = self.widget + '/' + value_name
settings.setValue(setting_name, self.value[value_name])
[docs] def put_values(self):
"""Put values to the GUI.
Notes
-----
In addition, when one small widget has been changed, it calls
set_modified, so that we know that the preference widget was modified.
"""
for value_name, widget in self.index.items():
widget.set_value(self.value[value_name])
widget.connect(self.set_modified)
[docs] def create_config(self):
"""Placeholder: it'll be replaced with actual layout."""
pass
[docs] def set_modified(self):
"""Simply mark that the preference widget was modified.
Notes
-----
You cannot use lambda because they don't accept assignments.
"""
self.modified = True
[docs]class ConfigUtils(Config):
def __init__(self, update_widget):
super().__init__('settings', update_widget)
[docs] def create_config(self):
box0 = QGroupBox('History')
self.index['max_dataset_history'] = FormInt()
self.index['recording_dir'] = FormStr()
form_layout = QFormLayout()
form_layout.addRow('Max History Size',
self.index['max_dataset_history'])
form_layout.addRow('Directory with recordings',
self.index['recording_dir'])
box0.setLayout(form_layout)
box1 = QGroupBox('Default values')
self.index['y_distance_presets'] = FormList()
self.index['y_scale_presets'] = FormList()
self.index['window_length_presets'] = FormList()
form_layout = QFormLayout()
form_layout.addRow('Signal scaling, presets',
self.index['y_scale_presets'])
form_layout.addRow('Distance between signals, presets',
self.index['y_distance_presets'])
form_layout.addRow('Window length, presets',
self.index['window_length_presets'])
box1.setLayout(form_layout)
main_layout = QVBoxLayout()
main_layout.addWidget(box0)
main_layout.addWidget(box1)
main_layout.addStretch(1)
self.setLayout(main_layout)
[docs]def read_settings(widget, value_name):
"""Read Settings information, either from INI or from default values.
Parameters
----------
widget : str
name of the widget
value_name : str
name of the value of interest.
Returns
-------
multiple types
type depends on the type in the default values. We make it more flexible
so that if the default type is int, it accepts float
"""
setting_name = widget + '/' + value_name
default_value = DEFAULTS[widget][value_name]
default_type = type(default_value)
if default_type is list:
default_type = type(default_value[0])
if value_name not in MUST_BE_INT and default_type is int:
default_type = float
val = settings.value(setting_name, default_value, type=default_type)
return val
[docs]class HelpDialog(QDialog):
"""Generic help dialog, showing uneditable HTML text."""
def __init__(self):
super().__init__(None, Qt.WindowSystemMenuHint | Qt.WindowTitleHint)
self.setWindowTitle('Help')
self.setWindowModality(Qt.ApplicationModal)
self.create_dialog()
[docs] def create_dialog(self):
bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.idx_ok = bbox.button(QDialogButtonBox.Ok)
bbox.clicked.connect(self.accept)
textbox = QTextEdit(self)
textbox.setReadOnly(True)
self.textbox = textbox
btnlayout = QHBoxLayout()
btnlayout.addStretch(1)
btnlayout.addWidget(bbox)
vlayout = QVBoxLayout()
vlayout.addWidget(textbox)
vlayout.addLayout(btnlayout)
self.setLayout(vlayout)
[docs] def set_text(self, message):
"""Insert HTML text."""
self.textbox.insertHtml(message)