"""Define a window to manage offline configurations."""
# from pymysql.err import IntegrityError, InternalError, OperationalError
from qtpy.QtCore import Qt, QPoint, Slot
from qtpy.QtWidgets import QVBoxLayout, QPushButton, \
QTableView, QWidget, QHBoxLayout, QInputDialog, QMenu, QAction, \
QMessageBox
from qtpy.QtGui import QKeySequence
from siriushla.widgets import SiriusMainWindow
from siriushla.widgets import LoadingDialog
from .ConfigModel import ConfigModel, ConfigDelegate
from .TuneDlg import TuneDlg
from .LoadingThread import LoadingThread
[docs]
class ConfigManagerWindow(SiriusMainWindow):
"""Window to manage offline configuration of BO and SI devices.
This window allows the user to create new configurations as well as
interpolate or apply a tune or chromaticity delta to a configuration.
"""
NEW_CONFIGURATION = 0
def __init__(self, config_type, parent=None):
"""Init UI."""
super(ConfigManagerWindow, self).__init__(parent)
self._config_type = config_type
self._model = ConfigModel(self._config_type)
self._delegate = ConfigDelegate()
self._setup_ui()
self.ld_cur_state_btn.clicked.connect(self._loadCurrentConfiguration)
self.ld_config_btn.clicked.connect(self._addConfiguration)
self.delete_config_btn.clicked.connect(self._removeConfiguration)
self.setGeometry(100, 100, 1600, 900)
self.setWindowTitle("Configuration Manager")
self.show()
def _setup_ui(self):
self.central_widget = QWidget()
self.central_widget.layout = QHBoxLayout()
self.button_box = QVBoxLayout()
self.ld_cur_state_btn = QPushButton("Load Current State")
self.ld_config_btn = QPushButton("Load Configuration")
self.ld_config_btn.setShortcut(QKeySequence.New)
self.delete_config_btn = QPushButton("Delete Configuration")
self.button_box.addWidget(self.delete_config_btn)
self.button_box.addWidget(self.ld_config_btn)
self.button_box.addWidget(self.ld_cur_state_btn)
self.button_box.addStretch()
# TableView
self.table = QTableView(self)
self.table.setModel(self._model)
self.table.setItemDelegate(self._delegate)
# self.table.setSelectionBehavior(QAbstractItemView.SelectColumns)
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.table.customContextMenuRequested.connect(self._showHeaderMenu)
self.table.resizeColumnsToContents()
self.table.resizeRowsToContents()
# TableView Headers
self.headers = self.table.horizontalHeader()
self.headers.setContextMenuPolicy(Qt.CustomContextMenu)
self.headers.customContextMenuRequested.connect(self._showHeaderMenu)
self.central_widget.layout.addLayout(self.button_box)
self.central_widget.layout.addWidget(self.table)
self.central_widget.setLayout(self.central_widget.layout)
# Set widget
self.setCentralWidget(self.central_widget)
[docs]
def closeEvent(self, event):
"""Close window.
The user is warned if there are any unsaved changes.
"""
columns = list(range(len(self._model.configurations)))
columns.sort(reverse=True)
if not self._closeConfigurations(columns):
event.ignore()
[docs]
def keyPressEvent(self, event):
"""Override keyPressEvent.
Ctrl+S - Save changes
Ctrl+W - Close configuration on focus
F2 - Rename configuration on focus
Ctrl+Z - Undo
Ctrl+R - Redo
"""
if event.key() == Qt.Key_S:
self._saveChanges()
return
if event.key() == Qt.Key_W:
self._closeConfigurationOnFocus()
return
if event.key() == Qt.Key_F2:
self._renameOnFocus()
return
if event.key() == Qt.Key_Z:
print(self._model._undo)
if len(self._model._undo) > 0:
self._model._undo.pop()[1]()
return
if event.key() == Qt.Key_R:
if len(self._model._redo) > 0:
self._model._redo.pop()[1]()
return
@Slot(QPoint)
def _showHeaderMenu(self, point):
column = self.headers.logicalIndexAt(point.x())
if column == -1:
return
menu = QMenu(self)
# Actions
cols = self.table.selectionModel().selectedColumns()
if len(cols) != 2 or column not in [col.column() for col in cols]:
self.table.selectColumn(column)
menu.aboutToHide.connect(lambda: self.table.clearSelection())
save = QAction("Save", menu)
save.triggered.connect(lambda: self._saveConfiguration(column))
save_all = QAction("Save all", menu)
save_all.triggered.connect(lambda: self._saveChanges())
save_all.setShortcut(QKeySequence.Save)
rename = QAction("Rename", menu)
rename.triggered.connect(lambda: self._renameConfiguration(column))
close = QAction("Close", menu)
close.triggered.connect(lambda: self._closeConfiguration(column))
close.setShortcut(QKeySequence.Close)
close_right = QAction("Close to the right", menu)
close_right.triggered.connect(
lambda: self._closeConfigurationsToTheRight(column))
close_others = QAction("Close other", menu)
close_others.triggered.connect(
lambda: self._closeOtherConfigurations(column))
close_all = QAction("Close all", menu)
close_all.triggered.connect(lambda: self._closeAllConfigurations())
tune = QAction("Tune", menu)
tune.triggered.connect(lambda: self._tuneConfiguration(column))
menu.addActions([save, save_all])
menu.addSeparator()
menu.addActions([rename])
menu.addSeparator()
menu.addActions([close, close_right, close_others, close_all])
menu.addSeparator()
menu.addActions([tune])
else:
bar = QAction("Interpolate", menu)
bar.triggered.connect(lambda: self._barConfiguration(cols))
menu.addAction(bar)
vheader_offset = self.table.verticalHeader().width()
point.setX(point.x() + vheader_offset)
menu.popup(self.mapToGlobal(point))
# ContextMenu Actions
@Slot(int)
def _saveConfiguration(self, column):
try:
self._model.saveConfiguration(column)
return True
except Exception as e:
QMessageBox(QMessageBox.Warning, "Failed to save data",
"{}, {}".format(e, type(e))).exec_()
return False
@Slot(int)
def _renameConfiguration(self, column):
new_name, ok = QInputDialog.getText(self, "New name", "Rename to:")
if ok and new_name:
return self._model.renameConfiguration(column, new_name)
@Slot(int)
def _closeConfiguration(self, column):
self._closeConfigurations([column])
@Slot(int)
def _closeConfigurationsToTheRight(self, column):
columns = list()
i = len(self._model.configurations) - 1
while i > column:
columns.append(i)
i -= 1
self._closeConfigurations(columns)
@Slot(int)
def _closeOtherConfigurations(self, column):
columns = list()
i = len(self._model.configurations) - 1
while i >= 0:
if i == column:
i -= 1
continue
columns.append(i)
i -= 1
self._closeConfigurations(columns)
@Slot()
def _closeAllConfigurations(self):
columns = list()
i = len(self._model.configurations) - 1
while i >= 0:
columns.append(i)
i -= 1
self._closeConfigurations(columns)
def _closeConfigurations(self, columns):
save = self._maybeSaveChanges(columns)
if save == QMessageBox.Discard:
for column in columns:
self._model.cleanUndo(column)
self._model.closeConfiguration(column)
return True
elif save == QMessageBox.Save:
for column in columns:
if self._saveConfiguration(column):
self._model.cleanUndo(column)
self._model.closeConfiguration(column)
else:
return False
return True
else:
return False
@Slot(int)
def _tuneConfiguration(self, column):
dlg = TuneDlg(self)
ok1 = dlg.exec_()
if ok1:
# Get Matrix Calculate deltaK and show to user
tune = [dlg.tune_x.value(), dlg.tune_y.value()]
try:
inv_matrix = self._model.getTuneMatrix()
except Exception as e:
self._showWarningBox("{}".format(e),
"Failed to retrieve tune matrix")
else:
delta_f = tune[0]*inv_matrix[0][0] + tune[1]*inv_matrix[0][1]
delta_d = tune[0]*inv_matrix[1][0] + tune[1]*inv_matrix[1][1]
# config_name, ok2 = QInputDialog.getText(
# self, "Select value", "New Configuration Name:")
# if ok2:
# if not config_name:
proceed = QMessageBox(
QMessageBox.Question, "Delta K",
("\u0394K<sub>d</sub> = {:1.3f}<br>"
"\u0394K<sub>f</sub> = {:1.3f}<br>"
"Proceed?").format(delta_d, delta_f),
QMessageBox.Ok | QMessageBox.Cancel, self).exec_()
if proceed == QMessageBox.Ok:
config_name = self._getNextName()
self._model.deriveConfiguration(config_name, column,
ConfigModel.TUNE,
[delta_d, delta_f])
@Slot(int)
def _barConfiguration(self, cols):
if len(cols) != 2:
raise SystemError("Must interpolate 2 columns")
new_name, ok = QInputDialog.getText(self, "New name", "Rename to:")
if ok:
if not new_name:
new_name = self._getNextName()
self._model.interpolateConfiguration(
new_name, cols[0].column(), cols[1].column())
# Window menu slots
@Slot()
def _addConfiguration(self):
try:
configs = self._model.getConfigurations()
except Exception as e:
self._showWarningBox(e, "Failed to retrieve configurations")
else:
if configs:
options = [item["name"] for item in configs]
config_name, ok = QInputDialog.getItem(
self, "Available Configurations",
"Select a configuration:", options, 0, False)
if ok and config_name:
if not self._isConfigurationLoaded(config_name):
self._model.loadConfiguration(name=config_name)
else:
QMessageBox(
QMessageBox.Information,
"Configuration already loaded",
"Configuration is already loaded."
).exec_()
# Highlight new column; or the one that is already loaded
col = self._model.getConfigurationColumn(config_name)
self.table.selectColumn(col)
else:
self._showMessageBox("No configuration found")
return
@Slot()
def _removeConfiguration(self):
try:
configs = self._model.getConfigurations()
except Exception as e:
self._showWarningBox(e, "Failed to retrieve configurations")
else:
if configs:
# Show configs available
options = [item["name"] for item in configs]
config, ok = QInputDialog.getItem(
self, "Available Configurations",
"Select a configuration:", options, 0, False)
if ok and config:
# Ask for confirmation
if self._isConfigurationLoaded(config):
msg = ("Configuration is currenty loaded."
"Delete it anyway?")
else:
msg = ("This will permanently delete configuration {}."
"Proceed?").format(config)
if self._showDialogBox(msg) == QMessageBox.Cancel:
return
# Delete configuration
config = configs[options.index(config)]
try:
self._model.deleteConfiguration(config)
except Exception as e:
self._showWarningBox(e)
else:
self._showMessageBox(
"Configuration {} was deleted.".format(
config['name']))
else:
self._showMessageBox("No configuration found")
return
@Slot()
def _loadCurrentConfiguration(self):
try:
t = LoadingThread(
self._getNextName(), self._model._vertical_header, self)
dlg = \
LoadingDialog("Loading", len(self._model._vertical_header), self)
t.taskUpdated.connect(dlg.update)
t.taskFinished.connect(dlg.done)
t.start()
dlg.exec_()
except Exception as e:
self._showWarningBox("{}".format(e))
# Actions binded with keys
def _saveChanges(self):
for column in range(len(self._model.configurations)):
self._saveConfiguration(column)
def _closeConfigurationOnFocus(self):
cols = self.table.selectionModel().selectedColumns()
columns = list()
for col in cols:
columns.append(col.column())
columns.sort(reverse=True)
self._closeConfigurations(columns)
def _renameOnFocus(self):
cols = self.table.selectionModel().selectedColumns()
if len(cols) == 1:
self._renameConfiguration(cols[0].column())
# Helpers
def _isConfigurationLoaded(self, config_name):
ret = self._model.getConfigurationColumn(config_name)
if ret == -1:
return False
return True
def _getNextName(self):
# Treat if there already exist saved configuration with this name
configs = self._model.getConfigurations(deleted=None)
configs = [item["name"] for item in configs]
configs.extend([item.name for item in self._model.configurations])
new_name = 'config-{}'.format(self.NEW_CONFIGURATION)
while new_name in configs:
self.NEW_CONFIGURATION += 1
new_name = 'config-{}'.format(self.NEW_CONFIGURATION)
return new_name
def _maybeSaveChanges(self, columns):
ask_to_save = False
for column in columns:
if self._model.configurations[column].dirty:
ask_to_save = True
break
# If nothing to save, will close all columns
if not ask_to_save:
return QMessageBox.Discard
# Ask if user wants to save changes
msg_box = QMessageBox(
QMessageBox.Question,
"There are unsaved changes",
"Keep changes?",
QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard,
self)
return msg_box.exec_()
def _showWarningBox(self, message, title="Warning"):
QMessageBox(QMessageBox.Warning, title, '{}'.format(message)).exec_()
def _showMessageBox(self, message, title="Message"):
return QMessageBox(QMessageBox.Information, title, message).exec_()
def _showDialogBox(self, message, title="Dialog"):
return QMessageBox(
QMessageBox.Information, title, message,
QMessageBox.Ok | QMessageBox.Cancel).exec_()