Newer
Older
# Mantid Repository : https://github.com/mantidproject/mantid
# Copyright © 2019 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source
# & Institut Laue - Langevin
# SPDX - License - Identifier: GPL - 3.0 +
# This file is part of the mantidqt package
from __future__ import (absolute_import, unicode_literals)
from qtpy.QtCore import Qt
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QDialogButtonBox
from mantid.api import MatrixWorkspace
from mantidqt.icons import get_icon
from mantidqt.utils.qt import load_ui
# Constants
RANGE_SPECIFIER = '-'
PLACEHOLDER_FORMAT = 'valid range: {}' + RANGE_SPECIFIER + '{}'
RED_ASTERISK = None
def red_asterisk():
global RED_ASTERISK
if RED_ASTERISK is None:
RED_ASTERISK = get_icon('mdi.asterisk', 'red', 0.6)
SpectraSelectionDialogUI, SpectraSelectionDialogUIBase = load_ui(__file__, 'spectraselectordialog.ui')
class SpectraSelection(object):
def __init__(self, workspaces):
self.workspaces = workspaces
self.wksp_indices = None
self.spectra = None
self.plot_type = SpectraSelection.Individual
class SpectraSelectionDialog(SpectraSelectionDialogUIBase):
@staticmethod
def raise_error_if_workspaces_not_compatible(workspaces):
for ws in workspaces:
if not isinstance(ws, MatrixWorkspace):
raise ValueError("Expected MatrixWorkspace, found {}.".format(ws.__class__.__name__))
def __init__(self, workspaces, parent=None, show_colorfill_btn=False, overplot=False):
super(SpectraSelectionDialog, self).__init__(parent)
self.icon = self.setWindowIcon(QIcon(':/images/MantidIcon.ico'))
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.raise_error_if_workspaces_not_compatible(workspaces)
# attributes
self._workspaces = workspaces
self.spec_min, self.spec_max = None, None
self.wi_min, self.wi_max = None, None
self.selection = None
self._show_colorfill_button = show_colorfill_btn
self._overplot = overplot
self._set_placeholder_text()
self._setup_connections()
def on_ok_clicked(self):
self.accept()
def on_plot_all_clicked(self):
selection = SpectraSelection(self._workspaces)
selection.wksp_indices = range(self.wi_min, self.wi_max + 1)
selection.plot_type = selection.Individual if self._ui.plotType.currentText() == 'Individual' else selection.Tiled
self.selection = selection
def on_colorfill_clicked(self):
self.selection = 'colorfill'
self.accept()
# ------------------- Private -------------------------
ui = SpectraSelectionDialogUI()
ui.setupUi(self)
self._ui = ui
ui.colorfillButton.setVisible(self._show_colorfill_button)
# overwrite the "Yes to All" button text
ui.buttonBox.button(QDialogButtonBox.YesToAll).setText('Plot All')
# ok disabled by default
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
# validity markers
ui.wkspIndicesValid.setIcon(red_asterisk())
ui.specNumsValid.setIcon(red_asterisk())
def _set_placeholder_text(self):
"""Sets placeholder text to indicate the ranges possible"""
workspaces = self._workspaces
# workspace index range
wi_max = min([ws.getNumberHistograms() - 1 for ws in workspaces])
self._ui.wkspIndices.setPlaceholderText(PLACEHOLDER_FORMAT.format(0, wi_max))
self.wi_min, self.wi_max = 0, wi_max
# spectra range
ws_spectra = [{ws.getSpectrum(i).getSpectrumNo() for i in range(ws.getNumberHistograms())} for ws in workspaces]
plottable = ws_spectra[0]
if len(ws_spectra) > 1:
for sp_set in ws_spectra[1:]:
plottable = plottable.intersection(sp_set)
plottable = sorted(plottable)
spec_min, spec_max = min(plottable), max(plottable)
self._ui.specNums.setPlaceholderText(PLACEHOLDER_FORMAT.format(spec_min, spec_max))
self.spec_min, self.spec_max = spec_min, spec_max
def _setup_connections(self):
ui = self._ui
ui.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.on_ok_clicked)
ui.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(self.reject)
ui.buttonBox.button(QDialogButtonBox.YesToAll).clicked.connect(self.on_plot_all_clicked)
ui.colorfillButton.clicked.connect(self.on_colorfill_clicked)
# line edits are mutually exclusive
ui.wkspIndices.textChanged.connect(self._on_wkspindices_changed)
ui.specNums.textChanged.connect(self._on_specnums_changed)
# combobox changed
ui.plotType.currentIndexChanged.connect(self._on_plot_type_changed)
def _on_wkspindices_changed(self):
ui.specNums.clear()
ui.specNumsValid.hide()
self._parse_wksp_indices()
ui.wkspIndicesValid.setVisible(not self._is_input_valid())
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(self._is_input_valid())
def _on_specnums_changed(self):
ui = self._ui
ui.wkspIndices.clear()
ui.wkspIndicesValid.hide()
self._parse_spec_nums()
ui.specNumsValid.setVisible(not self._is_input_valid())
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(self._is_input_valid())
def _on_plot_type_changed(self, new_index):
new_text = self._ui.plotType.currentText()
if self._overplot:
self._ui.plotType.setCurrentIndex(0)
return
if self.selection:
self.selection.plot_type = self.selection.Individual if new_text == 'Individual' else self.selection.Tiled
def _parse_wksp_indices(self):
wksp_indices = parse_selection_str(self._ui.wkspIndices.text(), self.wi_min, self.wi_max)
if wksp_indices:
selection = SpectraSelection(self._workspaces)
selection.wksp_indices = wksp_indices
selection.plot_type = selection.Individual if self._ui.plotType.currentText() == 'Individual' else selection.Tiled
else:
selection = None
self.selection = selection
def _parse_spec_nums(self):
spec_nums = parse_selection_str(self._ui.specNums.text(), self.spec_min, self.spec_max)
if spec_nums:
selection = SpectraSelection(self._workspaces)
selection.spectra = spec_nums
selection.plot_type = selection.Individual if self._ui.plotType.currentText() == 'Individual' else selection.Tiled
else:
selection = None
self.selection = selection
def _is_input_valid(self):
return self.selection is not None
def parse_selection_str(txt, min_val, max_val):
"""Parse an input string containing plot index selection.
:param txt: A single line of text containing a comma-separated list of values or range of values, i.e.
3-4,5,6,8,10-11
:param min_val: The minimum allowed value
:param max_val: The maximum allowed value
:returns A list containing each value in the range or None if the string is invalid
"""
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def append_if_valid(out, val):
try:
val = int(val)
if is_in_range(val):
out.add(val)
else:
return False
except ValueError:
return False
return True
def is_in_range(val):
return min_val <= val <= max_val
# split up any commas
comma_separated = txt.split(',')
# find and expand ranges
parsed_numbers = set()
valid = True
for cs_item in comma_separated:
post_split = cs_item.split('-')
if len(post_split) == 1:
valid = append_if_valid(parsed_numbers, post_split[0])
elif len(post_split) == 2:
# parse as range
try:
beg, end = int(post_split[0]), int(post_split[1])
except ValueError:
valid = False
else:
if is_in_range(beg) and is_in_range(end):
parsed_numbers = parsed_numbers.union(set(range(beg, end + 1)))
else:
valid = False
else:
valid = False
if not valid:
break
return list(parsed_numbers) if valid > 0 else None