def __init__(self, *args, **kwargs): super(HelpExplorer, self).__init__(*args, **kwargs) self.vbox = vbox = QVBoxLayout() self.combo = QComboBox(parent=self) vbox.addWidget(self.combo) if _viewers: self.viewers = _viewers.copy() for w in self.viewers.values(): w.setParent(self) else: self.viewers = OrderedDict( [(key, cls(parent=self)) for key, cls in six.iteritems( self.viewers)]) # save the UrlHelp because QWebEngineView creates child processes # that are not properly closed by PyQt and as such use too much # memory if is_running_tests(): for key, val in self.viewers.items(): _viewers[key] = val for key, ini in six.iteritems(self.viewers): self.combo.addItem(key) ini.hide() vbox.addWidget(ini) self.viewer = next(six.itervalues(self.viewers)) self.viewer.show() self.combo.currentIndexChanged[str].connect(self.set_viewer) self.setLayout(vbox)
def edit_attrs(self): """Edit the attributes of the current straditizer This creates a new dataframe editor to edit the :attr:`straditize.straditizer.Straditizer.attrs` meta informations""" def add_attr(key): model = editor.table.model() n = len(attrs) model.insertRow(n) model.setData(model.index(n, 0), key) model.setData(model.index(n, 1), '', change_type=six.text_type) from psyplot_gui.main import mainwindow from straditize.straditizer import common_attributes attrs = self.straditizer.attrs editor = mainwindow.new_data_frame_editor(attrs, 'Straditizer attributes') editor.table.resizeColumnToContents(1) editor.table.horizontalHeader().setVisible(False) editor.table.frozen_table_view.horizontalHeader().setVisible(False) combo = QComboBox() combo.addItems([''] + common_attributes) combo.currentTextChanged.connect(add_attr) hbox = QHBoxLayout() hbox.addWidget(QLabel('Common attributes:')) hbox.addWidget(combo) hbox.addStretch(0) editor.layout().insertLayout(1, hbox) return editor, combo
def __init__(self, attrs, fmt_widget, modulo_style=True): QComboBox.__init__(self) self.fmt_widget = fmt_widget self.addItems([''] + [(key + ': ' + str(val))[:40] for key, val in attrs.items()]) func = self.insert_modulo if modulo_style else self.insert_bracketed self.currentTextChanged.connect(func)
class DataTicksCalculatorFmtWidget(QWidget): """Fmt widget for :class:`psy_simple.plotters.DataTicksCalculator` This widget contains a combo box with the different options from the :attr:`psy_simple.plotters.DataTicksCalculator.calc_funcs`, a spin box for the number of increments and two text widgets for minimum and maximum percentile""" def __init__(self, parent, fmto, what=None, N=None, pctl_min=None, pctl_max=None): QWidget.__init__(self, parent) hbox = QHBoxLayout() self.combo = QComboBox() self.combo.addItems(sorted(fmto.calc_funcs)) hbox.addWidget(self.combo) self.sb_N = QSpinBox() hbox.addWidget(self.sb_N) self.txt_min_pctl = QLineEdit() self.txt_min_pctl.setValidator(QDoubleValidator(0., 100., 10)) hbox.addWidget(QLabel('Percentiles:')) hbox.addWidget(QLabel('Min.:')) hbox.addWidget(self.txt_min_pctl) self.txt_max_pctl = QLineEdit() self.txt_max_pctl.setValidator(QDoubleValidator(0., 100., 10)) hbox.addWidget(QLabel('Max.:')) hbox.addWidget(self.txt_max_pctl) if what is not None: self.combo.setCurrentText(what) if N is not None: self.sb_N.setValue(N) if pctl_min is not None: self.txt_min_pctl.setText('%1.6g' % pctl_min) if pctl_max is not None: self.txt_max_pctl.setText('%1.6g' % pctl_max) self.combo.currentTextChanged.connect(self.set_obj) self.sb_N.valueChanged.connect(self.set_obj) self.txt_min_pctl.textChanged.connect(self.set_obj) self.txt_max_pctl.textChanged.connect(self.set_obj) self.setLayout(hbox) def set_obj(self): obj = [self.combo.currentText(), self.sb_N.value()] if (self.txt_min_pctl.text().strip() or self.txt_max_pctl.text().strip()): obj.append(float(self.txt_min_pctl.text().strip() or 0)) if self.txt_max_pctl.text().strip(): obj.append(float(self.txt_max_pctl.text().strip())) self.parent().set_obj(obj)
def __init__(self, *args, **kwargs): super(HelpExplorer, self).__init__(*args, **kwargs) self.vbox = vbox = QVBoxLayout() self.combo = QComboBox(parent=self) vbox.addWidget(self.combo) self.viewers = OrderedDict([ (key, cls(parent=self)) for key, cls in six.iteritems(self.viewers) ]) for key, ini in six.iteritems(self.viewers): self.combo.addItem(key) ini.hide() vbox.addWidget(ini) self.viewer = next(six.itervalues(self.viewers)) self.viewer.show() self.combo.currentIndexChanged[str].connect(self.set_viewer) self.setLayout(vbox)
def __init__(self, parent, fmto, what=None, N=None, pctl_min=None, pctl_max=None): QWidget.__init__(self, parent) hbox = QHBoxLayout() self.combo = QComboBox() self.combo.addItems(sorted(fmto.calc_funcs)) hbox.addWidget(self.combo) self.sb_N = QSpinBox() hbox.addWidget(self.sb_N) self.txt_min_pctl = QLineEdit() self.txt_min_pctl.setValidator(QDoubleValidator(0., 100., 10)) hbox.addWidget(QLabel('Percentiles:')) hbox.addWidget(QLabel('Min.:')) hbox.addWidget(self.txt_min_pctl) self.txt_max_pctl = QLineEdit() self.txt_max_pctl.setValidator(QDoubleValidator(0., 100., 10)) hbox.addWidget(QLabel('Max.:')) hbox.addWidget(self.txt_max_pctl) if what is not None: self.combo.setCurrentText(what) if N is not None: self.sb_N.setValue(N) if pctl_min is not None: self.txt_min_pctl.setText('%1.6g' % pctl_min) if pctl_max is not None: self.txt_max_pctl.setText('%1.6g' % pctl_max) self.combo.currentTextChanged.connect(self.set_obj) self.sb_N.valueChanged.connect(self.set_obj) self.txt_min_pctl.textChanged.connect(self.set_obj) self.txt_max_pctl.textChanged.connect(self.set_obj) self.setLayout(hbox)
def __init__(self, parent, fmto, artist=None, base=None): """ Parameters ---------- %(LabelWidget.parameters.parent|fmto)s artist: matplotlib.text.Text The text instance this formatoption is modifying base: psyplot.plotter.Formatoption The original formatoption of the label the given `fmto` belongs to """ QWidget.__init__(self, parent) hbox = QHBoxLayout() hbox.addWidget(QLabel('Font weights:')) if artist is None: weight = 'normal' else: weight = artist.get_weight() self.spin_box = spin_box = QSpinBox(self) spin_box.setRange(1, 1000) try: weight = int(weight) except ValueError: spin_box.setValue(mpl_weight2qt(weight) * 10) else: spin_box.setValue(weight) spin_box.valueChanged.connect(parent.set_obj) hbox.addWidget(spin_box) combo = QComboBox() combo.addItems(list(weights_mpl2qt)) if weight in weights_mpl2qt: combo.setCurrentText(weight) else: combo.setCurrentText(weights_qt2mpl[mpl_weight2qt(weight)]) combo.currentTextChanged.connect(parent.set_obj) hbox.addWidget(combo) # add a button to change to the properties formatoption if base is not None: fmtos = [ base, getattr(fmto.plotter, base.key + 'props', None), getattr(fmto.plotter, base.key + 'size', None), ] fmtos = list(filter(None, fmtos)) hbox.addWidget(Switch2FmtButton(parent, *fmtos)) self.setLayout(hbox)
def __init__(self, parent, fmto, artist=None, base=None): """ Parameters ---------- %(FontWeightWidget.parameters)s """ QWidget.__init__(self, parent) hbox = QHBoxLayout() hbox.addWidget(QLabel('Font sizes:')) self.spin_box = spin_box = QSpinBox(self) spin_box.setRange(1, 1e9) if artist is not None: spin_box.setValue(int(artist.get_size())) spin_box.valueChanged.connect(parent.set_obj) hbox.addWidget(spin_box) combo = QComboBox() combo.addItems([ 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' ]) combo.currentTextChanged.connect(parent.set_obj) hbox.addWidget(combo) # add a button to change to the properties formatoption if base is not None: fmtos = [ base, getattr(fmto.plotter, base.key + 'props', None), getattr(fmto.plotter, base.key + 'weight', None), ] fmtos = list(filter(None, fmtos)) hbox.addWidget(Switch2FmtButton(parent, *fmtos)) self.setLayout(hbox)
def __init__(self, *args, **kwargs): super(HelpExplorer, self).__init__(*args, **kwargs) self.vbox = vbox = QVBoxLayout() self.combo = QComboBox(parent=self) vbox.addWidget(self.combo) self.viewers = OrderedDict( [(key, cls(parent=self)) for key, cls in six.iteritems( self.viewers)]) for key, ini in six.iteritems(self.viewers): self.combo.addItem(key) ini.hide() vbox.addWidget(ini) self.viewer = next(six.itervalues(self.viewers)) self.viewer.show() self.combo.currentIndexChanged[str].connect(self.set_viewer) self.setLayout(vbox)
def __init__(self, parent, array=None): QWidget.__init__(self, parent) self.txt_min = QLineEdit() self.txt_min.setValidator(QDoubleValidator()) self.txt_max = QLineEdit() self.txt_max.setValidator(QDoubleValidator()) self.txt_step = QLineEdit() self.txt_step.setValidator(QDoubleValidator(1e-10, 1e10, 10)) self.sb_nsteps = QSpinBox() self.step_inc_combo = combo = QComboBox() combo.addItems(['Step', '# Steps']) if array is not None: self.txt_min.setText('%1.4g' % array.min()) self.txt_max.setText('%1.4g' % array.max()) steps = np.diff(array) if len(steps) == 1 or np.diff(steps).max() < 1e-5: self.txt_step.setText('%1.4g' % steps[0]) combo.setCurrentIndex(0) else: combo.setCurrentIndex(1) self.sb_nsteps.setValue(len(array)) self.toggle_txt_step(combo.currentText()) hbox = QHBoxLayout() hbox.addWidget(QLabel('Min.')) hbox.addWidget(self.txt_min) hbox.addWidget(QLabel('Max.')) hbox.addWidget(self.txt_max) hbox.addWidget(combo) hbox.addWidget(self.txt_step) hbox.addWidget(self.sb_nsteps) self.setLayout(hbox) for w in [self.txt_min, self.txt_max, self.txt_step]: w.textChanged.connect(self.set_array) self.sb_nsteps.valueChanged.connect(self.set_array) combo.currentTextChanged.connect(self.toggle_txt_step)
def __init__(self, parent, fmto, project): QWidget.__init__(self, parent) hbox = QHBoxLayout() self.combo = combo = QComboBox(self) combo.addItems(['Auto discrete', 'No normalization', 'Discrete', 'Logarithmic', 'Symmetric logarithmic', 'Power-law', 'Custom']) hbox.addWidget(combo) value = fmto.value if value is None: combo.setCurrentText('No normalization') value = mcol.Normalize() elif isinstance(value, mcol.Normalize): if isinstance(value, mcol.LogNorm): combo.setCurrentText('Logarithmic') elif isinstance(value, mcol.SymLogNorm): combo.setCurrentText('Symmetric logarithmic') elif isinstance(value, mcol.PowerNorm): combo.setCurrentText('Power-law') else: combo.setCurrentText('Custom') elif isinstance(value[0], six.string_types): combo.setCurrentText('Auto discrete') else: combo.setCurrentText('Discrete') combo.currentTextChanged.connect(self.toggle_combo) # add a button to select other formatoptions hbox.addWidget(Switch2FmtButton(parent, fmto.cmap, fmto.cbar)) self.setLayout(hbox) self.toggle_combo(combo.currentText()) # refresh the norm widget if necessary if isinstance(value, mcol.Normalize): self.current_widget.norm = value self.current_widget.fill_from_norm()
class HelpExplorer(QWidget, DockMixin): """A widget for showing the documentation. It behaves somewhat similar to spyders object inspector plugin and can show restructured text either as html (if sphinx is installed) or as plain text. It furthermore has a browser to show html content Warnings -------- The :class:`HelpBrowser` class is known to crash under PyQt4 when new web page domains are loaded. Hence you should disable the browsing to different remote websites and even disable intersphinx""" #: The viewer classes used by the help explorer. :class:`HelpExplorer` #: instances replace this attribute with the corresponding HelpMixin #: instance viewers = OrderedDict([('HTML help', UrlHelp), ('Plain text', TextHelp)]) def __init__(self, *args, **kwargs): super(HelpExplorer, self).__init__(*args, **kwargs) self.vbox = vbox = QVBoxLayout() self.combo = QComboBox(parent=self) vbox.addWidget(self.combo) self.viewers = OrderedDict([ (key, cls(parent=self)) for key, cls in six.iteritems(self.viewers) ]) for key, ini in six.iteritems(self.viewers): self.combo.addItem(key) ini.hide() vbox.addWidget(ini) self.viewer = next(six.itervalues(self.viewers)) self.viewer.show() self.combo.currentIndexChanged[str].connect(self.set_viewer) self.setLayout(vbox) def set_viewer(self, name): """Sets the current documentation viewer Parameters ---------- name: str or object A string must be one of the :attr:`viewers` attribute. An object can be one of the values in the :attr:`viewers` attribute""" if isstring(name) and asstring(name) not in self.viewers: raise ValueError("Don't have a viewer named %s" % (name, )) elif not isstring(name): viewer = name else: name = asstring(name) viewer = self.viewers[name] self.viewer.hide() self.viewer = viewer self.viewer.show() if (isstring(name) and not self.combo.currentText() == name): self.combo.setCurrentIndex(list(self.viewers).index(name)) @docstrings.dedent def show_help(self, obj, oname='', files=None): """ Show the documentaion of the given object We first try to use the current viewer based upon it's :attr:`HelpMixin.can_document_object` attribute. If this does not work, we check the other viewers Parameters ---------- %(HelpMixin.show_help.parameters)s""" oname = asstring(oname) ret = None if self.viewer.can_document_object: try: ret = self.viewer.show_help(obj, oname=oname, files=files) except Exception: logger.debug("Could not document %s with %s viewer!", oname, self.combo.currentText(), exc_info=True) else: curr_i = self.combo.currentIndex() for i, (viewername, viewer) in enumerate(six.iteritems(self.viewers)): if i != curr_i and viewer.can_document_object: self.set_viewer(viewername) self.combo.blockSignals(True) self.combo.setCurrentIndex(i) self.combo.blockSignals(False) try: ret = viewer.show_help(obj, oname=oname, files=files) except Exception: logger.debug("Could not document %s with %s viewer!", oname, viewername, exc_info=True) if ret: self.parent().raise_() return ret @docstrings.dedent def show_rst(self, text, oname='', files=None): """ Show restructured text We first try to use the current viewer based upon it's :attr:`HelpMixin.can_show_rst` attribute. If this does not work, we check the other viewers Parameters ---------- %(HelpMixin.show_rst.parameters)s""" ret = None if self.viewer.can_show_rst: ret = self.viewer.show_rst(text, oname=oname, files=files) else: for viewer in six.itervalues(self.viewers): if viewer.can_show_rst: self.set_viewer(viewer) ret = viewer.show_rst(text, oname=oname, files=files) break if ret: self.parent().raise_() return ret @docstrings.dedent def show_intro(self, text=''): """ Show an intro text We first try to use the current viewer based upon it's :attr:`HelpMixin.can_show_rst` attribute. If this does not work, we check the other viewers Parameters ---------- %(HelpMixin.show_intro.parameters)s""" found = False for i, viewer in enumerate(six.itervalues(self.viewers)): viewer.show_intro(text) if not found and viewer.can_show_rst: if i: self.set_viewer(viewer) found = True def close(self, *args, **kwargs): self.viewers['HTML help'].close(*args, **kwargs) return super(HelpExplorer, self).close(*args, **kwargs)
class FormatoptionWidget(QWidget, DockMixin): """ Widget to update the formatoptions of the current project This widget, mainly made out of a combobox for the formatoption group, a combobox for the formatoption, and a text editor, is designed for updating the selected formatoptions for the current subproject. The widget is connected to the :attr:`psyplot.project.Project.oncpchange` signal and refills the comboboxes if the current subproject changes. The text editor either accepts python code that will be executed by the given `console`, or yaml code. """ no_fmtos_update = _temp_bool_prop( 'no_fmtos_update', """update the fmto combo box or not""") #: The combobox for the formatoption groups group_combo = None #: The combobox for the formatoptions fmt_combo = None #: The help_explorer to display the documentation of the formatoptions help_explorer = None #: The formatoption specific widget that is loaded from the formatoption fmt_widget = None #: A line edit for updating the formatoptions line_edit = None #: A multiline text editor for updating the formatoptions text_edit = None #: A button to switch between :attr:`line_edit` and :attr:`text_edit` multiline_button = None @property def shell(self): """The shell to execute the update of the formatoptions in the current project""" return self.console.kernel_manager.kernel.shell def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode( QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc) def refill_from_rc(self, sort_by_key): from psyplot.project import gcp self.fill_combos_from_project(gcp()) def fill_combos_from_project(self, project): """Fill :attr:`group_combo` and :attr:`fmt_combo` from a project Parameters ---------- project: psyplot.project.Project The project to use""" if rcParams['fmt.sort_by_key']: def sorter(fmto): return fmto.key else: sorter = self.get_name current_text = self.group_combo.currentText() with self.no_fmtos_update: self.group_combo.clear() if project is None or project.is_main or not len(project): self.fmt_combo.clear() self.groups = [] self.fmtos = [] self.line_edit.setEnabled(False) return self.line_edit.setEnabled(True) # get dimensions it_vars = chain.from_iterable( arr.psy.iter_base_variables for arr in project.arrays) dims = next(it_vars).dims sdims = set(dims) for var in it_vars: sdims.intersection_update(var.dims) coords = [d for d in dims if d in sdims] coords_name = [COORDSGROUP] if coords else [] coords_verbose = ['Dimensions'] if coords else [] coords = [coords] if coords else [] if len(project.plotters): # get formatoptions and group them alphabetically grouped_fmts = defaultdict(list) for fmto in project._fmtos: grouped_fmts[fmto.group].append(fmto) for val in six.itervalues(grouped_fmts): val.sort(key=sorter) grouped_fmts = OrderedDict( sorted(six.iteritems(grouped_fmts), key=lambda t: psyp.groups.get(t[0], t[0]))) fmt_groups = list(grouped_fmts.keys()) # save original names self.groups = coords_name + [ALLGROUP] + fmt_groups # save verbose group names (which are used in the combo box) self.groupnames = ( coords_verbose + ['All formatoptions'] + list( map(lambda s: psyp.groups.get(s, s), fmt_groups))) # save formatoptions fmtos = list(grouped_fmts.values()) self.fmtos = coords + [sorted( chain(*fmtos), key=sorter)] + fmtos else: self.groups = coords_name self.groupnames = coords_verbose self.fmtos = coords self.group_combo.addItems(self.groupnames) ind = self.group_combo.findText(current_text) self.group_combo.setCurrentIndex(ind if ind >= 0 else 0) self.fill_fmt_combo(self.group_combo.currentIndex()) def get_name(self, fmto): """Get the name of a :class:`psyplot.plotter.Formatoption` instance""" if isinstance(fmto, six.string_types): return fmto return '%s (%s)' % (fmto.name, fmto.key) if fmto.name else fmto.key @property def fmto(self): return self.fmtos[self.group_combo.currentIndex()][ self.fmt_combo.currentIndex()] @fmto.setter def fmto(self, value): name = self.get_name(value) for i, fmtos in enumerate(self.fmtos): if i == 1: # all formatoptions continue if name in map(self.get_name, fmtos): with self.no_fmtos_update: self.group_combo.setCurrentIndex(i) self.fill_fmt_combo(i, name) return def toggle_line_edit(self): """Switch between the :attr:`line_edit` and :attr:`text_edit` This method is called when the :attr:`multiline_button` is clicked and switches between the single line :attr:``line_edit` and the multiline :attr:`text_edit` """ # switch to multiline text edit if (self.multiline_button.isChecked() and not self.text_edit.isVisible()): self.line_edit.setVisible(False) self.text_edit.setVisible(True) self.text_edit.setPlainText(self.line_edit.text()) elif (not self.multiline_button.isChecked() and not self.line_edit.isVisible()): self.line_edit.setVisible(True) self.text_edit.setVisible(False) self.line_edit.setText(self.text_edit.toPlainText()) def fill_fmt_combo(self, i, current_text=None): """Fill the :attr:`fmt_combo` combobox based on the current group name """ if not self.no_fmtos_update: with self.no_fmtos_update: if current_text is None: current_text = self.fmt_combo.currentText() self.fmt_combo.clear() self.fmt_combo.addItems( list(map(self.get_name, self.fmtos[i]))) ind = self.fmt_combo.findText(current_text) self.fmt_combo.setCurrentIndex(ind if ind >= 0 else 0) # update completer model self.setup_fmt_completion_model() idx = self.fmt_combo.currentIndex() self.show_fmt_info(idx) self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def set_fmto(self, name): self.fmto = name def setup_fmt_completion_model(self): fmtos = list(unique_everseen(map( self.get_name, chain.from_iterable(self.fmtos)))) model = self.fmto_completer.model() model.setRowCount(len(fmtos)) for i, name in enumerate(fmtos): model.setItem(i, QStandardItem(name)) def load_fmt_widget(self, i): """Load the formatoption specific widget This method loads the formatoption specific widget from the :meth:`psyplot.plotter.Formatoption.get_fmt_widget` method and displays it above the :attr:`line_edit` Parameters ---------- i: int The index of the current formatoption""" self.remove_fmt_widget() group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: from psyplot.project import gcp if self.groups[group_ind] == COORDSGROUP: dim = self.fmtos[group_ind][i] self.fmt_widget = self.dim_widget self.dim_widget.set_dim(dim) self.dim_widget.set_single_selection( dim not in gcp()[0].dims) self.dim_widget.setVisible(True) else: fmto = self.fmtos[group_ind][i] self.fmt_widget = fmto.get_fmt_widget(self, gcp()) if self.fmt_widget is not None: self.vbox.insertWidget(2, self.fmt_widget) def reset_fmt_widget(self): idx = self.fmt_combo.currentIndex() self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def remove_fmt_widget(self): if self.fmt_widget is not None: self.fmt_widget.hide() if self.fmt_widget is self.dim_widget: self.fmt_widget.reset_combobox() else: self.vbox.removeWidget(self.fmt_widget) self.fmt_widget.close() del self.fmt_widget def set_current_fmt_value(self, i): """Add the value of the current formatoption to the line text""" group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: if self.groups[group_ind] == COORDSGROUP: from psyplot.project import gcp dim = self.fmtos[group_ind][i] self.set_obj(gcp().arrays[0].psy.idims[dim]) else: fmto = self.fmtos[group_ind][i] self.set_obj(fmto.value) def show_fmt_info(self, i): """Show the documentation of the formatoption in the help explorer """ group_ind = self.group_combo.currentIndex() if (not self.no_fmtos_update and self.groups[group_ind] != COORDSGROUP): fmto = self.fmtos[self.group_combo.currentIndex()][i] fmto.plotter.show_docs( fmto.key, include_links=self.include_links_cb.isChecked()) def run_code(self): """Run the update of the project inside the :attr:`shell`""" if self.line_edit.isVisible(): text = str(self.line_edit.text()) else: text = str(self.text_edit.toPlainText()) if not text or not self.fmtos: return group_ind = self.group_combo.currentIndex() if self.groups[group_ind] == COORDSGROUP: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()] param = 'dims' else: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()].key param = 'fmt' if self.yaml_cb.isChecked(): import psyplot.project as psy psy.gcp().update(**{key: yaml.load(text)}) else: code = "psy.gcp().update(%s={'%s': %s})" % (param, key, text) if ExecutionInfo is not None: info = ExecutionInfo(raw_cell=code, store_history=False, silent=True, shell_futures=False) e = ExecutionResult(info) else: e = ExecutionResult() self.console.run_command_in_shell(code, e) try: e.raise_error() except Exception: # reset the console and clear the error message raise finally: self.console.reset() def get_text(self): """Get the current update text""" if self.line_edit.isVisible(): return self.line_edit.text() else: return self.text_edit.toPlainText() def get_obj(self): """Get the current update text""" if self.line_edit.isVisible(): txt = self.line_edit.text() else: txt = self.text_edit.toPlainText() try: obj = yaml.load(txt) except Exception: self.error_msg.showTraceback("Could not load %s" % txt) else: return obj def insert_obj(self, obj): """Add a string to the formatoption widget""" current = self.get_text() use_yaml = self.yaml_cb.isChecked() use_line_edit = self.line_edit.isVisible() # strings are treated separately such that we consider quotation marks # at the borders if isstring(obj) and current: if use_line_edit: pos = self.line_edit.cursorPosition() else: pos = self.text_edit.textCursor().position() if pos not in [0, len(current)]: s = obj else: if current[0] in ['"', "'"]: current = current[1:-1] self.clear_text() if pos == 0: s = '"' + obj + current + '"' else: s = '"' + current + obj + '"' current = '' elif isstring(obj): # add quotation marks s = '"' + obj + '"' elif not use_yaml: s = repr(obj) else: s = yaml.dump(obj).strip() if s.endswith('\n...'): s = s[:-4] if use_line_edit: self.line_edit.insert(s) else: self.text_edit.insertPlainText(s) def clear_text(self): if self.line_edit.isVisible(): self.line_edit.clear() else: self.text_edit.clear() def set_obj(self, obj): self.clear_text() self.insert_obj(obj) def show_all_fmt_info(self, what): """Show the keys, summaries or docs of the formatoptions Calling this function let's the help browser show the documentation etc. of all docs or only the selected group determined by the state of the :attr:`grouped_cb` and :attr:`all_groups_cb` checkboxes Parameters ---------- what: {'keys', 'summaries', 'docs'} Determines what to show""" if not self.fmtos: return if (self.all_groups_cb.isChecked() or self.group_combo.currentIndex() < 2): fmtos = list(chain.from_iterable( fmto_group for i, fmto_group in enumerate(self.fmtos) if self.groups[i] not in [ALLGROUP, COORDSGROUP])) else: fmtos = self.fmtos[self.group_combo.currentIndex()] plotter = fmtos[0].plotter getattr(plotter, 'show_' + what)( [fmto.key for fmto in fmtos], grouped=self.grouped_cb.isChecked(), include_links=self.include_links_cb.isChecked())
def keyPressEvent(self, event): """Handle key press events""" if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: self.add_text_on_top() else: QComboBox.keyPressEvent(self, event)
def create_actions(self): """Define the actions for the toolbar and set everything up""" # Reader toolbar self.combo = QComboBox() self.combo.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.addWidget(self.combo) select_group = QActionGroup(self) # select action self._actions['select'] = a = self.addAction( QIcon(get_icon('select.png')), 'select', self.toggle_selection) a.setToolTip('Select pixels within a rectangle') a.setCheckable(True) select_group.addAction(a) # select menu select_menu = QMenu(self) self._select_actions['rect_select'] = menu_a = select_menu.addAction( QIcon(get_icon('select.png')), 'rectangle', self.set_rect_select_mode) menu_a.setToolTip('Select a rectangle') a.setToolTip(menu_a.toolTip()) self._select_actions['poly_select'] = menu_a = select_menu.addAction( QIcon(get_icon('poly_select.png')), 'polygon', self.set_poly_select_mode) menu_a.setToolTip('Select a rectangle') a.setToolTip(menu_a.toolTip()) a.setMenu(select_menu) # wand_select action self._actions['wand_select'] = a = self.addAction( QIcon(get_icon('wand_select.png')), 'select', self.toggle_selection) a.setCheckable(True) select_group.addAction(a) # wand menu tool_menu = QMenu(self) self._wand_actions['wand_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('wand_select.png')), 'wand', self.set_label_wand_mode) menu_a.setToolTip('Select labels within a rectangle') a.setToolTip(menu_a.toolTip()) self._wand_actions['color_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('color_select.png')), 'color wand', self.set_color_wand_mode) menu_a.setToolTip('Select colors') self._wand_actions['row_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('row_select.png')), 'row selection', self.set_row_wand_mode) menu_a.setToolTip('Select pixel rows') self._wand_actions['col_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('col_select.png')), 'column selection', self.set_col_wand_mode) menu_a.setToolTip('Select pixel columns') a.setMenu(tool_menu) # color_wand widgets self.distance_slider = slider = QSlider(Qt.Horizontal) slider.setMinimum(0) slider.setMaximum(255) slider.setValue(30) slider.setSingleStep(1) self.lbl_slider = QLabel('30') slider.valueChanged.connect(lambda i: self.lbl_slider.setText(str(i))) slider.setMaximumWidth(self.combo.sizeHint().width()) self.cb_whole_fig = QCheckBox('Whole plot') self.cb_whole_fig.setToolTip('Select the colors on the entire plot') self.cb_use_alpha = QCheckBox('Use alpha') self.cb_use_alpha.setToolTip('Use the alpha channel, i.e. the ' 'transparency of the RGBA image.') self.color_wand_actions = [ self.addWidget(slider), self.addWidget(self.lbl_slider), self.addWidget(self.cb_whole_fig), self.addWidget(self.cb_use_alpha)] self.set_label_wand_mode() self.addSeparator() type_group = QActionGroup(self) self._type_actions = {} # new selection action self._type_actions['new_select'] = a = self.addAction( QIcon(get_icon('new_selection.png')), 'Create a new selection') a.setToolTip('Select pixels within a rectangle and ignore the current ' 'selection') a.setCheckable(True) type_group.addAction(a) # add to selection action self._type_actions['add_select'] = a = self.addAction( QIcon(get_icon('add_select.png')), 'Add to selection') a.setToolTip('Select pixels within a rectangle and add them to the ' 'current selection') a.setCheckable(True) type_group.addAction(a) # remove action self._type_actions['remove_select'] = a = self.addAction( QIcon(get_icon('remove_select.png')), 'Remove from selection') a.setToolTip('Select pixels within a rectangle and remove them from ' 'the current selection') a.setCheckable(True) type_group.addAction(a) # info button self.addSeparator() self.info_button = InfoButton(self, 'selection_toolbar.rst') self.addWidget(self.info_button) # selection appearence options self.addSeparator() self.sl_alpha = slider = QSlider(Qt.Horizontal) self._appearance_actions['alpha'] = self.addWidget(slider) slider.setMinimum(0) slider.setMaximum(100) slider.setValue(100) slider.setSingleStep(1) self.lbl_alpha_slider = QLabel('100 %') slider.valueChanged.connect( lambda i: self.lbl_alpha_slider.setText(str(i) + ' %')) slider.valueChanged.connect(self.update_alpha) slider.setMaximumWidth(self.combo.sizeHint().width()) # Select all and invert selection buttons self.addSeparator() self._actions['select_all'] = a = self.addAction( QIcon(get_icon('select_all.png')), 'all', self.select_all) a.setToolTip('Select all labels') self._actions['expand_select'] = a = self.addAction( QIcon(get_icon('expand_select.png')), 'expand', self.expand_selection) a.setToolTip('Expand the selected areas to select the entire feature') self._actions['invert_select'] = a = self.addAction( QIcon(get_icon('invert_select.png')), 'invert', self.invert_selection) a.setToolTip('Invert selection') self._actions['clear_select'] = a = self.addAction( QIcon(get_icon('clear_select.png')), 'clear', self.clear_selection) a.setToolTip('Clear selection') self._actions['select_right'] = a = self.addAction( QIcon(get_icon('select_right.png')), 'right', self.select_everything_to_the_right) a.setToolTip('Select everything to the right of each column') self._actions['select_pattern'] = a = self.addAction( QIcon(get_icon('pattern.png')), 'pattern', self.start_pattern_selection) a.setCheckable(True) a.setToolTip( 'Select a binary pattern/hatch within the current selection') # wand menu pattern_menu = QMenu(self) self._pattern_actions['binary'] = menu_a = pattern_menu.addAction( QIcon(get_icon('pattern.png')), 'Binary', self.set_binary_pattern_mode) menu_a.setToolTip( 'Select a binary pattern/hatch within the current selection') a.setToolTip(menu_a.toolTip()) self._pattern_actions['grey'] = menu_a = pattern_menu.addAction( QIcon(get_icon('pattern_grey.png')), 'Greyscale', self.set_grey_pattern_mode) menu_a.setToolTip( 'Select a pattern/hatch within the current selection based on ' 'grey scale colors') a.setMenu(pattern_menu) self.new_select_action.setChecked(True) for a in self._type_actions.values(): a.toggled.connect(self.add_or_remove_pattern) self.refresh()
def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode( QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc)
def __init__(self, *args, **kwargs): from straditize.widgets.menu_actions import StraditizerMenuActions from straditize.widgets.progress_widget import ProgressWidget from straditize.widgets.data import DigitizingControl from straditize.widgets.selection_toolbar import SelectionToolbar from straditize.widgets.marker_control import MarkerControl from straditize.widgets.plots import PlotControl from straditize.widgets.axes_translations import AxesTranslations from straditize.widgets.image_correction import (ImageRotator, ImageRescaler) from straditize.widgets.colnames import ColumnNamesManager self._straditizers = [] super(StraditizerWidgets, self).__init__(*args, **kwargs) self.tree = QTreeWidget(parent=self) self.tree.setSelectionMode(QTreeWidget.NoSelection) self.refresh_button = QToolButton(self) self.refresh_button.setIcon(QIcon(get_psy_icon('refresh.png'))) self.refresh_button.setToolTip('Refresh from the straditizer') self.apply_button = EnableButton('Apply', parent=self) self.cancel_button = EnableButton('Cancel', parent=self) self.attrs_button = QPushButton('Attributes', parent=self) self.tutorial_button = QPushButton('Tutorial', parent=self) self.tutorial_button.setCheckable(True) self.error_msg = PyErrorMessage(self) self.stradi_combo = QComboBox() self.btn_open_stradi = QToolButton() self.btn_open_stradi.setIcon(QIcon(get_psy_icon('run_arrow.png'))) self.btn_close_stradi = QToolButton() self.btn_close_stradi.setIcon(QIcon(get_psy_icon('invalid.png'))) self.btn_reload_autosaved = QPushButton("Reload") self.btn_reload_autosaved.setToolTip( "Close the straditizer and reload the last autosaved project") # --------------------------------------------------------------------- # --------------------------- Tree widgets ---------------------------- # --------------------------------------------------------------------- self.tree.setHeaderLabels(['', '']) self.tree.setColumnCount(2) self.progress_item = QTreeWidgetItem(0) self.progress_item.setText(0, 'ToDo list') self.progress_widget = ProgressWidget(self, self.progress_item) self.menu_actions_item = QTreeWidgetItem(0) self.menu_actions_item.setText(0, 'Images import/export') self.tree.addTopLevelItem(self.menu_actions_item) self.menu_actions = StraditizerMenuActions(self) self.digitizer_item = item = QTreeWidgetItem(0) item.setText(0, 'Digitization control') self.digitizer = DigitizingControl(self, item) self.col_names_item = item = QTreeWidgetItem(0) item.setText(0, 'Column names') self.colnames_manager = ColumnNamesManager(self, item) self.add_info_button(item, 'column_names.rst') self.axes_translations_item = item = QTreeWidgetItem(0) item.setText(0, 'Axes translations') self.axes_translations = AxesTranslations(self, item) self.image_transform_item = item = QTreeWidgetItem(0) item.setText(0, 'Transform source image') self.image_rescaler = ImageRescaler(self, item) self.image_rotator_item = item = QTreeWidgetItem(0) item.setText(0, 'Rotate image') self.image_rotator = ImageRotator(self) self.image_transform_item.addChild(item) self.image_rotator.setup_children(item) self.plot_control_item = item = QTreeWidgetItem(0) item.setText(0, 'Plot control') self.plot_control = PlotControl(self, item) self.add_info_button(item, 'plot_control.rst') self.marker_control_item = item = QTreeWidgetItem(0) item.setText(0, 'Marker control') self.marker_control = MarkerControl(self, item) self.add_info_button(item, 'marker_control.rst') # --------------------------------------------------------------------- # ----------------------------- Toolbars ------------------------------ # --------------------------------------------------------------------- self.selection_toolbar = SelectionToolbar(self, 'Selection toolbar') # --------------------------------------------------------------------- # ----------------------------- InfoButton ---------------------------- # --------------------------------------------------------------------- self.info_button = InfoButton(self, get_doc_file('straditize.rst')) # --------------------------------------------------------------------- # --------------------------- Layouts --------------------------------- # --------------------------------------------------------------------- stradi_box = QHBoxLayout() stradi_box.addWidget(self.stradi_combo, 1) stradi_box.addWidget(self.btn_open_stradi) stradi_box.addWidget(self.btn_close_stradi) attrs_box = QHBoxLayout() attrs_box.addWidget(self.attrs_button) attrs_box.addStretch(0) attrs_box.addWidget(self.tutorial_button) btn_box = QHBoxLayout() btn_box.addWidget(self.refresh_button) btn_box.addWidget(self.info_button) btn_box.addStretch(0) btn_box.addWidget(self.apply_button) btn_box.addWidget(self.cancel_button) reload_box = QHBoxLayout() reload_box.addWidget(self.btn_reload_autosaved) reload_box.addStretch(0) vbox = QVBoxLayout() vbox.addLayout(stradi_box) vbox.addWidget(self.tree) vbox.addLayout(attrs_box) vbox.addLayout(btn_box) vbox.addLayout(reload_box) self.setLayout(vbox) self.apply_button.setEnabled(False) self.cancel_button.setEnabled(False) self.tree.expandItem(self.progress_item) self.tree.expandItem(self.digitizer_item) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.stradi_combo.currentIndexChanged.connect(self.set_current_stradi) self.refresh_button.clicked.connect(self.refresh) self.attrs_button.clicked.connect(self.edit_attrs) self.tutorial_button.clicked.connect(self.start_tutorial) self.open_external.connect(self._create_straditizer_from_args) self.btn_open_stradi.clicked.connect( self.menu_actions.open_straditizer) self.btn_close_stradi.clicked.connect(self.close_straditizer) self.btn_reload_autosaved.clicked.connect(self.reload_autosaved) self.refresh() header = self.tree.header() header.setStretchLastSection(False) header.setSectionResizeMode(0, QHeaderView.Stretch)
class SelectionToolbar(QToolBar, StraditizerControlBase): """A toolbar for selecting features in the straditizer and data image The current data object is set in the :attr:`combo` and can be accessed through the :attr:`data_obj` attribute. It's either the straditizer or the data_reader that is accessed""" _idPress = None _idRelease = None #: A signal that is emitted when something is selected selected = QtCore.pyqtSignal() set_cursor_id = None reset_cursor_id = None #: The QCombobox that defines the data object to be used combo = None @property def ax(self): """The :class:`matplotlib.axes.Axes` of the :attr:`data_obj`""" return self.data_obj.ax @property def data(self): """The np.ndarray of the :attr:`data_obj` image""" text = self.combo.currentText() if text == 'Reader': return self.straditizer.data_reader.binary elif text == 'Reader - Greyscale': return self.straditizer.data_reader.to_grey_pil( self.straditizer.data_reader.image) else: from straditize.binary import DataReader return DataReader.to_grey_pil(self.straditizer.image) @property def data_obj(self): """The data object as set in the :attr:`combo`. Either a :class:`~straditize.straditizer.Straditizer` or a :class:`straditize.binary.DataReader` instance. """ text = self.combo.currentText() if text in ['Reader', 'Reader - Greyscale']: return self.straditizer.data_reader else: return self.straditizer @data_obj.setter def data_obj(self, value): """The data object as set in the :attr:`combo`. Either a :class:`~straditize.straditizer.Straditizer` or a :class:`straditize.binary.DataReader` instance. """ if self.straditizer is None: return if isinstance(value, six.string_types): possible_values = { self.combo.itemText(i) for i in range(self.combo.count())} if value not in possible_values: raise ValueError( 'Do not understand %r! Please use one of %r' % ( value, possible_values)) else: self.combo.setCurrentText(value) else: if value is self.straditizer: self.combo.setCurrentText('Straditizer') elif value and value is self.straditizer.data_reader: self.combo.setCurrentText('Reader') else: raise ValueError('Do not understand %r! Please either use ' 'the Straditizer or DataReader instance!' % ( value, )) @property def fig(self): """The :class:`~matplotlib.figure.Figure` of the :attr:`data_obj`""" try: return self.ax.figure except AttributeError: return None @property def canvas(self): """The canvas of the :attr:`data_obj`""" try: return self.fig.canvas except AttributeError: return None @property def toolbar(self): """The toolbar of the :attr:`canvas`""" return self.canvas.toolbar @property def select_action(self): """The rectangle selection tool""" return self._actions['select'] @property def wand_action(self): """The wand selection tool""" return self._actions['wand_select'] @property def new_select_action(self): """The action to make new selection with one of the selection tools""" return self._type_actions['new_select'] @property def add_select_action(self): """The action to add to the current selection with the selection tools """ return self._type_actions['add_select'] @property def remove_select_action(self): """ An action to remove from the current selection with the selection tools """ return self._type_actions['remove_select'] @property def select_all_action(self): """An action to select all features in the :attr:`data`""" return self._actions['select_all'] @property def expand_select_action(self): """An action to expand the current selection to the full feature""" return self._actions['expand_select'] @property def invert_select_action(self): """An action to invert the current selection""" return self._actions['invert_select'] @property def clear_select_action(self): """An action to clear the current selection""" return self._actions['clear_select'] @property def select_right_action(self): """An action to select everything in the data column to the right""" return self._actions['select_right'] @property def select_pattern_action(self): """An action to start a pattern selection""" return self._actions['select_pattern'] @property def widgets2disable(self): if not self._actions: return [] elif self._selecting: return [self.combo] else: return list(chain([self.combo], self._actions.values(), self._appearance_actions.values())) @property def labels(self): """The labeled data that is displayed""" if self.data_obj._selection_arr is not None: return self.data_obj._selection_arr text = self.combo.currentText() if text == 'Reader': return self.straditizer.data_reader.labels.copy() elif text == 'Reader - Greyscale': return self.straditizer.data_reader.color_labels() else: return self.straditizer.get_labels() @property def rect_callbacks(self): """The functions to call after the rectangle selection. If not set manually, it is the :meth:`select_rect` method. Note that this is cleared at every call of the :meth:`end_selection`. Callables in this list must accept two arguments ``(slx, sly)``: the first one is the x-slice, and the second one the y-slice. They both correspond to the :attr:`data` attribute.""" return self._rect_callbacks or [self.select_rect] @rect_callbacks.setter def rect_callbacks(self, value): """The functions to call after the rectangle selection. If not set manually, it is the :meth:`select_rect` method. Note that this is cleared at every call of the :meth:`end_selection`. Callables in this list must accept two arguments ``(slx, sly)``: the first one is the x-slice, and the second one the y-slice. They both correspond to the :attr:`data` attribute.""" self._rect_callbacks = value @property def poly_callbacks(self): """The functions to call after the polygon selection If not set manually, it is the :meth:`select_poly` method. Note that this is cleared at every call of the :meth:`end_selection`. Callables in this list must accept one argument, a ``np.ndarray`` of shape ``(N, 2)``. This array defines the ``N`` x- and y-coordinates of the points of the polygon""" return self._poly_callbacks or [self.select_poly] @poly_callbacks.setter def poly_callbacks(self, value): """The functions to call after the polygon selection. If not set manually, it is the :meth:`poly_callbacks` method. Note that this is cleared at every call of the :meth:`end_selection`. Callables in this list must accept one argument, a ``np.ndarray`` of shape ``(N, 2)``. This array defines the ``N`` x- and y-coordinates of the points of the polygon""" self._poly_callbacks = value #: A :class:`PointOrRectangleSelector` to select features in the image selector = None _pattern_selection = None def __init__(self, straditizer_widgets, *args, **kwargs): super(SelectionToolbar, self).__init__(*args, **kwargs) self._actions = {} self._wand_actions = {} self._pattern_actions = {} self._select_actions = {} self._appearance_actions = {} # Boolean that is True if we are in a selection process self._selecting = False self.init_straditizercontrol(straditizer_widgets) self._ids_select = [] self._rect_callbacks = [] self._poly_callbacks = [] self._selection_mode = None self._lastCursor = None self.create_actions() self._changed_selection = False self._connected = [] self._action_clicked = None self.wand_type = 'labels' self.select_type = 'rect' self.pattern_type = 'binary' self.auto_expand = False def create_actions(self): """Define the actions for the toolbar and set everything up""" # Reader toolbar self.combo = QComboBox() self.combo.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.addWidget(self.combo) select_group = QActionGroup(self) # select action self._actions['select'] = a = self.addAction( QIcon(get_icon('select.png')), 'select', self.toggle_selection) a.setToolTip('Select pixels within a rectangle') a.setCheckable(True) select_group.addAction(a) # select menu select_menu = QMenu(self) self._select_actions['rect_select'] = menu_a = select_menu.addAction( QIcon(get_icon('select.png')), 'rectangle', self.set_rect_select_mode) menu_a.setToolTip('Select a rectangle') a.setToolTip(menu_a.toolTip()) self._select_actions['poly_select'] = menu_a = select_menu.addAction( QIcon(get_icon('poly_select.png')), 'polygon', self.set_poly_select_mode) menu_a.setToolTip('Select a rectangle') a.setToolTip(menu_a.toolTip()) a.setMenu(select_menu) # wand_select action self._actions['wand_select'] = a = self.addAction( QIcon(get_icon('wand_select.png')), 'select', self.toggle_selection) a.setCheckable(True) select_group.addAction(a) # wand menu tool_menu = QMenu(self) self._wand_actions['wand_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('wand_select.png')), 'wand', self.set_label_wand_mode) menu_a.setToolTip('Select labels within a rectangle') a.setToolTip(menu_a.toolTip()) self._wand_actions['color_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('color_select.png')), 'color wand', self.set_color_wand_mode) menu_a.setToolTip('Select colors') self._wand_actions['row_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('row_select.png')), 'row selection', self.set_row_wand_mode) menu_a.setToolTip('Select pixel rows') self._wand_actions['col_select'] = menu_a = tool_menu.addAction( QIcon(get_icon('col_select.png')), 'column selection', self.set_col_wand_mode) menu_a.setToolTip('Select pixel columns') a.setMenu(tool_menu) # color_wand widgets self.distance_slider = slider = QSlider(Qt.Horizontal) slider.setMinimum(0) slider.setMaximum(255) slider.setValue(30) slider.setSingleStep(1) self.lbl_slider = QLabel('30') slider.valueChanged.connect(lambda i: self.lbl_slider.setText(str(i))) slider.setMaximumWidth(self.combo.sizeHint().width()) self.cb_whole_fig = QCheckBox('Whole plot') self.cb_whole_fig.setToolTip('Select the colors on the entire plot') self.cb_use_alpha = QCheckBox('Use alpha') self.cb_use_alpha.setToolTip('Use the alpha channel, i.e. the ' 'transparency of the RGBA image.') self.color_wand_actions = [ self.addWidget(slider), self.addWidget(self.lbl_slider), self.addWidget(self.cb_whole_fig), self.addWidget(self.cb_use_alpha)] self.set_label_wand_mode() self.addSeparator() type_group = QActionGroup(self) self._type_actions = {} # new selection action self._type_actions['new_select'] = a = self.addAction( QIcon(get_icon('new_selection.png')), 'Create a new selection') a.setToolTip('Select pixels within a rectangle and ignore the current ' 'selection') a.setCheckable(True) type_group.addAction(a) # add to selection action self._type_actions['add_select'] = a = self.addAction( QIcon(get_icon('add_select.png')), 'Add to selection') a.setToolTip('Select pixels within a rectangle and add them to the ' 'current selection') a.setCheckable(True) type_group.addAction(a) # remove action self._type_actions['remove_select'] = a = self.addAction( QIcon(get_icon('remove_select.png')), 'Remove from selection') a.setToolTip('Select pixels within a rectangle and remove them from ' 'the current selection') a.setCheckable(True) type_group.addAction(a) # info button self.addSeparator() self.info_button = InfoButton(self, 'selection_toolbar.rst') self.addWidget(self.info_button) # selection appearence options self.addSeparator() self.sl_alpha = slider = QSlider(Qt.Horizontal) self._appearance_actions['alpha'] = self.addWidget(slider) slider.setMinimum(0) slider.setMaximum(100) slider.setValue(100) slider.setSingleStep(1) self.lbl_alpha_slider = QLabel('100 %') slider.valueChanged.connect( lambda i: self.lbl_alpha_slider.setText(str(i) + ' %')) slider.valueChanged.connect(self.update_alpha) slider.setMaximumWidth(self.combo.sizeHint().width()) # Select all and invert selection buttons self.addSeparator() self._actions['select_all'] = a = self.addAction( QIcon(get_icon('select_all.png')), 'all', self.select_all) a.setToolTip('Select all labels') self._actions['expand_select'] = a = self.addAction( QIcon(get_icon('expand_select.png')), 'expand', self.expand_selection) a.setToolTip('Expand the selected areas to select the entire feature') self._actions['invert_select'] = a = self.addAction( QIcon(get_icon('invert_select.png')), 'invert', self.invert_selection) a.setToolTip('Invert selection') self._actions['clear_select'] = a = self.addAction( QIcon(get_icon('clear_select.png')), 'clear', self.clear_selection) a.setToolTip('Clear selection') self._actions['select_right'] = a = self.addAction( QIcon(get_icon('select_right.png')), 'right', self.select_everything_to_the_right) a.setToolTip('Select everything to the right of each column') self._actions['select_pattern'] = a = self.addAction( QIcon(get_icon('pattern.png')), 'pattern', self.start_pattern_selection) a.setCheckable(True) a.setToolTip( 'Select a binary pattern/hatch within the current selection') # wand menu pattern_menu = QMenu(self) self._pattern_actions['binary'] = menu_a = pattern_menu.addAction( QIcon(get_icon('pattern.png')), 'Binary', self.set_binary_pattern_mode) menu_a.setToolTip( 'Select a binary pattern/hatch within the current selection') a.setToolTip(menu_a.toolTip()) self._pattern_actions['grey'] = menu_a = pattern_menu.addAction( QIcon(get_icon('pattern_grey.png')), 'Greyscale', self.set_grey_pattern_mode) menu_a.setToolTip( 'Select a pattern/hatch within the current selection based on ' 'grey scale colors') a.setMenu(pattern_menu) self.new_select_action.setChecked(True) for a in self._type_actions.values(): a.toggled.connect(self.add_or_remove_pattern) self.refresh() def should_be_enabled(self, w): if self.straditizer is None: return False elif (self._actions and w in [self.remove_select_action, self.invert_select_action, self.clear_select_action, self.expand_select_action, self.select_right_action] and not self._selecting): return False elif w in self._appearance_actions.values() and not self._selecting: return False elif (self.combo and not self.combo.currentText().startswith('Reader') and w is self.select_right_action): return False return True def disable_actions(self): if self._changed_selection: return for a in self._actions.values(): if a.isChecked(): a.setChecked(False) self.toggle_selection() else: a.setChecked(False) def select_all(self): """Select all features in the image See Also -------- straditize.label_selection.LabelSelection.select_all_labels""" obj = self.data_obj if obj._selection_arr is None: rgba = obj.image_array() if hasattr(obj, 'image_array') else None self.start_selection(self.labels, rgba=rgba) obj.select_all_labels() self.canvas.draw() def invert_selection(self): """Invert the current selection""" obj = self.data_obj if obj._selection_arr is None: rgba = obj.image_array() if hasattr(obj, 'image_array') else None self.start_selection(self.labels, rgba=rgba) if (obj._selection_arr != obj._orig_selection_arr).any(): selection = obj.selected_part # clear the current selection obj._selection_arr[:] = np.where( obj._selection_arr.astype(bool) & (~selection), obj._orig_selection_arr.max() + 1, obj._orig_selection_arr) obj._select_img.set_array(obj._selection_arr) obj.unselect_all_labels() else: obj.select_all_other_labels() self.canvas.draw() def clear_selection(self): """Clear the current selection""" obj = self.data_obj if obj._selection_arr is None: return obj._selection_arr[:] = obj._orig_selection_arr.copy() obj._select_img.set_array(obj._selection_arr) obj.unselect_all_labels() self.canvas.draw() def expand_selection(self): """Expand the selected areas to select the full labels""" obj = self.data_obj if obj._selection_arr is None: return arr = obj._orig_selection_arr.copy() selected_labels = np.unique(arr[obj.selected_part]) obj._selection_arr = arr obj._select_img.set_array(arr) obj.unselect_all_labels() obj.select_labels(selected_labels) self.canvas.draw() def update_alpha(self, i): """Set the transparency of the selection image Parameters ---------- i: int The transparency between 0 and 100""" self.data_obj._select_img.set_alpha(i / 100.) self.data_obj._update_magni_img() self.canvas.draw() def select_everything_to_the_right(self): """Selects everything to the right of the current selection""" reader = self.data_obj if reader._selection_arr is None: return bounds = reader.column_bounds selection = reader.selected_part new_select = np.zeros_like(selection) for start, end in bounds: can_be_selected = reader._selection_arr[:, start:end].astype(bool) end = start + can_be_selected.shape[1] last_in_row = selection[:, start:end].argmax(axis=-1).reshape( (-1, 1)) dist2start = np.tile(np.arange(end - start)[np.newaxis], (len(selection), 1)) can_be_selected[dist2start <= last_in_row] = False can_be_selected[~np.tile(last_in_row.astype(bool), (1, end - start))] = False new_select[:, start:end] = can_be_selected max_label = reader._orig_selection_arr.max() reader._selection_arr[new_select] = max_label + 1 reader._select_img.set_array(reader._selection_arr) reader._update_magni_img() self.canvas.draw() def start_pattern_selection(self): """Open the pattern selection dialog This method will enable the pattern selection by starting a :class:`straditize.widgets.pattern_selection.PatternSelectionWidget`""" from straditize.widgets.pattern_selection import PatternSelectionWidget if self.select_pattern_action.isChecked(): from straditize.binary import DataReader from psyplot_gui.main import mainwindow obj = self.data_obj if obj._selection_arr is None: if hasattr(obj, 'image_array'): rgba = obj.image_array() else: rgba = None self.start_selection(self.labels, rgba=rgba) self.select_all() if not obj.selected_part.any(): self.select_pattern_action.setChecked(False) raise ValueError( "No data in the image is selected. Please select the " "coarse region in which the pattern should be searched.") if self.pattern_type == 'binary': arr = DataReader.to_binary_pil(obj.image) else: arr = DataReader.to_grey_pil(obj.image) self._pattern_selection = w = PatternSelectionWidget( arr, obj) w.to_dock(mainwindow, 'Pattern selection') w.btn_close.clicked.connect(self.uncheck_pattern_selection) w.btn_cancel.clicked.connect(self.uncheck_pattern_selection) self.disable_actions() pattern_action = self.select_pattern_action for a in self._actions.values(): a.setEnabled(False) pattern_action.setEnabled(True) pattern_action.setChecked(True) w.show_plugin() w.maybe_tabify() w.raise_() elif self._pattern_selection is not None: self._pattern_selection.cancel() self._pattern_selection.close() self.uncheck_pattern_selection() del self._pattern_selection def uncheck_pattern_selection(self): """Disable the pattern selection""" self.select_pattern_action.setChecked(False) del self._pattern_selection for a in self._actions.values(): a.setEnabled(self.should_be_enabled(a)) def add_or_remove_pattern(self): """Enable the removing or adding of the pattern selection""" if getattr(self, '_pattern_selection', None) is None: return current = self._pattern_selection.remove_selection new = self.remove_select_action.isChecked() if new is not current: self._pattern_selection.remove_selection = new if self._pattern_selection.btn_select.isChecked(): self._pattern_selection.modify_selection( self._pattern_selection.sl_thresh.value()) def set_rect_select_mode(self): """Set the current wand tool to the color wand""" self.select_type = 'rect' self.select_action.setIcon(QIcon(get_icon('select.png'))) self._action_clicked = None self.toggle_selection() def set_poly_select_mode(self): """Set the current wand tool to the color wand""" self.select_type = 'poly' self.select_action.setIcon(QIcon(get_icon('poly_select.png'))) self._action_clicked = None self.toggle_selection() def set_label_wand_mode(self): """Set the current wand tool to the color wand""" self.wand_type = 'labels' self.wand_action.setIcon(QIcon(get_icon('wand_select.png'))) for a in self.color_wand_actions: a.setVisible(False) self._action_clicked = None self.toggle_selection() def set_color_wand_mode(self): """Set the current wand tool to the color wand""" self.wand_type = 'color' self.wand_action.setIcon(QIcon(get_icon('color_select.png'))) for a in self.color_wand_actions: a.setVisible(True) self._action_clicked = None self.toggle_selection() def set_row_wand_mode(self): """Set the current wand tool to the color wand""" self.wand_type = 'rows' self.wand_action.setIcon(QIcon(get_icon('row_select.png'))) for a in self.color_wand_actions: a.setVisible(False) self._action_clicked = None self.toggle_selection() def set_col_wand_mode(self): """Set the current wand tool to the color wand""" self.wand_type = 'cols' self.wand_action.setIcon(QIcon(get_icon('col_select.png'))) for a in self.color_wand_actions: a.setVisible(False) self._action_clicked = None self.toggle_selection() def set_binary_pattern_mode(self): """Set the current pattern mode to the binary pattern""" self.pattern_type = 'binary' self.select_pattern_action.setIcon(QIcon(get_icon('pattern.png'))) def set_grey_pattern_mode(self): """Set the current pattern mode to the binary pattern""" self.pattern_type = 'grey' self.select_pattern_action.setIcon(QIcon(get_icon('pattern_grey.png'))) def disconnect(self): if self.set_cursor_id is not None: if self.canvas is None: self.canvas.mpl_disconnect(self.set_cursor_id) self.canvas.mpl_disconnect(self.reset_cursor_id) self.set_cursor_id = None self.reset_cursor_id = None if self.selector is not None: self.selector.disconnect_events() self.selector = None def toggle_selection(self): """Activate selection mode""" if self.canvas is None: return self.disconnect() key = next((key for key, a in self._actions.items() if a.isChecked()), None) if key is None or key == self._action_clicked: self._action_clicked = None if key is not None: self._actions[key].setChecked(False) else: if self.wand_action.isChecked() and self.wand_type == 'color': self.selector = PointOrRectangleSelector( self.ax, self.on_rect_select, rectprops=dict(fc='none'), lineprops=dict(c='none'), useblit=True) elif self.select_action.isChecked() and self.select_type == 'poly': self.selector = mwid.LassoSelector( self.ax, self.on_poly_select) else: self.selector = PointOrRectangleSelector( self.ax, self.on_rect_select, useblit=True) self.set_cursor_id = self.canvas.mpl_connect( 'axes_enter_event', self._on_axes_enter) self.reset_cursor_id = self.canvas.mpl_connect( 'axes_leave_event', self._on_axes_leave) self._action_clicked = next(key for key, a in self._actions.items() if a.isChecked()) self.toolbar.set_message(self.toolbar.mode) def enable_or_disable_widgets(self, b): super(SelectionToolbar, self).enable_or_disable_widgets(b) if not b: for w in [self.clear_select_action, self.invert_select_action, self.expand_select_action]: w.setEnabled(self.should_be_enabled(w)) if self._actions and not self.select_action.isEnabled(): for a in self._actions.values(): if a.isChecked(): a.setChecked(False) self.toggle_selection() def refresh(self): super(SelectionToolbar, self).refresh() combo = self.combo if self.straditizer is None: combo.clear() else: if not combo.count(): combo.addItem('Straditizer') if self.straditizer.data_reader is not None: if not any(combo.itemText(i) == 'Reader' for i in range(combo.count())): combo.addItem('Reader') combo.addItem('Reader - Greyscale') else: for i in range(combo.count()): if combo.itemText(i).startswith('Reader'): combo.removeItem(i) def _on_axes_enter(self, event): ax = self.ax if ax is None: return if (event.inaxes is ax and self.toolbar._active == '' and self.selector is not None): if self._lastCursor != cursors.SELECT_REGION: self.toolbar.set_cursor(cursors.SELECT_REGION) self._lastCursor = cursors.SELECT_REGION def _on_axes_leave(self, event): ax = self.ax if ax is None: return if (event.inaxes is ax and self.toolbar._active == '' and self.selector is not None): if self._lastCursor != cursors.POINTER: self.toolbar.set_cursor(cursors.POINTER) self._lastCursor = cursors.POINTER def end_selection(self): """Finish the selection and disconnect everything""" if getattr(self, '_pattern_selection', None) is not None: self._pattern_selection.close() del self._pattern_selection self._selecting = False self._action_clicked = None self.toggle_selection() self.auto_expand = False self._labels = None self._rect_callbacks.clear() self._poly_callbacks.clear() self._wand_actions['color_select'].setEnabled(True) def get_xy_slice(self, lastx, lasty, x, y): """Transform x- and y-coordinates to :class:`slice` objects Parameters ---------- lastx: int The initial x-coordinate lasty: int The initial y-coordinate x: int The final x-coordinate y: int The final y-coordinate Returns ------- slice The ``slice(lastx, x)`` slice The ``slice(lasty, y)``""" all_x = np.floor(np.sort([lastx, x])).astype(int) all_y = np.floor(np.sort([lasty, y])).astype(int) extent = getattr(self.data_obj, 'extent', None) if extent is not None: all_x -= np.ceil(extent[0]).astype(int) all_y -= np.ceil(min(extent[2:])).astype(int) if self.wand_action.isChecked() and self.wand_type == 'color': all_x[0] = all_x[1] all_y[0] = all_y[1] all_x[all_x < 0] = 0 all_y[all_y < 0] = 0 all_x[1] += 1 all_y[1] += 1 return slice(*all_x), slice(*all_y) def on_rect_select(self, e0, e1): """Call the :attr:`rect_callbacks` after a rectangle selection Parameters ---------- e0: matplotlib.backend_bases.Event The initial event e1: matplotlib.backend_bases.Event The final event""" slx, sly = self.get_xy_slice(e0.xdata, e0.ydata, e1.xdata, e1.ydata) for func in self.rect_callbacks: func(slx, sly) def select_rect(self, slx, sly): """Select the data defined by a rectangle Parameters ---------- slx: slice The x-slice of the rectangle sly: slice The y-slice of the rectangle See Also -------- rect_callbacks""" obj = self.data_obj if obj._selection_arr is None: arr = self.labels rgba = obj.image_array() if hasattr(obj, 'image_array') else None self.start_selection(arr, rgba=rgba) expand = False if self.select_action.isChecked(): arr = self._select_rectangle(slx, sly) expand = True elif self.wand_type == 'labels': arr = self._select_labels(slx, sly) elif self.wand_type == 'rows': arr = self._select_rows(slx, sly) elif self.wand_type == 'cols': arr = self._select_cols(slx, sly) else: arr = self._select_colors(slx, sly) expand = True if arr is not None: obj._selection_arr = arr obj._select_img.set_array(arr) obj._update_magni_img() if expand and self.auto_expand: self.expand_selection() else: self.canvas.draw() def on_poly_select(self, points): """Call the :attr:`poly_callbacks` after a polygon selection Parameters ---------- e0: matplotlib.backend_bases.Event The initial event e1: matplotlib.backend_bases.Event The final event""" for func in self.poly_callbacks: func(points) def select_poly(self, points): """Select the data defined by a polygon Parameters ---------- points: np.ndarray of shape (N, 2) The x- and y-coordinates of the vertices of the polygon See Also -------- poly_callbacks""" obj = self.data_obj if obj._selection_arr is None: rgba = obj.image_array() if hasattr(obj, 'image_array') else None self.start_selection(self.labels, rgba=rgba) arr = self.labels mpath = mplp.Path(points) x = np.arange(obj._selection_arr.shape[1], dtype=int) y = np.arange(obj._selection_arr.shape[0], dtype=int) extent = getattr(obj, 'extent', None) if extent is not None: x += np.ceil(extent[0]).astype(int) y += np.ceil(min(extent[2:])).astype(int) pointsx, pointsy = np.array(points).T x0, x1 = x.searchsorted([pointsx.min(), pointsx.max()]) y0, y1 = y.searchsorted([pointsy.min(), pointsy.max()]) X, Y = np.meshgrid(x[x0:x1], y[y0:y1]) points = np.array((X.flatten(), Y.flatten())).T mask = np.zeros_like(obj._selection_arr, dtype=bool) mask[y0:y1, x0:x1] = ( mpath.contains_points(points).reshape(X.shape) & obj._selection_arr[y0:y1, x0:x1].astype(bool)) if self.remove_select_action.isChecked(): arr[mask] = -1 else: if self.new_select_action.isChecked(): arr = obj._orig_selection_arr.copy() obj._select_img.set_cmap(obj._select_cmap) obj._select_img.set_norm(obj._select_norm) arr[mask] = arr.max() + 1 obj._selection_arr = arr obj._select_img.set_array(arr) obj._update_magni_img() if self.auto_expand: self.expand_selection() else: self.canvas.draw() def _select_rectangle(self, slx, sly): """Select a rectangle within the array""" obj = self.data_obj arr = self.labels data_mask = obj._selection_arr.astype(bool) if self.remove_select_action.isChecked(): arr[sly, slx][data_mask[sly, slx]] = -1 else: if self.new_select_action.isChecked(): arr = obj._orig_selection_arr.copy() obj._select_img.set_cmap(obj._select_cmap) obj._select_img.set_norm(obj._select_norm) arr[sly, slx][data_mask[sly, slx]] = arr.max() + 1 return arr def _select_labels(self, slx, sly): """Select the unique labels in the array""" obj = self.data_obj arr = self.labels data_mask = obj._selection_arr.astype(bool) current_selected = obj.selected_labels new_selected = np.unique( arr[sly, slx][data_mask[sly, slx]]) valid_labels = np.unique( obj._orig_selection_arr[sly, slx][data_mask[sly, slx]]) valid_labels = valid_labels[valid_labels > 0] if not len(valid_labels): return if new_selected[0] == -1 or new_selected[-1] > obj._select_nlabels: mask = np.isin(obj._orig_selection_arr, valid_labels) current_selected = np.unique( np.r_[current_selected, obj._orig_selection_arr[sly, slx][ arr[sly, slx] > obj._select_nlabels]]) arr[mask] = obj._orig_selection_arr[mask] curr = set(current_selected) valid = set(valid_labels) if self.remove_select_action.isChecked(): new = curr - valid elif self.add_select_action.isChecked(): new = curr | valid else: new = valid arr = obj._orig_selection_arr.copy() obj.select_labels(np.array(sorted(new))) return arr def _select_rows(self, slx, sly): """Select the pixel rows defined by `sly` Parameters ---------- slx: slice The x-slice (is ignored) sly: slice The y-slice defining the rows to select""" obj = self.data_obj arr = self.labels rows = np.arange(arr.shape[0])[sly] if self.remove_select_action.isChecked(): arr[rows, :] = np.where(arr[rows, :], -1, 0) else: if self.new_select_action.isChecked(): arr = obj._orig_selection_arr.copy() obj._select_img.set_cmap(obj._select_cmap) obj._select_img.set_norm(obj._select_norm) arr[rows, :] = np.where(arr[rows, :], arr.max() + 1, 0) return arr def _select_cols(self, slx, sly): """Select the pixel columns defined by `slx` Parameters ---------- slx: slice The x-slice defining the columns to select sly: slice The y-slice (is ignored)""" obj = self.data_obj arr = self.labels cols = np.arange(arr.shape[1])[slx] if self.remove_select_action.isChecked(): arr[:, cols] = np.where(arr[:, cols], -1, 0) else: if self.new_select_action.isChecked(): arr = obj._orig_selection_arr.copy() obj._select_img.set_cmap(obj._select_cmap) obj._select_img.set_norm(obj._select_norm) arr[:, cols] = np.where(arr[:, cols], arr.max() + 1, 0) return arr def _select_colors(self, slx, sly): """Select the array based on the colors""" if self.cb_use_alpha.isChecked(): rgba = self._rgba n = 4 else: rgba = self._rgba[..., :-1] n = 3 rgba = rgba.astype(int) # get the unique colors colors = list( map(np.array, set(map(tuple, rgba[sly, slx].reshape((-1, n)))))) obj = self.data_obj arr = self.labels mask = np.zeros_like(arr, dtype=bool) max_dist = self.distance_slider.value() data_mask = obj._selection_arr.astype(bool) for c in colors: mask[np.all(np.abs(rgba - c.reshape((1, 1, -1))) <= max_dist, axis=-1)] = True if not self.cb_whole_fig.isChecked(): import skimage.morphology as skim all_labels = skim.label(mask, 8, return_num=False) selected_labels = np.unique(all_labels[sly, slx]) mask[~np.isin(all_labels, selected_labels)] = False if self.remove_select_action.isChecked(): arr[mask & data_mask] = -1 else: if self.new_select_action.isChecked(): arr = obj._orig_selection_arr.copy() obj._select_img.set_cmap(obj._select_cmap) obj._select_img.set_norm(obj._select_norm) arr[mask & data_mask] = arr.max() + 1 return arr def _remove_selected_labels(self): self.data_obj.remove_selected_labels(disable=True) def _disable_selection(self): return self.data_obj.disable_label_selection() def start_selection(self, arr=None, rgba=None, rect_callbacks=None, poly_callbacks=None, apply_funcs=(), cancel_funcs=(), remove_on_apply=True): """Start the selection in the current :attr:`data_obj` Parameters ---------- arr: np.ndarray The labeled selection array that is used. If specified, the :meth:`~straditize.label_selection.enable_label_selection` method is called of the :attr:`data_obj` with the given `arr`. If this parameter is ``None``, then we expect that this method has already been called rgba: np.ndarray The RGBA image that shall be used for the color selection (see the :meth:`set_color_wand_mode`) rect_callbacks: list A list of callbacks that shall be called after a rectangle selection has been made by the user (see :attr:`rect_callbacks`) poly_callbacks: list A list of callbacks that shall be called after a polygon selection has been made by the user (see :attr:`poly_callbacks`) apply_funcs: list A list of callables that shall be connected to the :attr:`~straditize.widgets.StraditizerWidgets.apply_button` cancel_funcs: list A list of callables that shall be connected to the :attr:`~straditize.widgets.StraditizerWidgets.cancel_button` remove_on_apply: bool If True and the :attr:`~straditize.widgets.StraditizerWidgets.apply_button` is clicked, the selected labels will be removed.""" obj = self.data_obj if arr is not None: obj.enable_label_selection( arr, arr.max(), set_picker=False, zorder=obj.plot_im.zorder + 0.1, extent=obj.plot_im.get_extent()) self._selecting = True self._rgba = rgba if rgba is None: self.set_label_wand_mode() self._wand_actions['color_select'].setEnabled(False) else: self._wand_actions['color_select'].setEnabled(True) self.connect2apply( (self._remove_selected_labels if remove_on_apply else self._disable_selection), obj.remove_small_selection_ellipses, obj.draw_figure, self.end_selection, *apply_funcs) self.connect2cancel(self._disable_selection, obj.remove_small_selection_ellipses, obj.draw_figure, self.end_selection, *cancel_funcs) if self.should_be_enabled(self._appearance_actions['alpha']): self.update_alpha(self.sl_alpha.value()) for w in chain(self._actions.values(), self._appearance_actions.values()): w.setEnabled(self.should_be_enabled(w)) if remove_on_apply: self.straditizer_widgets.apply_button.setText('Remove') if rect_callbacks is not None: self._rect_callbacks = rect_callbacks[:] if poly_callbacks is not None: self._poly_callbacks = poly_callbacks[:] del obj
def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption shell: IPython.core.interactiveshell.InteractiveShell The shell that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) shell = kwargs.pop('shell', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.shell = shell # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.run_button = QToolButton(parent=self) self.keys_button = QPushButton('Formatoption keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addStretch(0) for w in [ self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb ]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project)
class StraditizerWidgets(QWidget, DockMixin): """A widget that contains widgets to control the straditization in a GUI This widget is the basis of the straditize GUI and implemented as a plugin into the psyplot gui. The open straditizers are handled in the :attr:`_straditizer` attribute. The central parts of this widget are - The combobox to manage the open straditizers - The QTreeWidget in the :attr:`tree` attribute that contains all the controls to interface the straditizer - the tutorial area - the :guilabel:`Apply` and :guilabel:`Cancel` button""" #: Boolean that is True if all dialogs should be answered with `Yes` always_yes = False #: The QTreeWidget that contains the different widgets for the digitization tree = None #: The apply button apply_button = None #: The cancel button cancel_button = None #: The button to edit the straditizer attributes attrs_button = None #: The button to start a tutorial tutorial_button = None #: An :class:`InfoButton` to display the docs info_button = None #: A QComboBox to select the current straditizer stradi_combo = None #: A button to open a new straditizer btn_open_stradi = None #: A button to close the current straditizer btn_close_stradi = None #: A button to reload the last autosaved state btn_reload_autosaved = None #: The :class:`straditize.widgets.progress_widget.ProgressWidget` to #: display the progress of the straditization progress_widget = None #: The :class:`straditize.widgets.data.DigitizingControl` to interface #: the :straditize.straditizer.Straditizer.data_reader` digitizer = None #: The :class:`straditize.widgets.colnames.ColumnNamesManager` to interface #: the :straditize.straditizer.Straditizer.colnames_reader` colnames_manager = None #: The :class:`straditize.widgets.axes_translations.AxesTranslations` to #: handle the y- and x-axis conversions axes_translations = None #: The :class:`straditize.widgets.image_correction.ImageRescaler` class to #: rescale the image image_rescaler = None #: The :class:`straditize.widgets.image_correction.ImageRotator` class to #: rotate the image image_rotator = None #: The :class:`straditize.widgets.plots.PlotControl` to display additional #: information on the diagram plot_control = None #: The :class:`straditize.widgets.marker_control.MarkerControl` to modify #: the appearance of the :class:`~straditize.straditizer.Straditizer.marks` #: of the current straditizer marker_control = None #: The :class:`straditize.widgets.selection_toolbar.SelectionToolbar` to #: select features in the stratigraphic diagram selection_toolbar = None #: The :class:`straditize.straditizer.Straditizer` instance straditizer = None #: open straditizers _straditizers = [] #: The :class:`straditize.widgets.tutorial.Tutorial` class tutorial = None dock_position = Qt.LeftDockWidgetArea #: Auto-saved straditizers autosaved = [] hidden = True title = 'Stratigraphic diagram digitization' window_layout_action = None open_external = QtCore.pyqtSignal(list) def __init__(self, *args, **kwargs): from straditize.widgets.menu_actions import StraditizerMenuActions from straditize.widgets.progress_widget import ProgressWidget from straditize.widgets.data import DigitizingControl from straditize.widgets.selection_toolbar import SelectionToolbar from straditize.widgets.marker_control import MarkerControl from straditize.widgets.plots import PlotControl from straditize.widgets.axes_translations import AxesTranslations from straditize.widgets.image_correction import (ImageRotator, ImageRescaler) from straditize.widgets.colnames import ColumnNamesManager self._straditizers = [] super(StraditizerWidgets, self).__init__(*args, **kwargs) self.tree = QTreeWidget(parent=self) self.tree.setSelectionMode(QTreeWidget.NoSelection) self.refresh_button = QToolButton(self) self.refresh_button.setIcon(QIcon(get_psy_icon('refresh.png'))) self.refresh_button.setToolTip('Refresh from the straditizer') self.apply_button = EnableButton('Apply', parent=self) self.cancel_button = EnableButton('Cancel', parent=self) self.attrs_button = QPushButton('Attributes', parent=self) self.tutorial_button = QPushButton('Tutorial', parent=self) self.tutorial_button.setCheckable(True) self.error_msg = PyErrorMessage(self) self.stradi_combo = QComboBox() self.btn_open_stradi = QToolButton() self.btn_open_stradi.setIcon(QIcon(get_psy_icon('run_arrow.png'))) self.btn_close_stradi = QToolButton() self.btn_close_stradi.setIcon(QIcon(get_psy_icon('invalid.png'))) self.btn_reload_autosaved = QPushButton("Reload") self.btn_reload_autosaved.setToolTip( "Close the straditizer and reload the last autosaved project") # --------------------------------------------------------------------- # --------------------------- Tree widgets ---------------------------- # --------------------------------------------------------------------- self.tree.setHeaderLabels(['', '']) self.tree.setColumnCount(2) self.progress_item = QTreeWidgetItem(0) self.progress_item.setText(0, 'ToDo list') self.progress_widget = ProgressWidget(self, self.progress_item) self.menu_actions_item = QTreeWidgetItem(0) self.menu_actions_item.setText(0, 'Images import/export') self.tree.addTopLevelItem(self.menu_actions_item) self.menu_actions = StraditizerMenuActions(self) self.digitizer_item = item = QTreeWidgetItem(0) item.setText(0, 'Digitization control') self.digitizer = DigitizingControl(self, item) self.col_names_item = item = QTreeWidgetItem(0) item.setText(0, 'Column names') self.colnames_manager = ColumnNamesManager(self, item) self.add_info_button(item, 'column_names.rst') self.axes_translations_item = item = QTreeWidgetItem(0) item.setText(0, 'Axes translations') self.axes_translations = AxesTranslations(self, item) self.image_transform_item = item = QTreeWidgetItem(0) item.setText(0, 'Transform source image') self.image_rescaler = ImageRescaler(self, item) self.image_rotator_item = item = QTreeWidgetItem(0) item.setText(0, 'Rotate image') self.image_rotator = ImageRotator(self) self.image_transform_item.addChild(item) self.image_rotator.setup_children(item) self.plot_control_item = item = QTreeWidgetItem(0) item.setText(0, 'Plot control') self.plot_control = PlotControl(self, item) self.add_info_button(item, 'plot_control.rst') self.marker_control_item = item = QTreeWidgetItem(0) item.setText(0, 'Marker control') self.marker_control = MarkerControl(self, item) self.add_info_button(item, 'marker_control.rst') # --------------------------------------------------------------------- # ----------------------------- Toolbars ------------------------------ # --------------------------------------------------------------------- self.selection_toolbar = SelectionToolbar(self, 'Selection toolbar') # --------------------------------------------------------------------- # ----------------------------- InfoButton ---------------------------- # --------------------------------------------------------------------- self.info_button = InfoButton(self, get_doc_file('straditize.rst')) # --------------------------------------------------------------------- # --------------------------- Layouts --------------------------------- # --------------------------------------------------------------------- stradi_box = QHBoxLayout() stradi_box.addWidget(self.stradi_combo, 1) stradi_box.addWidget(self.btn_open_stradi) stradi_box.addWidget(self.btn_close_stradi) attrs_box = QHBoxLayout() attrs_box.addWidget(self.attrs_button) attrs_box.addStretch(0) attrs_box.addWidget(self.tutorial_button) btn_box = QHBoxLayout() btn_box.addWidget(self.refresh_button) btn_box.addWidget(self.info_button) btn_box.addStretch(0) btn_box.addWidget(self.apply_button) btn_box.addWidget(self.cancel_button) reload_box = QHBoxLayout() reload_box.addWidget(self.btn_reload_autosaved) reload_box.addStretch(0) vbox = QVBoxLayout() vbox.addLayout(stradi_box) vbox.addWidget(self.tree) vbox.addLayout(attrs_box) vbox.addLayout(btn_box) vbox.addLayout(reload_box) self.setLayout(vbox) self.apply_button.setEnabled(False) self.cancel_button.setEnabled(False) self.tree.expandItem(self.progress_item) self.tree.expandItem(self.digitizer_item) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.stradi_combo.currentIndexChanged.connect(self.set_current_stradi) self.refresh_button.clicked.connect(self.refresh) self.attrs_button.clicked.connect(self.edit_attrs) self.tutorial_button.clicked.connect(self.start_tutorial) self.open_external.connect(self._create_straditizer_from_args) self.btn_open_stradi.clicked.connect( self.menu_actions.open_straditizer) self.btn_close_stradi.clicked.connect(self.close_straditizer) self.btn_reload_autosaved.clicked.connect(self.reload_autosaved) self.refresh() header = self.tree.header() header.setStretchLastSection(False) header.setSectionResizeMode(0, QHeaderView.Stretch) def disable_apply_button(self): """Method that is called when the :attr:`cancel_button` is clicked""" for w in [self.apply_button, self.cancel_button]: try: w.clicked.disconnect() except TypeError: pass w.setEnabled(False) self.apply_button.setText('Apply') self.cancel_button.setText('Cancel') self.refresh_button.setEnabled(True) def switch_to_straditizer_layout(self): """Switch to the straditizer layout This method makes this widget visible and stacks it with the psyplot content widget""" mainwindow = self.dock.parent() mainwindow.figures_tree.hide_plugin() mainwindow.ds_tree.hide_plugin() mainwindow.fmt_widget.hide_plugin() self.show_plugin() mainwindow.tabifyDockWidget(mainwindow.project_content.dock, self.dock) hsize = self.marker_control.sizeHint().width() + 50 self.menu_actions.setup_shortcuts(mainwindow) if with_qt5: mainwindow.resizeDocks([self.dock], [hsize], Qt.Horizontal) self.tree.resizeColumnToContents(0) self.tree.resizeColumnToContents(1) self.info_button.click() def to_dock(self, main, *args, **kwargs): ret = super(StraditizerWidgets, self).to_dock(main, *args, **kwargs) if self.menu_actions.window_layout_action is None: main.window_layouts_menu.addAction(self.window_layout_action) main.callbacks['straditize'] = self.open_external.emit main.addToolBar(self.selection_toolbar) self.dock.toggleViewAction().triggered.connect( self.show_or_hide_toolbar) self.menu_actions.setup_menu_actions(main) self.menu_actions.setup_children(self.menu_actions_item) try: main.open_file_options['Straditize project'] = \ self.create_straditizer_from_args except AttributeError: # psyplot-gui <= 1.1.0 pass return ret def show_or_hide_toolbar(self): """Show or hide the toolbar depending on the visibility of this widget """ self.selection_toolbar.setVisible(self.is_shown) def _create_straditizer_from_args(self, args): """A method that is called when the :attr:`psyplot_gui.main.mainwindow` receives a 'straditize' callback""" self.create_straditizer_from_args(*args) def create_straditizer_from_args(self, fnames, project=None, xlim=None, ylim=None, full=False, reader_type='area'): """Create a straditizer from the given file name This method is called when the :attr:`psyplot_gui.main.mainwindow` receives a 'straditize' callback""" fname = fnames[0] if fname is not None: self.menu_actions.open_straditizer(fname) stradi = self.straditizer if stradi is None: return if xlim is not None: stradi.data_xlim = xlim if ylim is not None: stradi.data_ylim = ylim if xlim is not None or ylim is not None or full: if stradi.data_xlim is None: stradi.data_xlim = [0, np.shape(stradi.image)[1]] if stradi.data_ylim is None: stradi.data_ylim = [0, np.shape(stradi.image)[0]] stradi.init_reader(reader_type) stradi.data_reader.digitize() self.refresh() if not self.is_shown: self.switch_to_straditizer_layout() return fname is not None def start_tutorial(self, state, tutorial_cls=None): """Start or stop the tutorial Parameters ---------- state: bool If False, the tutorial is stopped. Otherwise it is started tutorial_cls: straditize.widgets.tutorial.beginner.Tutorial The tutorial class to use. If None, it will be asked in a QInputDialog""" if self.tutorial is not None or not state: self.tutorial.close() self.tutorial_button.setText('Tutorial') elif state: if tutorial_cls is None: tutorial_cls, ok = QInputDialog.getItem( self, 'Start tutorial', "Select the tutorial type", ["Beginner", "Advanced (Hoya del Castillo)"], editable=False) if not ok: self.tutorial_button.blockSignals(True) self.tutorial_button.setChecked(False) self.tutorial_button.blockSignals(False) return if tutorial_cls == 'Beginner': from straditize.widgets.tutorial import Tutorial else: from straditize.widgets.tutorial import ( HoyaDelCastilloTutorial as Tutorial) else: Tutorial = tutorial_cls self.tutorial = Tutorial(self) self.tutorial_button.setText('Stop tutorial') def edit_attrs(self): """Edit the attributes of the current straditizer This creates a new dataframe editor to edit the :attr:`straditize.straditizer.Straditizer.attrs` meta informations""" def add_attr(key): model = editor.table.model() n = len(attrs) model.insertRow(n) model.setData(model.index(n, 0), key) model.setData(model.index(n, 1), '', change_type=six.text_type) from psyplot_gui.main import mainwindow from straditize.straditizer import common_attributes attrs = self.straditizer.attrs editor = mainwindow.new_data_frame_editor(attrs, 'Straditizer attributes') editor.table.resizeColumnToContents(1) editor.table.horizontalHeader().setVisible(False) editor.table.frozen_table_view.horizontalHeader().setVisible(False) combo = QComboBox() combo.addItems([''] + common_attributes) combo.currentTextChanged.connect(add_attr) hbox = QHBoxLayout() hbox.addWidget(QLabel('Common attributes:')) hbox.addWidget(combo) hbox.addStretch(0) editor.layout().insertLayout(1, hbox) return editor, combo def refresh(self): """Refresh from the straditizer""" for i, stradi in enumerate(self._straditizers): self.stradi_combo.setItemText( i, self.get_attr(stradi, 'project_file') or self.get_attr(stradi, 'image_file') or '') # toggle visibility of close button and attributes button enable = self.straditizer is not None self.btn_close_stradi.setVisible(enable) self.attrs_button.setEnabled(enable) # refresh controls self.menu_actions.refresh() self.progress_widget.refresh() self.digitizer.refresh() self.selection_toolbar.refresh() self.plot_control.refresh() self.marker_control.refresh() self.axes_translations.refresh() if self.tutorial is not None: self.tutorial.refresh() self.image_rotator.refresh() self.image_rescaler.refresh() self.colnames_manager.refresh() self.btn_reload_autosaved.setEnabled(bool(self.autosaved)) def get_attr(self, stradi, attr): try: return stradi.get_attr(attr) except KeyError: pass docstrings.delete_params('InfoButton.parameters', 'parent') @docstrings.get_sectionsf('StraditizerWidgets.add_info_button') @docstrings.with_indent(8) def add_info_button(self, child, fname=None, rst=None, name=None, connections=[]): """Add an infobutton to the :attr:`tree` widget Parameters ---------- child: QTreeWidgetItem The item to which to add the infobutton %(InfoButton.parameters.no_parent)s connections: list of QPushButtons Buttons that should be clicked when the info button is clicked""" button = InfoButton(self, fname=fname, rst=rst, name=name) self.tree.setItemWidget(child, 1, button) for btn in connections: btn.clicked.connect(button.click) return button def raise_figures(self): """Raise the figures of the current straditizer in the GUI""" from psyplot_gui.main import mainwindow if mainwindow.figures and self.straditizer: dock = self.straditizer.ax.figure.canvas.manager.window dock.widget().show_plugin() dock.raise_() if self.straditizer.magni is not None: dock = self.straditizer.magni.ax.figure.canvas.manager.window dock.widget().show_plugin() dock.raise_() def set_current_stradi(self, i): """Set the i-th straditizer to the current one""" if not self._straditizers: return self.straditizer = self._straditizers[i] self.menu_actions.set_stradi_in_console() block = self.stradi_combo.blockSignals(True) self.stradi_combo.setCurrentIndex(i) self.stradi_combo.blockSignals(block) self.raise_figures() self.refresh() self.autosaved.clear() def _close_stradi(self, stradi): """Close the given straditizer and all it's figures""" is_current = stradi is self.straditizer if is_current: self.selection_toolbar.disconnect() stradi.close() try: i = self._straditizers.index(stradi) except ValueError: pass else: del self._straditizers[i] self.stradi_combo.removeItem(i) if is_current and self._straditizers: self.stradi_combo.setCurrentIndex(0) elif not self._straditizers: self.straditizer = None self.refresh() self.digitizer.digitize_item.takeChildren() self.digitizer.btn_digitize.setChecked(False) self.digitizer.btn_digitize.setCheckable(False) self.digitizer.toggle_txt_tolerance('') def close_straditizer(self): """Close the current straditizer""" self._close_stradi(self.straditizer) def close_all_straditizers(self): """Close all straditizers""" self.selection_toolbar.disconnect() for stradi in self._straditizers: stradi.close() self._straditizers.clear() self.straditizer = None self.stradi_combo.clear() self.digitizer.digitize_item.takeChildren() self.digitizer.btn_digitize.setChecked(False) self.digitizer.btn_digitize.setCheckable(False) self.digitizer.toggle_txt_tolerance('') self.refresh() def add_straditizer(self, stradi): """Add a straditizer to the list of open straditizers""" if stradi and stradi not in self._straditizers: self._straditizers.append(stradi) self.stradi_combo.addItem(' ') self.set_current_stradi(len(self._straditizers) - 1) def reset_control(self): """Reset the GUI of straditize""" if getattr(self.selection_toolbar, '_pattern_selection', None): self.selection_toolbar._pattern_selection.remove_plugin() del self.selection_toolbar._pattern_selection if getattr(self.digitizer, '_samples_editor', None): self.digitizer._close_samples_fig() tb = self.selection_toolbar tb.set_label_wand_mode() tb.set_rect_select_mode() tb.new_select_action.setChecked(True) tb.select_action.setChecked(False) tb.wand_action.setChecked(False) self.disable_apply_button() self.close_all_straditizers() self.colnames_manager.reset_control() def autosave(self): """Autosave the current straditizer""" self.autosaved = [self.straditizer.to_dataset().copy(True)] + \ self.autosaved[:4] def reload_autosaved(self): """Reload the autosaved straditizer and close the old one""" from straditize.straditizer import Straditizer if not self.autosaved: return answer = QMessageBox.question( self, 'Reload autosave', 'Shall I reload the last autosaved stage? This will close the ' 'current figures.') if answer == QMessageBox.Yes: self.close_straditizer() stradi = Straditizer.from_dataset(self.autosaved.pop(0)) self.menu_actions.finish_loading(stradi)
class FormatoptionWidget(QWidget, DockMixin): """ Widget to update the formatoptions of the current project This widget, mainly made out of a combobox for the formatoption group, a combobox for the formatoption, and a one-line text editor, is designed for updating the selected formatoptions for the current subproject. The widget is connected to the :attr:`psyplot.project.Project.oncpchange` signal and refills the comboboxes if the current subproject changes. The one-line text editor accepts python code that will be executed in side the given `shell`. """ no_fmtos_update = _temp_bool_prop('no_fmtos_update', """update the fmto combo box or not""") #: The combobox for the formatoption groups group_combo = None #: The combobox for the formatoptions fmt_combo = None #: The help_explorer to display the documentation of the formatoptions help_explorer = None #: The shell to execute the update of the formatoptions in the current #: project shell = None def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption shell: IPython.core.interactiveshell.InteractiveShell The shell that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) shell = kwargs.pop('shell', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.shell = shell # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.run_button = QToolButton(parent=self) self.keys_button = QPushButton('Formatoption keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addStretch(0) for w in [ self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb ]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) def fill_combos_from_project(self, project): """Fill :attr:`group_combo` and :attr:`fmt_combo` from a project Parameters ---------- project: psyplot.project.Project The project to use""" current_text = self.group_combo.currentText() with self.no_fmtos_update: self.group_combo.clear() if project is None or project.is_main or not len(project.plotters): self.fmt_combo.clear() self.groups = [] self.fmtos = [] self.line_edit.setEnabled(False) return self.line_edit.setEnabled(True) # get dimensions coords = sorted(project.coords_intersect) coords_name = [COORDSGROUP] if coords else [] coords_verbose = ['Dimensions'] if coords else [] coords = [coords] if coords else [] # get formatoptions and group them alphabetically grouped_fmts = defaultdict(list) for fmto in project._fmtos: grouped_fmts[fmto.group].append(fmto) for val in six.itervalues(grouped_fmts): val.sort(key=self.get_name) grouped_fmts = OrderedDict( sorted(six.iteritems(grouped_fmts), key=lambda t: psyp.groups.get(t[0], t[0]))) fmt_groups = list(grouped_fmts.keys()) # save original names self.groups = coords_name + [ALLGROUP] + fmt_groups # save verbose group names (which are used in the combo box) self.groupnames = coords_verbose + ['All formatoptions'] + list( map(lambda s: psyp.groups.get(s, s), fmt_groups)) # save formatoptions fmtos = list(grouped_fmts.values()) self.fmtos = coords + [sorted(chain(*fmtos), key=self.get_name) ] + fmtos self.group_combo.addItems(self.groupnames) ind = self.group_combo.findText(current_text) self.group_combo.setCurrentIndex(ind if ind >= 0 else 0) self.fill_fmt_combo(self.group_combo.currentIndex()) def get_name(self, fmto): """Get the name of a :class:`psyplot.plotter.Formatoption` instance""" if isinstance(fmto, six.string_types): return fmto return '%s (%s)' % (fmto.name, fmto.key) if fmto.name else fmto.key def fill_fmt_combo(self, i): """Fill the :attr:`fmt_combo` combobox based on the current group name """ if not self.no_fmtos_update: with self.no_fmtos_update: current_text = self.fmt_combo.currentText() self.fmt_combo.clear() self.fmt_combo.addItems(list(map(self.get_name, self.fmtos[i]))) ind = self.fmt_combo.findText(current_text) self.fmt_combo.setCurrentIndex(ind if ind >= 0 else 0) self.show_fmt_info(self.fmt_combo.currentIndex()) def show_fmt_info(self, i): """Show the documentation of the formatoption in the help explorer """ group_ind = self.group_combo.currentIndex() if (not self.no_fmtos_update and self.groups[group_ind] != COORDSGROUP): fmto = self.fmtos[self.group_combo.currentIndex()][i] fmto.plotter.show_docs( fmto.key, include_links=self.include_links_cb.isChecked()) def run_code(self): """Run the update of the project inside the :attr:`shell`""" text = str(self.line_edit.text()) if not text or not self.fmtos: return group_ind = self.group_combo.currentIndex() if self.groups[group_ind] == COORDSGROUP: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()] param = 'dims' else: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()].key param = 'fmt' e = ExecutionResult() self.shell.run_code( "psy.gcp().update(%s={'%s': %s})" % (param, key, text), e) e.raise_error() def show_all_fmt_info(self, what): """Show the keys, summaries or docs of the formatoptions Calling this function let's the help browser show the documentation etc. of all docs or only the selected group determined by the state of the :attr:`grouped_cb` and :attr:`all_groups_cb` checkboxes Parameters ---------- what: {'keys', 'summaries', 'docs'} Determines what to show""" if not self.fmtos: return if self.all_groups_cb.isChecked(): fmtos = list( chain(*(fmto_group for i, fmto_group in enumerate(self.fmtos) if not self.groups[i] in [ALLGROUP, COORDSGROUP]))) else: if self.groups[self.group_combo.currentIndex()] == COORDSGROUP: return fmtos = self.fmtos[self.group_combo.currentIndex()] plotter = fmtos[0].plotter getattr(plotter, 'show_' + what)([fmto.key for fmto in fmtos], grouped=self.grouped_cb.isChecked(), include_links=self.include_links_cb.isChecked())
def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode(QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [ self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb ]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc)
class MarkerControl(StraditizerControlBase, QWidget): """Widget to control the appearance of the marks This widget controls the appearance of the :class:`straditize.cross_mark.CrossMarks` instances in the :attr:`~straditize.straditizer.Straditizer.marks` attribute of the :attr:`~straditize.widgets.StraditizerControlBase.straditizer`""" _toolbar = None @property def marks(self): """The :class:`~straditize.cross_mark.CrossMarks` of the straditizer """ return chain(self.straditizer.marks, self.straditizer.magni_marks) @docstrings.dedent def __init__(self, straditizer_widgets, item, *args, **kwargs): """ Parameters ---------- %(StraditizerControlBase.init_straditizercontrol.parameters)s """ super(MarkerControl, self).__init__(*args, **kwargs) self.init_straditizercontrol(straditizer_widgets, item) vbox = QVBoxLayout() # auto hide button box_hide = QGridLayout() self.cb_auto_hide = QCheckBox('Auto-hide') self.cb_show_connected = QCheckBox('Show additionals') self.cb_drag_hline = QCheckBox('Drag in y-direction') self.cb_drag_vline = QCheckBox('Drag in x-direction') self.cb_selectable_hline = QCheckBox('Horizontal lines selectable') self.cb_selectable_vline = QCheckBox('Vertical lines selectable') self.cb_show_hlines = QCheckBox('Show horizontal lines') self.cb_show_vlines = QCheckBox('Show vertical lines') box_hide.addWidget(self.cb_auto_hide, 0, 0) box_hide.addWidget(self.cb_show_connected, 0, 1) box_hide.addWidget(self.cb_show_vlines, 1, 0) box_hide.addWidget(self.cb_show_hlines, 1, 1) box_hide.addWidget(self.cb_drag_hline, 2, 0) box_hide.addWidget(self.cb_drag_vline, 2, 1) box_hide.addWidget(self.cb_selectable_hline, 3, 0) box_hide.addWidget(self.cb_selectable_vline, 3, 1) vbox.addLayout(box_hide) style_box = QGridLayout() style_box.addWidget(QLabel('Unselected:'), 0, 1) selected_label = QLabel('Selected:') style_box.addWidget(selected_label, 0, 2) max_width = selected_label.sizeHint().width() # line color self.lbl_color_select = ColorLabel() self.lbl_color_unselect = ColorLabel() self.lbl_color_select.setMaximumWidth(max_width) self.lbl_color_unselect.setMaximumWidth(max_width) style_box.addWidget(QLabel('Line color:'), 1, 0) style_box.addWidget(self.lbl_color_unselect, 1, 1) style_box.addWidget(self.lbl_color_select, 1, 2) # line width self.txt_line_width = QLineEdit() self.txt_line_width_select = QLineEdit() validator = QDoubleValidator() validator.setBottom(0) self.txt_line_width.setValidator(validator) self.txt_line_width_select.setValidator(validator) style_box.addWidget(QLabel('Line width:'), 2, 0) style_box.addWidget(self.txt_line_width, 2, 1) style_box.addWidget(self.txt_line_width_select, 2, 2) vbox.addLayout(style_box) # line style hbox_line_style = QHBoxLayout() hbox_line_style.addWidget(QLabel('Line style')) self.combo_line_style = QComboBox() hbox_line_style.addWidget(self.combo_line_style) vbox.addLayout(hbox_line_style) self.fill_linestyles() self.combo_line_style.setSizeAdjustPolicy(QComboBox.AdjustToContents) # marker style hbox_marker_style = QHBoxLayout() hbox_marker_style.addWidget(QLabel('Marker size')) self.txt_marker_size = QLineEdit() self.txt_marker_size.setMinimumWidth(40) self.txt_marker_size.setText(str(mpl.rcParams['lines.markersize'])) validator = QDoubleValidator() validator.setBottom(0) self.txt_marker_size.setValidator(validator) hbox_marker_style.addWidget(self.txt_marker_size) hbox_marker_style.addWidget(QLabel('Marker style')) self.combo_marker_style = QComboBox() hbox_marker_style.addWidget(self.combo_marker_style) vbox.addLayout(hbox_marker_style) self.setLayout(vbox) self.widgets2disable = [ self.lbl_color_select, self.lbl_color_unselect, self.cb_auto_hide, self.txt_line_width, self.txt_line_width_select, self.combo_line_style, self.cb_show_connected, self.txt_marker_size, self.combo_marker_style, self.cb_drag_vline, self.cb_drag_hline, self.cb_selectable_vline, self.cb_selectable_hline, self.cb_show_hlines, self.cb_show_vlines ] self.fill_markerstyles() self.tb_actions = [] # --------------------------------------------------------------------- # ---------------------------- connections ---------------------------- # --------------------------------------------------------------------- self.lbl_color_select.color_changed.connect(self.change_select_colors) self.lbl_color_unselect.color_changed.connect( self.change_unselect_colors) self.txt_line_width.textChanged.connect(self.change_line_widths) self.txt_line_width_select.textChanged.connect( self.change_selection_line_widths) self.cb_auto_hide.stateChanged.connect(self.change_auto_hide) self.combo_marker_style.currentIndexChanged.connect( self.change_marker_style) self.combo_line_style.currentIndexChanged.connect( self.change_line_style) self.txt_marker_size.textChanged.connect(self.change_marker_size) self.cb_show_connected.stateChanged.connect( self.change_show_connected_artists) self.cb_drag_hline.stateChanged.connect(self.change_hline_draggable) self.cb_drag_vline.stateChanged.connect(self.change_vline_draggable) self.cb_selectable_hline.stateChanged.connect( self.change_hline_selectable) self.cb_selectable_vline.stateChanged.connect( self.change_vline_selectable) self.cb_show_vlines.stateChanged.connect(self.change_show_vlines) self.cb_show_hlines.stateChanged.connect(self.change_show_hlines) def draw_figs(self): """Draw the figures of the :attr:`marks`""" for canvas in {m.fig.canvas for m in self.marks}: canvas.draw_idle() def fill_linestyles(self): """Fill the :attr:`combo_line_style` combobox""" self.line_styles = [('-', 'solid'), ('--', 'dashed'), ('-.', 'dashdot'), (':', 'dotted'), ('None', None, '', ' ')] self.combo_line_style.addItems([t[0] for t in self.line_styles]) def fill_markerstyles(self): """Fill the :attr:`combo_marker_style` combobox""" self.marker_styles = [('point', (".", )), ('pixel', (",", )), ('circle', ("o", )), ('triangle down', ("v", "1")), ('triangle up', ("^", "2")), ('triangle left', ("<", "3")), ('triangle right', (">", "4")), ('octagon', ("8", )), ('square', ("s", )), ('pentagon', ("p", )), ('plus (filled)', ("P", )), ('star', ("*", )), ('hexagon1', ("h", )), ('hexagon2', ("H", )), ('plus', ("+", )), ('x', ("x", )), ('x (filled)', ("X", )), ('diamond', ("D", )), ('thin diamond', ("d", )), ('vline', ("|", )), ('hline', ("_", )), ('no marker', ('None', None, '', ' '))] self.combo_marker_style.addItems([ '%s (%s)' % (key.capitalize(), ','.join(map(str, filter(None, t)))) for key, t in self.marker_styles ]) def set_marker_item(self, marker): """Switch the :attr:`combo_marker_style` to the given `marker` Parameters ---------- marker: str A matplotlib marker string""" for i, (key, t) in enumerate(self.marker_styles): if marker in t: block = self.combo_marker_style.blockSignals(True) self.combo_marker_style.setCurrentIndex(i) self.combo_marker_style.blockSignals(block) break def set_line_style_item(self, ls): """Switch the :attr:`combo_line_style` to the given linestyle Parameters ---------- ls: str The matplotlib linestyle string """ for i, t in enumerate(self.line_styles): if ls in t: block = self.combo_line_style.blockSignals(True) self.combo_line_style.setCurrentIndex(i) self.combo_line_style.blockSignals(block) break def should_be_enabled(self, w): """Check if a widget `w` should be enabled or disabled Parameters ---------- w: PyQt5.QtWidgets.QWidget The widget to (potentially) enable Returns ------- bool True if the :attr:`straditizer` of this instance has marks. Otherwise False""" return self.straditizer is not None and bool(self.straditizer.marks) def change_select_colors(self, color): """Change the selection color of the marks Change the selection color of the marks to the given color. Parameters ---------- color: PyQt5.QtGui.QColor or a matplotlib color The color to use """ if isinstance(color, QtGui.QColor): color = np.array(color.getRgb()) / 255. for mark in self.marks: key = 'color' if 'color' in mark._select_props else 'c' mark._select_props[key] = color mark._unselect_props[key] = mark.hline.get_c() self.draw_figs() def change_unselect_colors(self, color): """Change the :attr:`straditize.cross_mark.CrossMark.cunselect` color Change the unselection color of the marks to the given color. Parameters ---------- color: PyQt5.QtGui.QColor or a matplotlib color The color to use """ if isinstance(color, QtGui.QColor): color = np.array(color.getRgb()) / 255. for mark in self.marks: key = 'color' if 'color' in mark._unselect_props else 'c' mark._unselect_props[key] = color for l in chain(mark.hlines, mark.vlines, mark.line_connections): l.set_color(color) self.draw_figs() def change_line_widths(self, lw): """Change the linewidth of the marks Parameters ---------- lw: float The line width to use """ lw = float(lw or 0) for mark in self.marks: key = 'lw' if 'lw' in mark._unselect_props else 'linewidth' mark._unselect_props[key] = lw if not mark.auto_hide: for l in chain(mark.hlines, mark.vlines, mark.line_connections): l.set_lw(lw) self.draw_figs() def change_selection_line_widths(self, lw): """Change the linewidth for selected marks Parameters ---------- lw: float The linewidth for selected marks""" lw = float(lw or 0) for mark in self.marks: key = 'lw' if 'lw' in mark._select_props else 'linewidth' mark._select_props[key] = lw def change_auto_hide(self, auto_hide): """Toggle the :attr:`~straditize.cross_mark.CrossMark.auto_hide` This method disables or enables the :attr:`~straditize.cross_mark.CrossMark.auto_hide` of the marks Parameters ---------- auto_hide: bool or PyQt5.QtGui.Qt.Checked or PyQt5.QtGui.Qt.Unchecked The value to use for the auto_hide. :data:`PyQt5.QtGui.Qt.Checked` is equivalent to ``True`` """ if auto_hide is Qt.Checked: auto_hide = True elif auto_hide is Qt.Unchecked: auto_hide = False for mark in self.marks: mark.auto_hide = auto_hide if auto_hide: for l in chain(mark.hlines, mark.vlines, mark.line_connections): l.set_lw(0) else: lw = mark._unselect_props.get( 'lw', mark._unselect_props.get('linewidth')) for l in chain(mark.hlines, mark.vlines, mark.line_connections): l.set_lw(lw) self.draw_figs() def change_show_connected_artists(self, show): """Change the visibility of connected artists Parameters ---------- show: bool The visibility for the :meth:`straditize.cross_mark.CrossMarks.set_connected_artists_visible` method""" if show is Qt.Checked: show = True elif show is Qt.Unchecked: show = False for mark in self.marks: mark.set_connected_artists_visible(show) self.draw_figs() def change_line_style(self, i): """Change the line style of the marks Parameters ---------- i: int The index of the line style in the :attr:`line_styles` attribute to use """ ls = self.line_styles[i][0] for mark in self.marks: for l in chain(mark.hlines, mark.vlines, mark.line_connections): l.set_ls(ls) self.draw_figs() def change_marker_style(self, i): """Change the marker style of the marks Parameters ---------- i: int The index of the marker style in the :attr:`marker_styles` attribute to use """ marker = self.marker_styles[i][1][0] for mark in self.marks: for l in chain(mark.hlines, mark.vlines, mark.line_connections): l.set_marker(marker) self.draw_figs() def change_marker_size(self, markersize): """Change the size of the markers Parameters ---------- markersize: float The size of the marker to use""" markersize = float(markersize or 0) for mark in self.marks: for l in chain(mark.hlines, mark.vlines, mark.line_connections): l.set_markersize(markersize) self.draw_figs() def fill_from_mark(self, mark): """Set the widgets of this :class:`MarkerControl` from a mark This method sets the color labels, combo boxes, check boxes and text edits to match the properties of the given `mark` Parameters ---------- mark: straditize.cross_mark.CrossMark The mark to use the attributes from """ line = mark.hline try: cselect = mark._select_props['c'] except KeyError: try: cselect = mark._select_props['color'] except KeyError: cselect = mark.hline.get_c() lw_key = 'lw' if 'lw' in mark._unselect_props else 'linewidth' self.set_line_style_item(line.get_linestyle()) self.set_marker_item(line.get_marker()) self.txt_line_width.setText(str(mark._unselect_props.get(lw_key, 0))) self.txt_line_width_select.setText( str(mark._select_props.get(lw_key, 0))) self.txt_marker_size.setText(str(line.get_markersize())) self.lbl_color_select._set_color(cselect) self.lbl_color_unselect._set_color(mark.hline.get_c()) self.cb_auto_hide.setChecked(mark.auto_hide) self.cb_show_connected.setChecked(mark.show_connected_artists) try: mark.x except NotImplementedError: self.cb_drag_vline.setEnabled(False) self.cb_show_vlines.setEnabled(False) self.cb_selectable_vline.setEnabled(False) else: self.cb_drag_vline.setEnabled(True) self.cb_show_vlines.setEnabled(True) self.cb_selectable_vline.setEnabled(True) try: mark.y except NotImplementedError: self.cb_drag_hline.setEnabled(False) self.cb_show_hlines.setEnabled(False) self.cb_selectable_hline.setEnabled(False) else: self.cb_drag_hline.setEnabled(True) self.cb_show_hlines.setEnabled(True) self.cb_selectable_hline.setEnabled(True) self.cb_drag_hline.setChecked('h' in mark.draggable) self.cb_drag_vline.setChecked('v' in mark.draggable) self.cb_show_hlines.setChecked(not mark.hide_horizontal) self.cb_show_vlines.setChecked(not mark.hide_vertical) self.cb_selectable_hline.setChecked('h' in mark.selectable) self.cb_selectable_vline.setChecked('v' in mark.selectable) @property def line_props(self): """The properties of the lines as a :class:`dict`""" return { 'ls': self.combo_line_style.currentText(), 'marker': self.marker_styles[self.combo_marker_style.currentIndex()][1][0], 'lw': float(self.txt_line_width.text().strip() or 0), 'markersize': float(self.txt_marker_size.text().strip() or 0), 'c': self.lbl_color_unselect.color.getRgbF(), } @property def select_props(self): """The properties of selected marks as a :class:`dict`""" return { 'c': self.lbl_color_select.color.getRgbF(), 'lw': float(self.txt_line_width_select.text().strip() or 0) } def update_mark(self, mark): """Update the properties of a mark to match the settings Parameters ---------- mark: straditize.cross_mark.CrossMarks The mark to update """ if len(self.straditizer.marks) < 2: return # line properties props = self.line_props mark._unselect_props.update(props) mark._select_props.update(self.select_props) # auto_hide auto_hide = self.cb_auto_hide.isChecked() mark.auto_hide = auto_hide if auto_hide: props['lw'] = 0 # show_connected show_connected = self.cb_show_connected.isChecked() mark.set_connected_artists_visible(show_connected) # drag hline drag_hline = self.cb_drag_hline.isChecked() if drag_hline and 'h' not in mark.draggable: mark.draggable.append('h') elif not drag_hline and 'h' in mark.draggable: mark.draggable.remove('h') # drag vline drag_vline = self.cb_drag_vline.isChecked() if drag_vline and 'v' not in mark.draggable: mark.draggable.append('v') elif not drag_vline and 'v' in mark.draggable: mark.draggable.remove('v') # select hline select_hline = self.cb_selectable_hline.isChecked() if select_hline and 'h' not in mark.selectable: mark.selectable.append('h') elif not select_hline and 'h' in mark.selectable: mark.selectable.remove('h') # select hline select_vline = self.cb_selectable_vline.isChecked() if select_vline and 'v' not in mark.selectable: mark.selectable.append('v') elif not select_vline and 'v' in mark.selectable: mark.selectable.remove('v') show_horizontal = self.cb_show_hlines.isChecked() mark.hide_horizontal = ~show_horizontal show_vertical = self.cb_show_vlines.isChecked() mark.hide_vertical = ~show_vertical for l in chain(mark.hlines, mark.vlines): l.update(props) for l in mark.hlines: l.set_visible(show_horizontal) for l in mark.vlines: l.set_visible(show_vertical) def change_hline_draggable(self, state): """Enable or disable the dragging of horizontal lines Parameters ---------- state: Qt.Checked or Qt.Unchecked If Qt.Checked, the horizontal lines can be dragged and dropped""" if state == Qt.Checked: for mark in self.marks: mark.draggable = np.unique(np.r_[['h'], mark.draggable]) else: for mark in self.marks: mark.draggable = np.array(list(set(mark.draggable) - {'h'})) def change_hline_selectable(self, state): """Enable or disable the selection of horizontal lines Parameters ---------- state: Qt.Checked or Qt.Unchecked If Qt.Checked, the horizontal lines can be selected""" if state == Qt.Checked: for mark in self.marks: mark.selectable = np.unique(np.r_[['h'], mark.selectable]) else: for mark in self.marks: mark.selectable = np.array(list(set(mark.selectable) - {'h'})) def change_vline_draggable(self, state): """Enable or disable the dragging of vertical lines Parameters ---------- state: Qt.Checked or Qt.Unchecked If Qt.Checked, the vertical lines can be dragged and dropped""" if state == Qt.Checked: for mark in self.marks: mark.draggable = np.unique(np.r_[['v'], mark.draggable]) else: for mark in self.marks: mark.draggable = np.array(list(set(mark.draggable) - {'v'})) def change_vline_selectable(self, state): """Enable or disable the selection of vertical lines Parameters ---------- state: Qt.Checked or Qt.Unchecked If Qt.Checked, the vertical lines can be selected""" if state == Qt.Checked: for mark in self.marks: mark.selectable = np.unique(np.r_[['v'], mark.selectable]) else: for mark in self.marks: mark.selectable = np.array(list(set(mark.selectable) - {'v'})) def change_show_hlines(self, state): """Enable of disable the visibility of horizontal lines Parameters ---------- state: Qt.Checked or Qt.Unchecked If Qt.Checked, all horizontal lines are hidden""" if state == Qt.Checked: for mark in self.marks: mark.hide_horizontal = False mark.set_visible(True) else: for mark in self.marks: mark.hide_horizontal = True mark.set_visible(True) self.draw_figs() def change_show_vlines(self, state): """Enable of disable the visibility of vertical lines Parameters ---------- state: Qt.Checked or Qt.Unchecked If Qt.Checked, all vertical lines are hidden""" if state == Qt.Checked: for mark in self.marks: mark.hide_vertical = False mark.set_visible(True) else: for mark in self.marks: mark.hide_vertical = True mark.set_visible(True) self.draw_figs() def enable_or_disable_widgets(self, b): """Renabled to use the :meth:`refresh` method """ self.refresh() def fill_after_adding(self, mark): if not self._filled: self.refresh() def go_to_right_mark(self): """Move the plot to the next right cross mark""" ax = self.straditizer.marks[0].ax if ax.xaxis_inverted(): return self.go_to_smaller_x_mark(min(ax.get_xlim())) else: return self.go_to_greater_x_mark(max(ax.get_xlim())) def go_to_left_mark(self): """Move the plot to the previous left cross mark""" ax = self.straditizer.marks[0].ax if ax.xaxis_inverted(): return self.go_to_greater_x_mark(max(ax.get_xlim())) else: return self.go_to_smaller_x_mark(min(ax.get_xlim())) def go_to_greater_x_mark(self, x): """Move the plot to the next mark with a x-position greater than `x` Parameters ---------- x: float The reference x-position that shall be smaller than the new centered mark""" def is_visible(mark): return (np.searchsorted(np.sort(xlim), mark.xa) == 1).any() and (np.searchsorted( np.sort(ylim), mark.ya) == 1).any() ax = next(self.marks).ax xlim = np.asarray(ax.get_xlim()) ylim = np.asarray(ax.get_ylim()) marks = self.straditizer.marks if len(marks[0].xa) > 1: # get the mark in the center yc = ylim.mean() try: mark = min(filter(is_visible, marks), key=lambda m: np.abs(m.ya - yc).min()) except ValueError: # empty sequence mark = min(marks, key=lambda m: np.abs(m.ya - yc).min()) # if all edges are visible already, we return if (np.searchsorted(xlim, mark.xa) == 1).all(): return mask = mark.xa > x if not mask.any(): # already on the right side return i = mark.xa[mask].argmin() x = mark.xa[mask][i] dx = np.diff(xlim) / 2. ax.set_xlim(x - dx, x + dx) ax.set_ylim(*ax.get_ylim()) else: distances = ((mark.xa > x).any() and (mark.xa[mark.xa > x] - x).min() for mark in marks) try: dist, mark = min((t for t in zip(distances, marks) if t[0]), key=lambda t: t[0]) except ValueError: # empty sequence return mask = mark.xa > x i = mark.xa[mask].argmin() x = mark.xa[mask][i] j = np.abs(mark.ya - ylim.mean()).argmin() y = mark.ya[j] dx = np.diff(xlim) / 2. ax.set_xlim(x - dx, x + dx) dy = np.diff(ylim) / 2. ax.set_ylim(y - dy, y + dy) self.straditizer.draw_figure() def go_to_smaller_x_mark(self, x): """Move the plot to the next mark with a x-position smaller than `x` Parameters ---------- x: float The reference x-position that shall be greater than the new centered mark""" def is_visible(mark): return (np.searchsorted(np.sort(xlim), mark.xa) == 1).any() and (np.searchsorted( np.sort(ylim), mark.ya) == 1).any() ax = next(self.marks).ax xlim = np.asarray(ax.get_xlim()) ylim = np.asarray(ax.get_ylim()) marks = self.straditizer.marks if len(marks[0].xa) > 1: # get the mark in the center yc = ylim.mean() try: mark = min(filter(is_visible, marks), key=lambda m: np.abs(m.ya - yc).min()) except ValueError: # empty sequence mark = min(marks, key=lambda m: np.abs(m.ya - yc).min()) # if all edges are visible already, we return if (np.searchsorted(xlim, mark.xa) == 1).all(): return mask = mark.xa < x if not mask.any(): # already on the right side return i = mark.xa[mask].argmin() x = mark.xa[mask][i] dx = np.diff(xlim) / 2. ax.set_xlim(x - dx, x + dx) ax.set_ylim(*ax.get_ylim()) else: distances = ((mark.xa < x).any() and (mark.xa[mark.xa < x] - x).min() for mark in marks) try: dist, mark = min((t for t in zip(distances, marks) if t[0]), key=lambda t: t[0]) except ValueError: # empty sequence return mask = mark.xa < x i = mark.xa[mask].argmin() x = mark.xa[mask][i] j = np.abs(mark.ya - ylim.mean()).argmin() y = mark.ya[j] dx = np.diff(xlim) / 2. ax.set_xlim(x - dx, x + dx) dy = np.diff(ylim) / 2. ax.set_ylim(y - dy, y + dy) self.straditizer.draw_figure() def go_to_upper_mark(self): """Go to the next mark above the current y-limits""" ax = self.straditizer.marks[0].ax if ax.xaxis_inverted(): return self.go_to_greater_y_mark(max(ax.get_ylim())) else: return self.go_to_smaller_y_mark(min(ax.get_ylim())) def go_to_lower_mark(self): """Go to the next mark below the current y-limits""" ax = self.straditizer.marks[0].ax if ax.xaxis_inverted(): return self.go_to_smaller_y_mark(min(ax.get_ylim())) else: return self.go_to_greater_y_mark(max(ax.get_ylim())) def go_to_greater_y_mark(self, y): """Move the plot to the next mark with a y-position greater than `y` Parameters ---------- y: float The reference y-position that shall be smaller than the new centered mark""" def is_visible(mark): return (np.searchsorted(np.sort(xlim), mark.xa) == 1).any() and (np.searchsorted( np.sort(ylim), mark.ya) == 1).any() ax = next(self.marks).ax xlim = np.asarray(ax.get_xlim()) ylim = np.asarray(ax.get_ylim()) marks = self.straditizer.marks if len(marks[0].ya) > 1: # get the mark in the center xc = xlim.mean() try: mark = min(filter(is_visible, marks), key=lambda m: np.abs(m.xa - xc).min()) except ValueError: # empty sequence mark = min(marks, key=lambda m: np.abs(m.xa - xc).min()) # if all edges are visible already, we return if (np.searchsorted(ylim, mark.ya) == 1).all(): return mask = mark.ya > y if not mask.any(): # already on the right side return i = mark.ya[mask].argmin() y = mark.ya[mask][i] dy = np.diff(ylim) / 2. ax.set_ylim(y - dy, y + dy) else: distances = ((mark.ya > y).any() and (mark.xa[mark.ya > y] - y).min() for mark in marks) try: dist, mark = min((t for t in zip(distances, marks) if t[0]), key=lambda t: t[0]) except ValueError: # empty sequence return mask = mark.ya > y i = mark.ya[mask].argmin() y = mark.ya[mask][i] j = np.abs(mark.xa - xlim.mean()).argmin() x = mark.xa[j] dx = np.diff(xlim) / 2. ax.set_xlim(x - dx, x + dx) dy = np.diff(ylim) / 2. ax.set_ylim(y - dy, y + dy) self.straditizer.draw_figure() def go_to_smaller_y_mark(self, y): """Move the plot to the next mark with a y-position smaller than `x` Parameters ---------- y: float The reference y-position that shall be smaller than the new centered mark""" def is_visible(mark): return (np.searchsorted(np.sort(xlim), mark.xa) == 1).any() and (np.searchsorted( np.sort(ylim), mark.ya) == 1).any() ax = next(self.marks).ax xlim = np.asarray(ax.get_xlim()) ylim = np.asarray(ax.get_ylim()) marks = self.straditizer.marks if len(marks[0].ya) > 1: # get the mark in the center xc = xlim.mean() try: mark = min(filter(is_visible, marks), key=lambda m: np.abs(m.xa - xc).min()) except ValueError: # empty sequence mark = min(marks, key=lambda m: np.abs(m.xa - xc).min()) # if all edges are visible already, we return if (np.searchsorted(ylim, mark.ya) == 1).all(): return mask = mark.ya < y if not mask.any(): # already on the right side return i = mark.ya[mask].argmin() y = mark.ya[mask][i] dy = np.diff(ylim) / 2. ax.set_ylim(y - dy, y + dy) else: distances = ((mark.ya < y).any() and (mark.ya[mark.ya < y] - y).min() for mark in marks) try: dist, mark = min((t for t in zip(distances, marks) if t[0]), key=lambda t: t[0]) except ValueError: # empty sequence return mask = mark.ya < y i = mark.ya[mask].argmin() y = mark.ya[mask][i] j = np.abs(mark.xa - xlim.mean()).argmin() x = mark.xa[j] dx = np.diff(xlim) / 2. ax.set_xlim(x - dx, x + dx) dy = np.diff(ylim) / 2. ax.set_ylim(y - dy, y + dy) self.straditizer.draw_figure() def add_toolbar_widgets(self, mark): """Add the navigation actions to the toolbar""" tb = self.straditizer.marks[0].ax.figure.canvas.toolbar if not isinstance(tb, QToolBar): return if self.tb_actions: self.remove_actions() self.tb_actions.append(tb.addSeparator()) try: mark.x except NotImplementedError: add_right = False else: a = tb.addAction(QIcon(get_icon('left_mark.png')), 'left mark', self.go_to_left_mark) a.setToolTip('Move to the next cross mark on the left') self.tb_actions.append(a) add_right = True try: mark.y except NotImplementedError: pass else: a = tb.addAction(QIcon(get_icon('upper_mark.png')), 'upper mark', self.go_to_upper_mark) a.setToolTip('Move to the next cross mark above') self.tb_actions.append(a) a = tb.addAction(QIcon(get_icon('lower_mark.png')), 'lower mark', self.go_to_lower_mark) a.setToolTip('Move to the next cross mark below') self.tb_actions.append(a) if add_right: a = tb.addAction(QIcon(get_icon('right_mark.png')), 'right mark', self.go_to_right_mark) a.setToolTip('Move to the next cross mark on the right') self.tb_actions.append(a) self._toolbar = tb def remove_actions(self): """Remove the navigation actions from the toolbar""" if self._toolbar is None: return tb = self._toolbar for a in self.tb_actions: tb.removeAction(a) self.tb_actions.clear() def refresh(self): """Reimplemented to also set the properties of this widget """ super(MarkerControl, self).refresh() if self.straditizer is not None and self.straditizer.marks: self._filled = True mark = self.straditizer.marks[0] self.fill_from_mark(mark) self.add_toolbar_widgets(mark) else: self.remove_actions() self._filled = False if self.straditizer is not None: self.straditizer.mark_added.connect(self.fill_after_adding) self.straditizer.mark_added.connect(self.update_mark)
class HelpExplorer(QWidget, DockMixin): """A widget for showing the documentation. It behaves somewhat similar to spyders object inspector plugin and can show restructured text either as html (if sphinx is installed) or as plain text. It furthermore has a browser to show html content Warnings -------- The :class:`HelpBrowser` class is known to crash under PyQt4 when new web page domains are loaded. Hence you should disable the browsing to different remote websites and even disable intersphinx""" #: The viewer classes used by the help explorer. :class:`HelpExplorer` #: instances replace this attribute with the corresponding HelpMixin #: instance viewers = OrderedDict([('HTML help', UrlHelp), ('Plain text', TextHelp)]) def __init__(self, *args, **kwargs): super(HelpExplorer, self).__init__(*args, **kwargs) self.vbox = vbox = QVBoxLayout() self.combo = QComboBox(parent=self) vbox.addWidget(self.combo) self.viewers = OrderedDict( [(key, cls(parent=self)) for key, cls in six.iteritems( self.viewers)]) for key, ini in six.iteritems(self.viewers): self.combo.addItem(key) ini.hide() vbox.addWidget(ini) self.viewer = next(six.itervalues(self.viewers)) self.viewer.show() self.combo.currentIndexChanged[str].connect(self.set_viewer) self.setLayout(vbox) def set_viewer(self, name): """Sets the current documentation viewer Parameters ---------- name: str or object A string must be one of the :attr:`viewers` attribute. An object can be one of the values in the :attr:`viewers` attribute""" if isstring(name) and asstring(name) not in self.viewers: raise ValueError("Don't have a viewer named %s" % (name, )) elif not isstring(name): viewer = name else: name = asstring(name) viewer = self.viewers[name] self.viewer.hide() self.viewer = viewer self.viewer.show() if (isstring(name) and not self.combo.currentText() == name): self.combo.setCurrentIndex(list(self.viewers).index(name)) @docstrings.dedent def show_help(self, obj, oname='', files=None): """ Show the documentaion of the given object We first try to use the current viewer based upon it's :attr:`HelpMixin.can_document_object` attribute. If this does not work, we check the other viewers Parameters ---------- %(HelpMixin.show_help.parameters)s""" oname = asstring(oname) ret = None if self.viewer.can_document_object: try: ret = self.viewer.show_help(obj, oname=oname, files=files) except Exception: logger.debug("Could not document %s with %s viewer!", oname, self.combo.currentText(), exc_info=True) else: curr_i = self.combo.currentIndex() for i, (viewername, viewer) in enumerate( six.iteritems(self.viewers)): if i != curr_i and viewer.can_document_object: self.set_viewer(viewername) self.combo.blockSignals(True) self.combo.setCurrentIndex(i) self.combo.blockSignals(False) try: ret = viewer.show_help(obj, oname=oname, files=files) except Exception: logger.debug("Could not document %s with %s viewer!", oname, viewername, exc_info=True) if ret: self.parent().raise_() return ret @docstrings.dedent def show_rst(self, text, oname='', files=None): """ Show restructured text We first try to use the current viewer based upon it's :attr:`HelpMixin.can_show_rst` attribute. If this does not work, we check the other viewers Parameters ---------- %(HelpMixin.show_rst.parameters)s""" ret = None if self.viewer.can_show_rst: ret = self.viewer.show_rst(text, oname=oname, files=files) else: for viewer in six.itervalues(self.viewers): if viewer.can_show_rst: self.set_viewer(viewer) ret = viewer.show_rst(text, oname=oname, files=files) break if ret: self.parent().raise_() return ret @docstrings.dedent def show_intro(self, text=''): """ Show an intro text We first try to use the current viewer based upon it's :attr:`HelpMixin.can_show_rst` attribute. If this does not work, we check the other viewers Parameters ---------- %(HelpMixin.show_intro.parameters)s""" found = False for i, viewer in enumerate(six.itervalues(self.viewers)): viewer.show_intro(text) if not found and viewer.can_show_rst: if i: self.set_viewer(viewer) found = True def close(self, *args, **kwargs): self.viewers['HTML help'].close(*args, **kwargs) return super(HelpExplorer, self).close(*args, **kwargs)
def __init__(self, straditizer_widgets, item, *args, **kwargs): """ Parameters ---------- %(StraditizerControlBase.init_straditizercontrol.parameters)s """ super(MarkerControl, self).__init__(*args, **kwargs) self.init_straditizercontrol(straditizer_widgets, item) vbox = QVBoxLayout() # auto hide button box_hide = QGridLayout() self.cb_auto_hide = QCheckBox('Auto-hide') self.cb_show_connected = QCheckBox('Show additionals') self.cb_drag_hline = QCheckBox('Drag in y-direction') self.cb_drag_vline = QCheckBox('Drag in x-direction') self.cb_selectable_hline = QCheckBox('Horizontal lines selectable') self.cb_selectable_vline = QCheckBox('Vertical lines selectable') self.cb_show_hlines = QCheckBox('Show horizontal lines') self.cb_show_vlines = QCheckBox('Show vertical lines') box_hide.addWidget(self.cb_auto_hide, 0, 0) box_hide.addWidget(self.cb_show_connected, 0, 1) box_hide.addWidget(self.cb_show_vlines, 1, 0) box_hide.addWidget(self.cb_show_hlines, 1, 1) box_hide.addWidget(self.cb_drag_hline, 2, 0) box_hide.addWidget(self.cb_drag_vline, 2, 1) box_hide.addWidget(self.cb_selectable_hline, 3, 0) box_hide.addWidget(self.cb_selectable_vline, 3, 1) vbox.addLayout(box_hide) style_box = QGridLayout() style_box.addWidget(QLabel('Unselected:'), 0, 1) selected_label = QLabel('Selected:') style_box.addWidget(selected_label, 0, 2) max_width = selected_label.sizeHint().width() # line color self.lbl_color_select = ColorLabel() self.lbl_color_unselect = ColorLabel() self.lbl_color_select.setMaximumWidth(max_width) self.lbl_color_unselect.setMaximumWidth(max_width) style_box.addWidget(QLabel('Line color:'), 1, 0) style_box.addWidget(self.lbl_color_unselect, 1, 1) style_box.addWidget(self.lbl_color_select, 1, 2) # line width self.txt_line_width = QLineEdit() self.txt_line_width_select = QLineEdit() validator = QDoubleValidator() validator.setBottom(0) self.txt_line_width.setValidator(validator) self.txt_line_width_select.setValidator(validator) style_box.addWidget(QLabel('Line width:'), 2, 0) style_box.addWidget(self.txt_line_width, 2, 1) style_box.addWidget(self.txt_line_width_select, 2, 2) vbox.addLayout(style_box) # line style hbox_line_style = QHBoxLayout() hbox_line_style.addWidget(QLabel('Line style')) self.combo_line_style = QComboBox() hbox_line_style.addWidget(self.combo_line_style) vbox.addLayout(hbox_line_style) self.fill_linestyles() self.combo_line_style.setSizeAdjustPolicy(QComboBox.AdjustToContents) # marker style hbox_marker_style = QHBoxLayout() hbox_marker_style.addWidget(QLabel('Marker size')) self.txt_marker_size = QLineEdit() self.txt_marker_size.setMinimumWidth(40) self.txt_marker_size.setText(str(mpl.rcParams['lines.markersize'])) validator = QDoubleValidator() validator.setBottom(0) self.txt_marker_size.setValidator(validator) hbox_marker_style.addWidget(self.txt_marker_size) hbox_marker_style.addWidget(QLabel('Marker style')) self.combo_marker_style = QComboBox() hbox_marker_style.addWidget(self.combo_marker_style) vbox.addLayout(hbox_marker_style) self.setLayout(vbox) self.widgets2disable = [ self.lbl_color_select, self.lbl_color_unselect, self.cb_auto_hide, self.txt_line_width, self.txt_line_width_select, self.combo_line_style, self.cb_show_connected, self.txt_marker_size, self.combo_marker_style, self.cb_drag_vline, self.cb_drag_hline, self.cb_selectable_vline, self.cb_selectable_hline, self.cb_show_hlines, self.cb_show_vlines ] self.fill_markerstyles() self.tb_actions = [] # --------------------------------------------------------------------- # ---------------------------- connections ---------------------------- # --------------------------------------------------------------------- self.lbl_color_select.color_changed.connect(self.change_select_colors) self.lbl_color_unselect.color_changed.connect( self.change_unselect_colors) self.txt_line_width.textChanged.connect(self.change_line_widths) self.txt_line_width_select.textChanged.connect( self.change_selection_line_widths) self.cb_auto_hide.stateChanged.connect(self.change_auto_hide) self.combo_marker_style.currentIndexChanged.connect( self.change_marker_style) self.combo_line_style.currentIndexChanged.connect( self.change_line_style) self.txt_marker_size.textChanged.connect(self.change_marker_size) self.cb_show_connected.stateChanged.connect( self.change_show_connected_artists) self.cb_drag_hline.stateChanged.connect(self.change_hline_draggable) self.cb_drag_vline.stateChanged.connect(self.change_vline_draggable) self.cb_selectable_hline.stateChanged.connect( self.change_hline_selectable) self.cb_selectable_vline.stateChanged.connect( self.change_vline_selectable) self.cb_show_vlines.stateChanged.connect(self.change_show_vlines) self.cb_show_hlines.stateChanged.connect(self.change_show_hlines)
class FormatoptionWidget(QWidget, DockMixin): """ Widget to update the formatoptions of the current project This widget, mainly made out of a combobox for the formatoption group, a combobox for the formatoption, and a text editor, is designed for updating the selected formatoptions for the current subproject. The widget is connected to the :attr:`psyplot.project.Project.oncpchange` signal and refills the comboboxes if the current subproject changes. The text editor either accepts python code that will be executed by the given `console`, or yaml code. """ no_fmtos_update = _temp_bool_prop('no_fmtos_update', """update the fmto combo box or not""") #: The combobox for the formatoption groups group_combo = None #: The combobox for the formatoptions fmt_combo = None #: The help_explorer to display the documentation of the formatoptions help_explorer = None #: The formatoption specific widget that is loaded from the formatoption fmt_widget = None #: A line edit for updating the formatoptions line_edit = None #: A multiline text editor for updating the formatoptions text_edit = None #: A button to switch between :attr:`line_edit` and :attr:`text_edit` multiline_button = None @property def shell(self): """The shell to execute the update of the formatoptions in the current project""" return self.console.kernel_manager.kernel.shell def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode(QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [ self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb ]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc) def refill_from_rc(self, sort_by_key): from psyplot.project import gcp self.fill_combos_from_project(gcp()) def fill_combos_from_project(self, project): """Fill :attr:`group_combo` and :attr:`fmt_combo` from a project Parameters ---------- project: psyplot.project.Project The project to use""" if rcParams['fmt.sort_by_key']: def sorter(fmto): return fmto.key else: sorter = self.get_name current_text = self.group_combo.currentText() with self.no_fmtos_update: self.group_combo.clear() if project is None or project.is_main or not len(project): self.fmt_combo.clear() self.groups = [] self.fmtos = [] self.line_edit.setEnabled(False) return self.line_edit.setEnabled(True) # get dimensions it_vars = chain.from_iterable(arr.psy.iter_base_variables for arr in project.arrays) dims = next(it_vars).dims sdims = set(dims) for var in it_vars: sdims.intersection_update(var.dims) coords = [d for d in dims if d in sdims] coords_name = [COORDSGROUP] if coords else [] coords_verbose = ['Dimensions'] if coords else [] coords = [coords] if coords else [] if len(project.plotters): # get formatoptions and group them alphabetically grouped_fmts = defaultdict(list) for fmto in project._fmtos: grouped_fmts[fmto.group].append(fmto) for val in six.itervalues(grouped_fmts): val.sort(key=sorter) grouped_fmts = OrderedDict( sorted(six.iteritems(grouped_fmts), key=lambda t: psyp.groups.get(t[0], t[0]))) fmt_groups = list(grouped_fmts.keys()) # save original names self.groups = coords_name + [ALLGROUP] + fmt_groups # save verbose group names (which are used in the combo box) self.groupnames = ( coords_verbose + ['All formatoptions'] + list(map(lambda s: psyp.groups.get(s, s), fmt_groups))) # save formatoptions fmtos = list(grouped_fmts.values()) self.fmtos = coords + [sorted(chain(*fmtos), key=sorter) ] + fmtos else: self.groups = coords_name self.groupnames = coords_verbose self.fmtos = coords self.group_combo.addItems(self.groupnames) ind = self.group_combo.findText(current_text) self.group_combo.setCurrentIndex(ind if ind >= 0 else 0) self.fill_fmt_combo(self.group_combo.currentIndex()) def get_name(self, fmto): """Get the name of a :class:`psyplot.plotter.Formatoption` instance""" if isinstance(fmto, six.string_types): return fmto return '%s (%s)' % (fmto.name, fmto.key) if fmto.name else fmto.key @property def fmto(self): return self.fmtos[self.group_combo.currentIndex()][ self.fmt_combo.currentIndex()] @fmto.setter def fmto(self, value): name = self.get_name(value) for i, fmtos in enumerate(self.fmtos): if i == 1: # all formatoptions continue if name in map(self.get_name, fmtos): with self.no_fmtos_update: self.group_combo.setCurrentIndex(i) self.fill_fmt_combo(i, name) return def toggle_line_edit(self): """Switch between the :attr:`line_edit` and :attr:`text_edit` This method is called when the :attr:`multiline_button` is clicked and switches between the single line :attr:``line_edit` and the multiline :attr:`text_edit` """ # switch to multiline text edit if (self.multiline_button.isChecked() and not self.text_edit.isVisible()): self.line_edit.setVisible(False) self.text_edit.setVisible(True) self.text_edit.setPlainText(self.line_edit.text()) elif (not self.multiline_button.isChecked() and not self.line_edit.isVisible()): self.line_edit.setVisible(True) self.text_edit.setVisible(False) self.line_edit.setText(self.text_edit.toPlainText()) def fill_fmt_combo(self, i, current_text=None): """Fill the :attr:`fmt_combo` combobox based on the current group name """ if not self.no_fmtos_update: with self.no_fmtos_update: if current_text is None: current_text = self.fmt_combo.currentText() self.fmt_combo.clear() self.fmt_combo.addItems(list(map(self.get_name, self.fmtos[i]))) ind = self.fmt_combo.findText(current_text) self.fmt_combo.setCurrentIndex(ind if ind >= 0 else 0) # update completer model self.setup_fmt_completion_model() idx = self.fmt_combo.currentIndex() self.show_fmt_info(idx) self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def set_fmto(self, name): self.fmto = name def setup_fmt_completion_model(self): fmtos = list( unique_everseen(map(self.get_name, chain.from_iterable(self.fmtos)))) model = self.fmto_completer.model() model.setRowCount(len(fmtos)) for i, name in enumerate(fmtos): model.setItem(i, QStandardItem(name)) def load_fmt_widget(self, i): """Load the formatoption specific widget This method loads the formatoption specific widget from the :meth:`psyplot.plotter.Formatoption.get_fmt_widget` method and displays it above the :attr:`line_edit` Parameters ---------- i: int The index of the current formatoption""" self.remove_fmt_widget() group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: from psyplot.project import gcp if self.groups[group_ind] == COORDSGROUP: dim = self.fmtos[group_ind][i] self.fmt_widget = self.dim_widget self.dim_widget.set_dim(dim) self.dim_widget.set_single_selection(dim not in gcp()[0].dims) self.dim_widget.setVisible(True) else: fmto = self.fmtos[group_ind][i] self.fmt_widget = fmto.get_fmt_widget(self, gcp()) if self.fmt_widget is not None: self.vbox.insertWidget(2, self.fmt_widget) def reset_fmt_widget(self): idx = self.fmt_combo.currentIndex() self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def remove_fmt_widget(self): if self.fmt_widget is not None: self.fmt_widget.hide() if self.fmt_widget is self.dim_widget: self.fmt_widget.reset_combobox() else: self.vbox.removeWidget(self.fmt_widget) self.fmt_widget.close() del self.fmt_widget def set_current_fmt_value(self, i): """Add the value of the current formatoption to the line text""" group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: if self.groups[group_ind] == COORDSGROUP: from psyplot.project import gcp dim = self.fmtos[group_ind][i] self.set_obj(gcp().arrays[0].psy.idims[dim]) else: fmto = self.fmtos[group_ind][i] self.set_obj(fmto.value) def show_fmt_info(self, i): """Show the documentation of the formatoption in the help explorer """ group_ind = self.group_combo.currentIndex() if (not self.no_fmtos_update and self.groups[group_ind] != COORDSGROUP): fmto = self.fmtos[self.group_combo.currentIndex()][i] fmto.plotter.show_docs( fmto.key, include_links=self.include_links_cb.isChecked()) def run_code(self): """Run the update of the project inside the :attr:`shell`""" if self.line_edit.isVisible(): text = str(self.line_edit.text()) else: text = str(self.text_edit.toPlainText()) if not text or not self.fmtos: return group_ind = self.group_combo.currentIndex() if self.groups[group_ind] == COORDSGROUP: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()] param = 'dims' else: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()].key param = 'fmt' if self.yaml_cb.isChecked(): import psyplot.project as psy psy.gcp().update(**{key: yaml.load(text, Loader=yaml.Loader)}) else: code = "psy.gcp().update(%s={'%s': %s})" % (param, key, text) if ExecutionInfo is not None: info = ExecutionInfo(raw_cell=code, store_history=False, silent=True, shell_futures=False) e = ExecutionResult(info) else: e = ExecutionResult() self.console.run_command_in_shell(code, e) try: e.raise_error() except Exception: # reset the console and clear the error message raise finally: self.console.reset() def get_text(self): """Get the current update text""" if self.line_edit.isVisible(): return self.line_edit.text() else: return self.text_edit.toPlainText() def get_obj(self): """Get the current update text""" if self.line_edit.isVisible(): txt = self.line_edit.text() else: txt = self.text_edit.toPlainText() try: obj = yaml.load(txt, Loader=yaml.Loader) except Exception: self.error_msg.showTraceback("Could not load %s" % txt) else: return obj def insert_obj(self, obj): """Add a string to the formatoption widget""" current = self.get_text() use_yaml = self.yaml_cb.isChecked() use_line_edit = self.line_edit.isVisible() # strings are treated separately such that we consider quotation marks # at the borders if isstring(obj) and current: if use_line_edit: pos = self.line_edit.cursorPosition() else: pos = self.text_edit.textCursor().position() if pos not in [0, len(current)]: s = obj else: if current[0] in ['"', "'"]: current = current[1:-1] self.clear_text() if pos == 0: s = '"' + obj + current + '"' else: s = '"' + current + obj + '"' current = '' elif isstring(obj): # add quotation marks s = '"' + obj + '"' elif not use_yaml: s = repr(obj) else: s = yaml.dump(obj, default_flow_style=True).strip() if s.endswith('\n...'): s = s[:-4] if use_line_edit: self.line_edit.insert(s) else: self.text_edit.insertPlainText(s) def clear_text(self): if self.line_edit.isVisible(): self.line_edit.clear() else: self.text_edit.clear() def set_obj(self, obj): self.clear_text() self.insert_obj(obj) def show_all_fmt_info(self, what): """Show the keys, summaries or docs of the formatoptions Calling this function let's the help browser show the documentation etc. of all docs or only the selected group determined by the state of the :attr:`grouped_cb` and :attr:`all_groups_cb` checkboxes Parameters ---------- what: {'keys', 'summaries', 'docs'} Determines what to show""" if not self.fmtos: return if (self.all_groups_cb.isChecked() or self.group_combo.currentIndex() < 2): fmtos = list( chain.from_iterable( fmto_group for i, fmto_group in enumerate(self.fmtos) if self.groups[i] not in [ALLGROUP, COORDSGROUP])) else: fmtos = self.fmtos[self.group_combo.currentIndex()] plotter = fmtos[0].plotter getattr(plotter, 'show_' + what)([fmto.key for fmto in fmtos], grouped=self.grouped_cb.isChecked(), include_links=self.include_links_cb.isChecked())