def __init__(self, straditizer_widgets, item, *args, **kwargs): super(PlotControl, self).__init__(*args, **kwargs) self.btn_view_global = QPushButton('Zoom out') self.btn_view_data = QPushButton('Zoom to data') self.table = PlotControlTable(straditizer_widgets) self.results_plot = ResultsPlot(straditizer_widgets) self.init_straditizercontrol(straditizer_widgets, item) # --------------------------------------------------------------------- # ------------------------------ Layout ------------------------------- # --------------------------------------------------------------------- hbox = QHBoxLayout() hbox.addWidget(self.btn_view_global) hbox.addWidget(self.btn_view_data) vbox = QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(self.table) self.setLayout(vbox) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.btn_view_global.clicked.connect(self.zoom_global) self.btn_view_data.clicked.connect(self.zoom_data)
def get_fmt_widget(self, parent, project): """Get the formatoption widget to update this formatoption in the GUI """ from psyplot_gui.compat.qtcompat import QPushButton button = QPushButton('Test', parent) button.clicked.connect(lambda: parent.insert_obj(button.text())) return button
def __init__(self, main=None): super(Prefences, self).__init__(parent=main) self.setWindowTitle('Preferences') # Widgets self.pages_widget = QStackedWidget() self.contents_widget = QListWidget() self.bt_reset = QPushButton('Reset to defaults') self.bt_load_plugins = QPushButton('Load plugin pages') self.bt_load_plugins.setToolTip( 'Load the rcParams for the plugins in separate pages') self.bbox = bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel) # Widgets setup # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle('Preferences') self.contents_widget.setMovement(QListView.Static) self.contents_widget.setSpacing(1) self.contents_widget.setCurrentRow(0) # Layout hsplitter = QSplitter() hsplitter.addWidget(self.contents_widget) hsplitter.addWidget(self.pages_widget) hsplitter.setStretchFactor(1, 1) btnlayout = QHBoxLayout() btnlayout.addWidget(self.bt_reset) btnlayout.addWidget(self.bt_load_plugins) btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addWidget(hsplitter) vlayout.addLayout(btnlayout) self.setLayout(vlayout) # Signals and slots if main is not None: self.bt_reset.clicked.connect(main.reset_rcParams) self.bt_load_plugins.clicked.connect(self.load_plugin_pages) self.pages_widget.currentChanged.connect(self.current_page_changed) self.contents_widget.currentRowChanged.connect( self.pages_widget.setCurrentIndex) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.bt_apply.clicked.connect(self.apply_clicked) self.bt_apply.setEnabled(False)
def __init__(self, straditizer_widgets, item=None, *args, **kwargs): """ Parameters ---------- %(StraditizerControlBase.init_straditizercontrol.parameters)s""" super(ImageRotator, self).__init__(*args, **kwargs) self.txt_rotate = QLineEdit() self.txt_rotate.setValidator(QDoubleValidator()) self.btn_rotate_horizontal = QPushButton('Horizontal alignment') self.btn_rotate_horizontal.setToolTip( 'Mark two points that should be on the same horizontal level ' 'and rotate the picture to achieve this.') self.btn_rotate_vertical = QPushButton('Vertical alignment') self.btn_rotate_vertical.setToolTip( 'Mark two points that should be on the same vertical level ' 'and rotate the picture to achieve this.') self.init_straditizercontrol(straditizer_widgets, item) # --------------------------------------------------------------------- # --------------------------- Layouts --------------------------------- # --------------------------------------------------------------------- layout = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel('Rotate:')) hbox.addWidget(self.txt_rotate) layout.addLayout(hbox) layout.addWidget(self.btn_rotate_horizontal) layout.addWidget(self.btn_rotate_vertical) self.setLayout(layout) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.txt_rotate.textChanged.connect(self.start_rotation) self.btn_rotate_horizontal.clicked.connect( self.start_horizontal_alignment) self.btn_rotate_vertical.clicked.connect( self.start_vertical_alignment) self.widgets2disable = [self.txt_rotate, self.btn_rotate_horizontal, self.btn_rotate_vertical]
def __init__(self, straditizer_widgets, item): """ Parameters ---------- %(StraditizerControlBase.init_straditizercontrol.parameters)s """ self.btn_marks_for_y = QPushButton('Insert Y-axis values') self.btn_marks_for_x = QPushButton('Insert X-axis values') self.btn_marks_for_y.clicked.connect(self.marks_for_y) self.btn_marks_for_x.clicked.connect(partial(self.marks_for_x, True)) self.widgets2disable = [self.btn_marks_for_x, self.btn_marks_for_y] self.init_straditizercontrol(straditizer_widgets, item)
def __init__(self, parent, fmto, project): QWidget.__init__(self, parent) hbox = QHBoxLayout() # add a select colormap button self.btn_choose = button = QPushButton('Choose...') button.clicked.connect(partial(self.choose_cmap, None)) hbox.addWidget(button) # add a show colormap button self.btn_show = button = QPushButton('Show...') button.clicked.connect(self.show_cmap) hbox.addWidget(button) hbox.addWidget(Switch2FmtButton(parent, fmto.bounds, fmto.cbar)) self.setLayout(hbox)
def __init__(self, versions, *args, **kwargs): """ Parameters ---------- %(DependenciesTree.parameters)s """ super(DependenciesDialog, self).__init__(*args, **kwargs) self.setWindowTitle('Dependencies') self.versions = versions self.vbox = layout = QVBoxLayout() self.label = QLabel(""" psyplot and the plugins depend on several python libraries. The tree widget below lists the versions of the plugins and the requirements. You can select the items in the tree and copy them to clipboard.""", parent=self) layout.addWidget(self.label) self.tree = DependenciesTree(versions, parent=self) self.tree.setSelectionMode(QAbstractItemView.MultiSelection) layout.addWidget(self.tree) # copy button self.bt_copy = QPushButton('Copy selection to clipboard') self.bt_copy.setToolTip( 'Copy the selected packages in the above table to the clipboard.') self.bt_copy.clicked.connect(lambda: self.copy_selected()) self.bbox = QDialogButtonBox(QDialogButtonBox.Ok) self.bbox.accepted.connect(self.accept) hbox = QHBoxLayout() hbox.addWidget(self.bt_copy) hbox.addStretch(1) hbox.addWidget(self.bbox) layout.addLayout(hbox) #: A label for simple status update self.info_label = QLabel('', self) layout.addWidget(self.info_label) self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.clear_label) self.setLayout(layout)
def __init__(self, straditizer_widgets): self.init_straditizercontrol(straditizer_widgets) self.btn_plot = QPushButton("Plot results") self.cb_final = QCheckBox("Samples") self.cb_final.setToolTip( "Create the diagram based on the samples only, not on the full " "digized data") self.cb_final.setChecked(True) self.cb_final.setEnabled(False) self.cb_transformed = QCheckBox("Translated") self.cb_transformed.setToolTip("Use the x-axis and y-axis translation") self.cb_transformed.setChecked(True) self.cb_transformed.setEnabled(False) self.btn_plot.clicked.connect(self.plot_results)
def __init__(self, main=None): super(Prefences, self).__init__(parent=main) self.setWindowTitle('Preferences') # Widgets self.pages_widget = QStackedWidget() self.contents_widget = QListWidget() self.bt_reset = QPushButton('Reset to defaults') self.bt_load_plugins = QPushButton('Load plugin pages') self.bt_load_plugins.setToolTip( 'Load the rcParams for the plugins in separate pages') self.bbox = bbox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel) # Widgets setup # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle('Preferences') self.contents_widget.setMovement(QListView.Static) self.contents_widget.setSpacing(1) self.contents_widget.setCurrentRow(0) # Layout hsplitter = QSplitter() hsplitter.addWidget(self.contents_widget) hsplitter.addWidget(self.pages_widget) hsplitter.setStretchFactor(1, 1) btnlayout = QHBoxLayout() btnlayout.addWidget(self.bt_reset) btnlayout.addWidget(self.bt_load_plugins) btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addWidget(hsplitter) vlayout.addLayout(btnlayout) self.setLayout(vlayout) # Signals and slots if main is not None: self.bt_reset.clicked.connect(main.reset_rcParams) self.bt_load_plugins.clicked.connect(self.load_plugin_pages) self.pages_widget.currentChanged.connect(self.current_page_changed) self.contents_widget.currentRowChanged.connect( self.pages_widget.setCurrentIndex) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.bt_apply.clicked.connect(self.apply_clicked) self.bt_apply.setEnabled(False)
def __init__(self, *args, **kwargs): super(RcParamsWidget, self).__init__(*args, **kwargs) self.vbox = vbox = QVBoxLayout() self.description = QLabel( '<p>Modify the rcParams for your need. Changes will not be applied' ' until you click the Apply or Ok button.</p>' '<p>Values must be entered in yaml syntax</p>', parent=self) vbox.addWidget(self.description) self.tree = tree = RcParamsTree(self.rc, getattr(self.rc, 'validate', None), getattr(self.rc, 'descriptions', None), parent=self) tree.setSelectionMode(QAbstractItemView.MultiSelection) vbox.addWidget(self.tree) self.bt_select_all = QPushButton('Select All', self) self.bt_select_changed = QPushButton('Select changes', self) self.bt_select_none = QPushButton('Clear Selection', self) self.bt_export = QToolButton(self) self.bt_export.setText('Export Selection...') self.bt_export.setToolTip('Export the selected rcParams to a file') self.bt_export.setPopupMode(QToolButton.InstantPopup) self.export_menu = export_menu = QMenu(self) export_menu.addAction(self.save_settings_action()) export_menu.addAction(self.save_settings_action(True)) self.bt_export.setMenu(export_menu) hbox = QHBoxLayout() hbox.addWidget(self.bt_select_all) hbox.addWidget(self.bt_select_changed) hbox.addWidget(self.bt_select_none) hbox.addStretch(1) hbox.addWidget(self.bt_export) vbox.addLayout(hbox) self.setLayout(vbox) self.bt_select_all.clicked.connect(self.tree.selectAll) self.bt_select_none.clicked.connect(self.tree.clearSelection) self.bt_select_changed.clicked.connect(self.tree.select_changes)
def _init_digitize_child(self): self.lbl_col = QLabel('') self.btn_prev = QPushButton('<') self.btn_next = QPushButton('>') self.btn_edit = QPushButton('Edit') self.btn_add = QPushButton('+') self.reset_lbl_col() self.btn_box = w = QWidget() vbox = QVBoxLayout() vbox.addWidget(self.lbl_col) hbox = QHBoxLayout() hbox.addWidget(self.btn_prev) hbox.addWidget(self.btn_next) hbox.addWidget(self.btn_edit) hbox.addWidget(self.btn_add) vbox.addLayout(hbox) w.setLayout(vbox) self.digitize_child = QTreeWidgetItem(0) self.straditizer_widgets.digitizer.digitize_item.addChild( self.digitize_child) self.straditizer_widgets.digitizer.tree.setItemWidget( self.digitize_child, 0, w) self.widgets2disable = [self.btn_prev, self.btn_next, self.btn_edit, self.btn_add] self.btn_next.clicked.connect(self.increase_current_col) self.btn_prev.clicked.connect(self.decrease_current_col) self.btn_edit.clicked.connect(self.select_current_column) self.btn_add.clicked.connect(self.select_and_add_current_column)
class Prefences(QDialog): """Preferences dialog""" @property def bt_apply(self): return self.bbox.button(QDialogButtonBox.Apply) @property def pages(self): return map(self.get_page, range(self.pages_widget.count())) def __init__(self, main=None): super(Prefences, self).__init__(parent=main) self.setWindowTitle('Preferences') # Widgets self.pages_widget = QStackedWidget() self.contents_widget = QListWidget() self.bt_reset = QPushButton('Reset to defaults') self.bt_load_plugins = QPushButton('Load plugin pages') self.bt_load_plugins.setToolTip( 'Load the rcParams for the plugins in separate pages') self.bbox = bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel) # Widgets setup # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle('Preferences') self.contents_widget.setMovement(QListView.Static) self.contents_widget.setSpacing(1) self.contents_widget.setCurrentRow(0) # Layout hsplitter = QSplitter() hsplitter.addWidget(self.contents_widget) hsplitter.addWidget(self.pages_widget) hsplitter.setStretchFactor(1, 1) btnlayout = QHBoxLayout() btnlayout.addWidget(self.bt_reset) btnlayout.addWidget(self.bt_load_plugins) btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addWidget(hsplitter) vlayout.addLayout(btnlayout) self.setLayout(vlayout) # Signals and slots if main is not None: self.bt_reset.clicked.connect(main.reset_rcParams) self.bt_load_plugins.clicked.connect(self.load_plugin_pages) self.pages_widget.currentChanged.connect(self.current_page_changed) self.contents_widget.currentRowChanged.connect( self.pages_widget.setCurrentIndex) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.bt_apply.clicked.connect(self.apply_clicked) self.bt_apply.setEnabled(False) def set_current_index(self, index): """Set current page index""" self.contents_widget.setCurrentRow(index) def current_page_changed(self, index): configpage = self.get_page(index) self.bt_apply.setVisible(not configpage.auto_updates) self.check_changes(configpage) def get_page(self, index=None): """Return page widget""" if index is None: widget = self.pages_widget.currentWidget() else: widget = self.pages_widget.widget(index) return widget.widget() def accept(self): """Reimplement Qt method""" for configpage in self.pages: if not configpage.is_valid: continue configpage.apply_changes() QDialog.accept(self) def apply_clicked(self): # Apply button was clicked configpage = self.get_page() if configpage.is_valid: configpage.apply_changes() self.check_changes(configpage) def add_page(self, widget): """Add a new page to the preferences dialog Parameters ---------- widget: ConfigPage The page to add""" widget.validChanged.connect(self.bt_apply.setEnabled) widget.validChanged.connect( self.bbox.button(QDialogButtonBox.Ok).setEnabled) scrollarea = QScrollArea(self) scrollarea.setWidgetResizable(True) scrollarea.setWidget(widget) self.pages_widget.addWidget(scrollarea) item = QListWidgetItem(self.contents_widget) try: item.setIcon(widget.icon) except TypeError: pass item.setText(widget.title) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setSizeHint(QtCore.QSize(0, 25)) widget.propose_changes.connect(self.check_changes) def check_changes(self, configpage): """Enable the apply button if there are changes to the settings""" if configpage != self.get_page(): return self.bt_apply.setEnabled(not configpage.auto_updates and configpage.is_valid and configpage.changed) def load_plugin_pages(self): """Load the rcParams for the plugins in separate pages""" validators = psy_rcParams.validate descriptions = psy_rcParams.descriptions for ep in psy_rcParams._load_plugin_entrypoints(): plugin = ep.load() rc = getattr(plugin, 'rcParams', None) if rc is None: rc = RcParams() w = RcParamsWidget(parent=self) w.title = 'rcParams of ' + ep.module_name w.default_path = PsyRcParamsWidget.default_path w.initialize(rcParams=rc, validators=validators, descriptions=descriptions) # use the full rcParams after initialization w.rc = psy_rcParams self.add_page(w)
class DataFrameEditor(DockMixin, QWidget): """An editor for data frames""" dock_cls = DataFrameDock #: A signal that is emitted, if the table is cleared cleared = QtCore.pyqtSignal() #: A signal that is emitted when a cell has been changed. The argument #: is a tuple of two integers and one float: #: the row index, the column index and the new value cell_edited = QtCore.pyqtSignal(int, int, object, object) #: A signal that is emitted, if rows have been inserted into the dataframe. #: The first value is the integer of the (original) position of the row, #: the second one is the number of rows rows_inserted = QtCore.pyqtSignal(int, int) @property def hidden(self): return not self.table.filled def __init__(self, *args, **kwargs): super(DataFrameEditor, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) # Label for displaying the DataFrame size self.lbl_size = QLabel() # A Checkbox for enabling and disabling the editability of the index self.cb_index_editable = QCheckBox('Index editable') # A checkbox for enabling and disabling the change of data types self.cb_dtypes_changeable = QCheckBox('Datatypes changeable') # A checkbox for enabling and disabling sorting self.cb_enable_sort = QCheckBox('Enable sorting') # A button to open a dataframe from the file self.btn_open_df = QToolButton(parent=self) self.btn_open_df.setIcon(QIcon(get_icon('run_arrow.png'))) self.btn_open_df.setToolTip('Open a DataFrame from your disk') self.btn_from_console = LoadFromConsoleButton(pd.DataFrame) self.btn_from_console.setToolTip('Show a DataFrame from the console') # The table to display the DataFrame self.table = DataFrameView(pd.DataFrame(), self) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) # table clearing button self.btn_clear = QPushButton('Clear') self.btn_clear.setToolTip( 'Clear the table and disconnect from the DataFrame') # refresh button self.btn_refresh = QToolButton() self.btn_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.btn_refresh.setToolTip('Refresh the table') # close button self.btn_close = QPushButton('Close') self.btn_close.setToolTip('Close this widget permanentely') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_index_editable) hbox.addWidget(self.cb_dtypes_changeable) hbox.addWidget(self.cb_enable_sort) hbox.addWidget(self.lbl_size) hbox.addStretch(0) hbox.addWidget(self.btn_open_df) hbox.addWidget(self.btn_from_console) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_clear) hbox.addWidget(self.btn_close) hbox.addWidget(self.btn_refresh) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.cb_dtypes_changeable.stateChanged.connect( self.set_dtypes_changeable) self.cb_index_editable.stateChanged.connect(self.set_index_editable) self.btn_from_console.object_loaded.connect(self._open_ds_from_console) self.rows_inserted.connect(lambda i, n: self.set_lbl_size_text()) self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_clear.clicked.connect(self.clear_table) self.btn_close.clicked.connect(self.clear_table) self.btn_close.clicked.connect(lambda: self.close()) self.btn_refresh.clicked.connect(self.table.reset_model) self.btn_open_df.clicked.connect(self._open_dataframe) self.table.set_index_action.triggered.connect( self.update_index_editable) self.table.append_index_action.triggered.connect( self.update_index_editable) self.cb_enable_sort.stateChanged.connect( self.table.setSortingEnabled) def update_index_editable(self): model = self.table.model() if len(model.df.index.names) > 1: model.index_editable = False self.cb_index_editable.setEnabled(False) self.cb_index_editable.setChecked(model.index_editable) def set_lbl_size_text(self, nrows=None, ncols=None): """Set the text of the :attr:`lbl_size` label to display the size""" model = self.table.model() nrows = nrows if nrows is not None else model.rowCount() ncols = ncols if ncols is not None else model.columnCount() if not nrows and not ncols: self.lbl_size.setText('') else: self.lbl_size.setText('Rows: %i, Columns: %i' % (nrows, ncols)) def clear_table(self): """Clear the table and emit the :attr:`cleared` signal""" df = pd.DataFrame() self.set_df(df, show=False) def _open_ds_from_console(self, oname, df): self.set_df(df) @docstrings.dedent def set_df(self, df, *args, **kwargs): """ Fill the table from a :class:`~pandas.DataFrame` Parameters ---------- %(DataFrameModel.parameters.no_parent)s show: bool If True (default), show and raise_ the editor """ show = kwargs.pop('show', True) self.table.set_df(df, *args, **kwargs) self.set_lbl_size_text(*df.shape) model = self.table.model() self.cb_dtypes_changeable.setChecked(model.dtypes_changeable) if len(model.df.index.names) > 1: model.index_editable = False self.cb_index_editable.setEnabled(False) else: self.cb_index_editable.setEnabled(True) self.cb_index_editable.setChecked(model.index_editable) self.cleared.emit() if show: self.show_plugin() self.dock.raise_() def set_index_editable(self, state): """Set the :attr:`DataFrameModel.index_editable` attribute""" self.table.model().index_editable = state == Qt.Checked def set_dtypes_changeable(self, state): """Set the :attr:`DataFrameModel.dtypes_changeable` attribute""" self.table.model().dtypes_changeable = state == Qt.Checked def toggle_fmt_button(self, text): try: text % 1.1 except (TypeError, ValueError): self.btn_change_format.setEnabled(False) else: self.btn_change_format.setEnabled( text.strip() != self.table.model()._format) def update_format(self): """Update the format of the table""" self.table.model().set_format(self.format_editor.text().strip()) def to_dock(self, main, *args, **kwargs): connect = self.dock is None super(DataFrameEditor, self).to_dock(main, *args, **kwargs) if connect: self.dock.toggleViewAction().triggered.connect(self.maybe_tabify) def maybe_tabify(self): main = self.dock.parent() if self.is_shown and main.dockWidgetArea( main.help_explorer.dock) == main.dockWidgetArea(self.dock): main.tabifyDockWidget(main.help_explorer.dock, self.dock) def _open_dataframe(self): self.open_dataframe() def open_dataframe(self, fname=None, *args, **kwargs): """Opens a file dialog and the dataset that has been inserted""" if fname is None: fname = QFileDialog.getOpenFileName( self, 'Open dataset', os.getcwd(), 'Comma separated files (*.csv);;' 'Excel files (*.xls *.xlsx);;' 'JSON files (*.json);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if isinstance(fname, pd.DataFrame): self.set_df(fname) elif not fname: return else: ext = osp.splitext(fname)[1] open_funcs = { '.xls': pd.read_excel, '.xlsx': pd.read_excel, '.json': pd.read_json, '.tab': partial(pd.read_csv, delimiter='\t'), '.dat': partial(pd.read_csv, delim_whitespace=True), } open_func = open_funcs.get(ext, pd.read_csv) try: df = open_func(fname) except Exception: self.error_msg.showTraceback( '<b>Could not open DataFrame %s with %s</b>' % ( fname, open_func)) return self.set_df(df) def close(self, *args, **kwargs): if self.dock is not None: self.dock.close(*args, **kwargs) # removes the dock window del self.dock return super(DataFrameEditor, self).close(*args, **kwargs)
class PatternSelectionWidget(QWidget, DockMixin): """A wdiget to select patterns in the image This widget consist of an :class:`EmbededMplCanvas` to display the template for the pattern and uses the :func:`skimage.feature.match_template` function to identify it in the :attr:`arr` See Also -------- straditize.widget.selection_toolbar.SelectionToolbar.start_pattern_selection """ #: The template to look for in the :attr:`arr` template = None #: The selector to select the template in the original image selector = None #: The extents of the :attr:`template` in the original image template_extents = None #: The matplotlib artist of the :attr:`template` in the #: :attr:`template_fig` template_im = None #: The :class:`EmbededMplCanvas` to display the :attr:`template` template_fig = None axes = None #: A QSlider to set the threshold for the template correlation sl_thresh = None _corr_plot = None key_press_cid = None def __init__(self, arr, data_obj, remove_selection=False, *args, **kwargs): """ Parameters ---------- arr: np.ndarray of shape ``(Ny, Nx)`` The labeled selection array data_obj: straditize.label_selection.LabelSelection The data object whose image shall be selected remove_selection: bool If True, remove the selection on apply """ super(PatternSelectionWidget, self).__init__(*args, **kwargs) self.arr = arr self.data_obj = data_obj self.remove_selection = remove_selection self.template = None # the figure to show the template self.template_fig = EmbededMplCanvas() # the button to select the template self.btn_select_template = QPushButton('Select a template') self.btn_select_template.setCheckable(True) # the checkbox to allow fractions of the template self.fraction_box = QGroupBox('Template fractions') self.fraction_box.setCheckable(True) self.fraction_box.setChecked(False) self.fraction_box.setEnabled(False) self.sl_fraction = QSlider(Qt.Horizontal) self.lbl_fraction = QLabel('0.75') self.sl_fraction.setValue(75) # the slider to select the increments of the fractions self.sl_increments = QSlider(Qt.Horizontal) self.sl_increments.setValue(3) self.sl_increments.setMinimum(1) self.lbl_increments = QLabel('3') # the button to perform the correlation self.btn_correlate = QPushButton('Find template') self.btn_correlate.setEnabled(False) # the button to plot the correlation self.btn_plot_corr = QPushButton('Plot correlation') self.btn_plot_corr.setCheckable(True) self.btn_plot_corr.setEnabled(False) # slider for subselection self.btn_select = QPushButton('Select pattern') self.sl_thresh = QSlider(Qt.Horizontal) self.lbl_thresh = QLabel('0.5') self.btn_select.setCheckable(True) self.btn_select.setEnabled(False) self.sl_thresh.setValue(75) self.sl_thresh.setVisible(False) self.lbl_thresh.setVisible(False) # cancel and close button self.btn_cancel = QPushButton('Cancel') self.btn_close = QPushButton('Apply') self.btn_close.setEnabled(False) vbox = QVBoxLayout() vbox.addWidget(self.template_fig) hbox = QHBoxLayout() hbox.addStretch(0) hbox.addWidget(self.btn_select_template) vbox.addLayout(hbox) fraction_layout = QGridLayout() fraction_layout.addWidget(QLabel('Fraction'), 0, 0) fraction_layout.addWidget(self.sl_fraction, 0, 1) fraction_layout.addWidget(self.lbl_fraction, 0, 2) fraction_layout.addWidget(QLabel('Increments'), 1, 0) fraction_layout.addWidget(self.sl_increments, 1, 1) fraction_layout.addWidget(self.lbl_increments, 1, 2) self.fraction_box.setLayout(fraction_layout) vbox.addWidget(self.fraction_box) vbox.addWidget(self.btn_correlate) vbox.addWidget(self.btn_plot_corr) vbox.addWidget(self.btn_select) thresh_box = QHBoxLayout() thresh_box.addWidget(self.sl_thresh) thresh_box.addWidget(self.lbl_thresh) vbox.addLayout(thresh_box) hbox = QHBoxLayout() hbox.addWidget(self.btn_cancel) hbox.addWidget(self.btn_close) vbox.addLayout(hbox) self.setLayout(vbox) self.btn_select_template.clicked.connect( self.toggle_template_selection) self.sl_fraction.valueChanged.connect( lambda i: self.lbl_fraction.setText(str(i / 100.))) self.sl_increments.valueChanged.connect( lambda i: self.lbl_increments.setText(str(i))) self.btn_correlate.clicked.connect(self.start_correlation) self.btn_plot_corr.clicked.connect(self.toggle_correlation_plot) self.sl_thresh.valueChanged.connect( lambda i: self.lbl_thresh.setText(str((i - 50) / 50.))) self.sl_thresh.valueChanged.connect(self.modify_selection) self.btn_select.clicked.connect(self.toggle_selection) self.btn_cancel.clicked.connect(self.cancel) self.btn_close.clicked.connect(self.close) def toggle_template_selection(self): """Enable or disable the template selection""" if (not self.btn_select_template.isChecked() and self.selector is not None): self.selector.set_active(False) for a in self.selector.artists: a.set_visible(False) self.btn_select_template.setText('Select a template') elif self.selector is not None and self.template_im is not None: self.selector.set_active(True) for a in self.selector.artists: a.set_visible(True) self.btn_select_template.setText('Apply') else: self.selector = RectangleSelector(self.data_obj.ax, self.update_image, interactive=True) if self.template_extents is not None: self.selector.draw_shape(self.template_extents) self.key_press_cid = self.data_obj.ax.figure.canvas.mpl_connect( 'key_press_event', self.update_image) self.btn_select_template.setText('Cancel') self.data_obj.draw_figure() if self.template is not None: self.fraction_box.setEnabled(True) self.sl_increments.setMaximum(min(self.template.shape[:2])) self.btn_correlate.setEnabled(True) def update_image(self, *args, **kwargs): """Update the template image based on the :attr:`selector` extents""" if self.template_im is not None: self.template_im.remove() del self.template_im elif self.axes is None: self.axes = self.template_fig.figure.add_subplot(111) self.template_fig.figure.subplots_adjust(bottom=0.3) if not self.selector.artists[0].get_visible(): self.template_extents = None self.template = None self.btn_select_template.setText('Cancel') else: self.template_extents = np.round(self.selector.extents).astype(int) x, y = self.template_extents.reshape((2, 2)) if getattr(self.data_obj, 'extent', None) is not None: extent = self.data_obj.extent x -= int(min(extent[:2])) y -= int(min(extent[2:])) slx = slice(*sorted(x)) sly = slice(*sorted(y)) self.template = template = self.arr[sly, slx] if template.ndim == 3: self.template_im = self.axes.imshow(template) else: self.template_im = self.axes.imshow(template, cmap='binary') self.btn_select_template.setText('Apply') self.template_fig.draw() def start_correlation(self): """Look for the correlations of template and source""" if self.fraction_box.isChecked(): self._fraction = self.sl_fraction.value() / 100. increments = self.sl_increments.value() else: self._fraction = 0 increments = 1 corr = self.correlate_template(self.arr, self.template, self._fraction, increments) if corr is not None: self._correlation = corr enable = self._correlation is not None self.btn_plot_corr.setEnabled(enable) self.btn_select.setEnabled(enable) def toggle_selection(self): """Modifiy the selection (or not) based on the template correlation""" obj = self.data_obj if self.btn_select.isChecked(): self._orig_selection_arr = obj._selection_arr.copy() self._selected_labels = obj.selected_labels self._select_cmap = obj._select_cmap self._select_norm = obj._select_norm self.btn_select.setText('Reset') self.btn_close.setEnabled(True) obj.unselect_all_labels() self.sl_thresh.setVisible(True) self.lbl_thresh.setVisible(True) self.modify_selection(self.sl_thresh.value()) else: if obj._selection_arr is not None: obj._selection_arr[:] = self._orig_selection_arr obj._select_img.set_array(self._orig_selection_arr) obj.select_labels(self._selected_labels) obj._update_magni_img() del self._orig_selection_arr, self._selected_labels self.btn_select.setText('Select pattern') self.btn_close.setEnabled(False) self.sl_thresh.setVisible(False) self.lbl_thresh.setVisible(False) obj.draw_figure() def modify_selection(self, i): """Modify the selection based on the correlation threshold Parameters ---------- i: int An integer between 0 and 100, the value of the :attr:`sl_thresh` slider""" if not self.btn_select.isChecked(): return obj = self.data_obj val = (i - 50.) / 50. # select the values above 50 if not self.remove_selection: # clear the selection obj._selection_arr[:] = obj._orig_selection_arr.copy() select_val = obj._selection_arr.max() + 1 obj._selection_arr[self._correlation >= val] = select_val else: obj._selection_arr[:] = self._orig_selection_arr.copy() obj._selection_arr[self._correlation >= val] = -1 obj._select_img.set_array(obj._selection_arr) obj._update_magni_img() obj.draw_figure() def correlate_template(self, arr, template, fraction=False, increment=1, report=True): """Correlate a template with the `arr` This method uses the :func:`skimage.feature.match_template` function to find the given `template` in the source array `arr`. Parameters ---------- arr: np.ndarray of shape ``(Ny,Nx)`` The labeled selection array (see :attr:`arr`), the source of the given `template` template: np.ndarray of shape ``(nx, ny)`` The template from ``arr`` that shall be searched fraction: float If not null, we will look through the given fraction of the template to look for partial matches as well increment: int The increment of the loop with the `fraction`. report: bool If True and `fraction` is not null, a QProgressDialog is opened to inform the user about the progress""" from skimage.feature import match_template mask = self.data_obj.selected_part x = mask.any(axis=0) if not x.any(): raise ValueError("No data selected!") y = mask.any(axis=1) xmin = x.argmax() xmax = len(x) - x[::-1].argmax() ymin = y.argmax() ymax = len(y) - y[::-1].argmax() if arr.ndim == 3: mask = np.tile(mask[..., np.newaxis], (1, 1, arr.shape[-1])) src = np.where(mask[ymin:ymax, xmin:xmax], arr[ymin:ymax, xmin:xmax], 0) sny, snx = src.shape if not fraction: corr = match_template(src, template) full_shape = np.array(corr.shape) else: # loop through the template to allow partial hatches shp = np.array(template.shape, dtype=int)[:2] ny, nx = shp fshp = np.round(fraction * shp).astype(int) fny, fnx = fshp it = list( product(range(0, fny, increment), range(0, fnx, increment))) ntot = len(it) full_shape = fshp - shp + src.shape corr = np.zeros(full_shape, dtype=float) if report: txt = 'Searching template...' dialog = QProgressDialog(txt, 'Cancel', 0, ntot) dialog.setWindowModality(Qt.WindowModal) t0 = dt.datetime.now() for k, (i, j) in enumerate(it): if report: dialog.setValue(k) if k and not k % 10: passed = (dt.datetime.now() - t0).total_seconds() dialog.setLabelText(txt + ' %1.0f seconds remaning' % ((passed * (ntot / k - 1.)))) if report and dialog.wasCanceled(): return else: y_end, x_start = fshp - (i, j) - 1 sly = slice(y_end, full_shape[0]) slx = slice(0, -x_start or full_shape[1]) corr[sly, slx] = np.maximum( corr[sly, slx], match_template(src, template[:-i or ny, j:])) ret = np.zeros_like(arr, dtype=corr.dtype) dny, dnx = src.shape - full_shape for i, j in product(range(dny + 1), range(dnx + 1)): ret[ymin + i:ymax - dny + i, xmin + j:xmax - dnx + j] = np.maximum( ret[ymin + i:ymax - dny + i, xmin + j:xmax - dnx + j], corr) return np.where(mask, ret, 0) def toggle_correlation_plot(self): """Toggle the correlation plot between :attr:`template` and :attr:`arr` """ obj = self.data_obj if self._corr_plot is None: self._corr_plot = obj.ax.imshow( self._correlation, extent=obj._select_img.get_extent(), zorder=obj._select_img.zorder + 0.1) self._corr_cbar = obj.ax.figure.colorbar(self._corr_plot, orientation='vertical') self._corr_cbar.set_label('Correlation') else: for a in [self._corr_cbar, self._corr_plot]: try: a.remove() except ValueError: pass del self._corr_plot, self._corr_cbar obj.draw_figure() def to_dock(self, main, title=None, position=None, docktype='df', *args, **kwargs): if position is None: position = main.dockWidgetArea(main.help_explorer.dock) connect = self.dock is None ret = super(PatternSelectionWidget, self).to_dock(main, title, position, docktype=docktype, *args, **kwargs) if connect: self.dock.toggleViewAction().triggered.connect(self.maybe_tabify) return ret def maybe_tabify(self): main = self.dock.parent() if self.is_shown and main.dockWidgetArea( main.help_explorer.dock) == main.dockWidgetArea(self.dock): main.tabifyDockWidget(main.help_explorer.dock, self.dock) def cancel(self): if self.btn_select.isChecked(): self.btn_select.setChecked(False) self.toggle_selection() self.close() def close(self): from psyplot_gui.main import mainwindow if self.selector is not None: self.selector.disconnect_events() for a in self.selector.artists: try: a.remove() except ValueError: pass self.data_obj.draw_figure() del self.selector if self._corr_plot is not None: self.toggle_correlation_plot() if self.key_press_cid is not None: self.data_obj.ax.figure.canvas.mpl_disconnect(self.key_press_cid) for attr in ['data_obj', 'arr', 'template', 'key_press_cid']: try: delattr(self, attr) except AttributeError: pass mainwindow.removeDockWidget(self.dock) return super(PatternSelectionWidget, self).close()
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 __init__(self, straditizer, axes=None, *args, **kwargs): """ Parameters ---------- straditizer: weakref.ref The reference to the straditizer axes: matplotlib.axes.Axes The matplotlib axes corresponding to the marks """ super(MultiCrossMarksEditor, self).__init__(*args, **kwargs) self.straditizer = straditizer straditizer = straditizer() self.error_msg = PyErrorMessage(self) #: Plot the reconstructed data self.cb_plot_lines = QCheckBox('Plot reconstruction') self.cb_plot_lines.setChecked(True) # A Checkbox to automatically zoom to the selection self.cb_zoom_to_selection = QCheckBox('Zoom to selection') # A Checkbox to automaticall hide the other marks self.cb_selection_only = QCheckBox('Selection only') # A Checkbox to automatically fit the selected cells to the selected # data self.cb_fit2selection = QCheckBox( 'Fit selected cells to selected data') self.cb_fit2selection.setToolTip( 'If checked, select cells from the table and click on one of the ' 'plots to update the table with the data at the selected position.' ) # The table to display the DataFrame self.table = self.create_view(axes=axes) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) self.btn_save = QPushButton('Save') self.btn_save.setToolTip('Save the samples and continue editing') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_zoom_to_selection) hbox.addWidget(self.cb_selection_only) hbox.addWidget(self.cb_fit2selection) hbox.addWidget(self.cb_plot_lines) hbox.addStretch(0) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_save) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_save.clicked.connect(self.save_samples) straditizer.mark_added.connect(self.table.model().load_new_marks) straditizer.mark_removed.connect(self.table.model().remove_mark) self.table.selectionModel().selectionChanged.connect( self.maybe_zoom_to_selection) self.table.frozen_table_view.selectionModel().selectionChanged.connect( self.maybe_zoom_to_selection) self.table.selectionModel().selectionChanged.connect( self.maybe_show_selection_only) self.table.frozen_table_view.selectionModel().selectionChanged.connect( self.maybe_show_selection_only) self.cb_zoom_to_selection.stateChanged.connect( self.toggle_cb_zoom_to_selection) self.cb_selection_only.stateChanged.connect( self.toggle_cb_selection_only) self.cb_fit2selection.stateChanged.connect(self.toggle_fit2selection) self.cb_plot_lines.stateChanged.connect(self.toggle_plot_lines) self.toggle_plot_lines()
class Prefences(QDialog): """Preferences dialog""" @property def bt_apply(self): return self.bbox.button(QDialogButtonBox.Apply) @property def pages(self): return map(self.get_page, range(self.pages_widget.count())) def __init__(self, main=None): super(Prefences, self).__init__(parent=main) self.setWindowTitle('Preferences') # Widgets self.pages_widget = QStackedWidget() self.contents_widget = QListWidget() self.bt_reset = QPushButton('Reset to defaults') self.bt_load_plugins = QPushButton('Load plugin pages') self.bt_load_plugins.setToolTip( 'Load the rcParams for the plugins in separate pages') self.bbox = bbox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel) # Widgets setup # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle('Preferences') self.contents_widget.setMovement(QListView.Static) self.contents_widget.setSpacing(1) self.contents_widget.setCurrentRow(0) # Layout hsplitter = QSplitter() hsplitter.addWidget(self.contents_widget) hsplitter.addWidget(self.pages_widget) hsplitter.setStretchFactor(1, 1) btnlayout = QHBoxLayout() btnlayout.addWidget(self.bt_reset) btnlayout.addWidget(self.bt_load_plugins) btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addWidget(hsplitter) vlayout.addLayout(btnlayout) self.setLayout(vlayout) # Signals and slots if main is not None: self.bt_reset.clicked.connect(main.reset_rcParams) self.bt_load_plugins.clicked.connect(self.load_plugin_pages) self.pages_widget.currentChanged.connect(self.current_page_changed) self.contents_widget.currentRowChanged.connect( self.pages_widget.setCurrentIndex) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.bt_apply.clicked.connect(self.apply_clicked) self.bt_apply.setEnabled(False) def set_current_index(self, index): """Set current page index""" self.contents_widget.setCurrentRow(index) def current_page_changed(self, index): configpage = self.get_page(index) self.bt_apply.setVisible(not configpage.auto_updates) self.check_changes(configpage) def get_page(self, index=None): """Return page widget""" if index is None: widget = self.pages_widget.currentWidget() else: widget = self.pages_widget.widget(index) return widget.widget() def accept(self): """Reimplement Qt method""" for configpage in self.pages: if not configpage.is_valid: continue configpage.apply_changes() QDialog.accept(self) def apply_clicked(self): # Apply button was clicked configpage = self.get_page() if configpage.is_valid: configpage.apply_changes() self.check_changes(configpage) def add_page(self, widget): """Add a new page to the preferences dialog Parameters ---------- widget: ConfigPage The page to add""" widget.validChanged.connect(self.bt_apply.setEnabled) widget.validChanged.connect( self.bbox.button(QDialogButtonBox.Ok).setEnabled) scrollarea = QScrollArea(self) scrollarea.setWidgetResizable(True) scrollarea.setWidget(widget) self.pages_widget.addWidget(scrollarea) item = QListWidgetItem(self.contents_widget) try: item.setIcon(widget.icon) except TypeError: pass item.setText(widget.title) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setSizeHint(QtCore.QSize(0, 25)) widget.propose_changes.connect(self.check_changes) def check_changes(self, configpage): """Enable the apply button if there are changes to the settings""" if configpage != self.get_page(): return self.bt_apply.setEnabled( not configpage.auto_updates and configpage.is_valid and configpage.changed) def load_plugin_pages(self): """Load the rcParams for the plugins in separate pages""" validators = psy_rcParams.validate descriptions = psy_rcParams.descriptions for ep in psy_rcParams._load_plugin_entrypoints(): plugin = ep.load() rc = getattr(plugin, 'rcParams', None) if rc is None: rc = RcParams() w = RcParamsWidget(parent=self) w.title = 'rcParams of ' + ep.module_name w.default_path = PsyRcParamsWidget.default_path w.initialize(rcParams=rc, validators=validators, descriptions=descriptions) # use the full rcParams after initialization w.rc = psy_rcParams self.add_page(w)
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 DataFrameEditor(DockMixin, QWidget): """An editor for data frames""" dock_cls = DataFrameDock #: A signal that is emitted, if the table is cleared cleared = QtCore.pyqtSignal() #: A signal that is emitted when a cell has been changed. The argument #: is a tuple of two integers and one float: #: the row index, the column index and the new value cell_edited = QtCore.pyqtSignal(int, int, object, object) #: A signal that is emitted, if rows have been inserted into the dataframe. #: The first value is the integer of the (original) position of the row, #: the second one is the number of rows rows_inserted = QtCore.pyqtSignal(int, int) @property def hidden(self): return not self.table.filled def __init__(self, *args, **kwargs): super(DataFrameEditor, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) # Label for displaying the DataFrame size self.lbl_size = QLabel() # A Checkbox for enabling and disabling the editability of the index self.cb_index_editable = QCheckBox('Index editable') # A checkbox for enabling and disabling the change of data types self.cb_dtypes_changeable = QCheckBox('Datatypes changeable') # A checkbox for enabling and disabling sorting self.cb_enable_sort = QCheckBox('Enable sorting') # A button to open a dataframe from the file self.btn_open_df = QToolButton(parent=self) self.btn_open_df.setIcon(QIcon(get_icon('run_arrow.png'))) self.btn_open_df.setToolTip('Open a DataFrame from your disk') self.btn_from_console = LoadFromConsoleButton(pd.DataFrame) self.btn_from_console.setToolTip('Show a DataFrame from the console') # The table to display the DataFrame self.table = DataFrameView(pd.DataFrame(), self) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) # table clearing button self.btn_clear = QPushButton('Clear') self.btn_clear.setToolTip( 'Clear the table and disconnect from the DataFrame') # refresh button self.btn_refresh = QToolButton() self.btn_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.btn_refresh.setToolTip('Refresh the table') # close button self.btn_close = QPushButton('Close') self.btn_close.setToolTip('Close this widget permanentely') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_index_editable) hbox.addWidget(self.cb_dtypes_changeable) hbox.addWidget(self.cb_enable_sort) hbox.addWidget(self.lbl_size) hbox.addStretch(0) hbox.addWidget(self.btn_open_df) hbox.addWidget(self.btn_from_console) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_clear) hbox.addWidget(self.btn_close) hbox.addWidget(self.btn_refresh) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.cb_dtypes_changeable.stateChanged.connect( self.set_dtypes_changeable) self.cb_index_editable.stateChanged.connect(self.set_index_editable) self.btn_from_console.object_loaded.connect(self._open_ds_from_console) self.rows_inserted.connect(lambda i, n: self.set_lbl_size_text()) self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_clear.clicked.connect(self.clear_table) self.btn_close.clicked.connect(self.clear_table) self.btn_close.clicked.connect(lambda: self.close()) self.btn_refresh.clicked.connect(self.table.reset_model) self.btn_open_df.clicked.connect(self._open_dataframe) self.table.set_index_action.triggered.connect( self.update_index_editable) self.table.append_index_action.triggered.connect( self.update_index_editable) self.cb_enable_sort.stateChanged.connect( self.table.setSortingEnabled) def update_index_editable(self): model = self.table.model() if len(model.df.index.names) > 1: model.index_editable = False self.cb_index_editable.setEnabled(False) self.cb_index_editable.setChecked(model.index_editable) def set_lbl_size_text(self, nrows=None, ncols=None): """Set the text of the :attr:`lbl_size` label to display the size""" model = self.table.model() nrows = nrows if nrows is not None else model.rowCount() ncols = ncols if ncols is not None else model.columnCount() if not nrows and not ncols: self.lbl_size.setText('') else: self.lbl_size.setText('Rows: %i, Columns: %i' % (nrows, ncols)) def clear_table(self): """Clear the table and emit the :attr:`cleared` signal""" df = pd.DataFrame() self.set_df(df, show=False) def _open_ds_from_console(self, oname, df): self.set_df(df) @docstrings.dedent def set_df(self, df, *args, **kwargs): """ Fill the table from a :class:`~pandas.DataFrame` Parameters ---------- %(DataFrameModel.parameters.no_parent)s show: bool If True (default), show and raise_ the editor """ show = kwargs.pop('show', True) self.table.set_df(df, *args, **kwargs) self.set_lbl_size_text(*df.shape) model = self.table.model() self.cb_dtypes_changeable.setChecked(model.dtypes_changeable) if len(model.df.index.names) > 1: model.index_editable = False self.cb_index_editable.setEnabled(False) else: self.cb_index_editable.setEnabled(True) self.cb_index_editable.setChecked(model.index_editable) self.cleared.emit() if show: self.show_plugin() self.dock.raise_() def set_index_editable(self, state): """Set the :attr:`DataFrameModel.index_editable` attribute""" self.table.model().index_editable = state == Qt.Checked def set_dtypes_changeable(self, state): """Set the :attr:`DataFrameModel.dtypes_changeable` attribute""" self.table.model().dtypes_changeable = state == Qt.Checked def toggle_fmt_button(self, text): try: text % 1.1 except (TypeError, ValueError): self.btn_change_format.setEnabled(False) else: self.btn_change_format.setEnabled( text.strip() != self.table.model()._format) def update_format(self): """Update the format of the table""" self.table.model().set_format(self.format_editor.text().strip()) def to_dock(self, main, *args, **kwargs): connect = self.dock is None super(DataFrameEditor, self).to_dock(main, *args, **kwargs) if connect: self.dock.toggleViewAction().triggered.connect(self.maybe_tabify) def maybe_tabify(self): main = self.dock.parent() if self.is_shown and main.dockWidgetArea( main.help_explorer.dock) == main.dockWidgetArea(self.dock): main.tabifyDockWidget(main.help_explorer.dock, self.dock) def _open_dataframe(self): self.open_dataframe() def open_dataframe(self, fname=None, *args, **kwargs): """Opens a file dialog and the dataset that has been inserted""" if fname is None: fname = QFileDialog.getOpenFileName( self, 'Open dataset', os.getcwd(), 'Comma separated files (*.csv);;' 'Excel files (*.xls *.xlsx);;' 'JSON files (*.json);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if isinstance(fname, pd.DataFrame): self.set_df(fname) elif not fname: return else: ext = osp.splitext(fname)[1] open_funcs = { '.xls': pd.read_excel, '.xlsx': pd.read_excel, '.json': pd.read_json, '.tab': partial(pd.read_csv, delimiter='\t'), '.dat': partial(pd.read_csv, delim_whitespace=True), } open_func = open_funcs.get(ext, pd.read_csv) try: df = open_func(fname) except Exception: self.error_msg.showTraceback( '<b>Could not open DataFrame %s with %s</b>' % ( fname, open_func)) return self.set_df(df) def close(self, *args, **kwargs): if self.dock is not None: self.dock.close(*args, **kwargs) # removes the dock window del self.dock return super(DataFrameEditor, self).close(*args, **kwargs)
def __init__(self, *args, **kwargs): super(DataFrameEditor, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) # Label for displaying the DataFrame size self.lbl_size = QLabel() # A Checkbox for enabling and disabling the editability of the index self.cb_index_editable = QCheckBox('Index editable') # A checkbox for enabling and disabling the change of data types self.cb_dtypes_changeable = QCheckBox('Datatypes changeable') # A checkbox for enabling and disabling sorting self.cb_enable_sort = QCheckBox('Enable sorting') # A button to open a dataframe from the file self.btn_open_df = QToolButton(parent=self) self.btn_open_df.setIcon(QIcon(get_icon('run_arrow.png'))) self.btn_open_df.setToolTip('Open a DataFrame from your disk') self.btn_from_console = LoadFromConsoleButton(pd.DataFrame) self.btn_from_console.setToolTip('Show a DataFrame from the console') # The table to display the DataFrame self.table = DataFrameView(pd.DataFrame(), self) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) # table clearing button self.btn_clear = QPushButton('Clear') self.btn_clear.setToolTip( 'Clear the table and disconnect from the DataFrame') # refresh button self.btn_refresh = QToolButton() self.btn_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.btn_refresh.setToolTip('Refresh the table') # close button self.btn_close = QPushButton('Close') self.btn_close.setToolTip('Close this widget permanentely') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_index_editable) hbox.addWidget(self.cb_dtypes_changeable) hbox.addWidget(self.cb_enable_sort) hbox.addWidget(self.lbl_size) hbox.addStretch(0) hbox.addWidget(self.btn_open_df) hbox.addWidget(self.btn_from_console) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_clear) hbox.addWidget(self.btn_close) hbox.addWidget(self.btn_refresh) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.cb_dtypes_changeable.stateChanged.connect( self.set_dtypes_changeable) self.cb_index_editable.stateChanged.connect(self.set_index_editable) self.btn_from_console.object_loaded.connect(self._open_ds_from_console) self.rows_inserted.connect(lambda i, n: self.set_lbl_size_text()) self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_clear.clicked.connect(self.clear_table) self.btn_close.clicked.connect(self.clear_table) self.btn_close.clicked.connect(lambda: self.close()) self.btn_refresh.clicked.connect(self.table.reset_model) self.btn_open_df.clicked.connect(self._open_dataframe) self.table.set_index_action.triggered.connect( self.update_index_editable) self.table.append_index_action.triggered.connect( self.update_index_editable) self.cb_enable_sort.stateChanged.connect( self.table.setSortingEnabled)
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 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, arr, data_obj, remove_selection=False, *args, **kwargs): """ Parameters ---------- arr: np.ndarray of shape ``(Ny, Nx)`` The labeled selection array data_obj: straditize.label_selection.LabelSelection The data object whose image shall be selected remove_selection: bool If True, remove the selection on apply """ super(PatternSelectionWidget, self).__init__(*args, **kwargs) self.arr = arr self.data_obj = data_obj self.remove_selection = remove_selection self.template = None # the figure to show the template self.template_fig = EmbededMplCanvas() # the button to select the template self.btn_select_template = QPushButton('Select a template') self.btn_select_template.setCheckable(True) # the checkbox to allow fractions of the template self.fraction_box = QGroupBox('Template fractions') self.fraction_box.setCheckable(True) self.fraction_box.setChecked(False) self.fraction_box.setEnabled(False) self.sl_fraction = QSlider(Qt.Horizontal) self.lbl_fraction = QLabel('0.75') self.sl_fraction.setValue(75) # the slider to select the increments of the fractions self.sl_increments = QSlider(Qt.Horizontal) self.sl_increments.setValue(3) self.sl_increments.setMinimum(1) self.lbl_increments = QLabel('3') # the button to perform the correlation self.btn_correlate = QPushButton('Find template') self.btn_correlate.setEnabled(False) # the button to plot the correlation self.btn_plot_corr = QPushButton('Plot correlation') self.btn_plot_corr.setCheckable(True) self.btn_plot_corr.setEnabled(False) # slider for subselection self.btn_select = QPushButton('Select pattern') self.sl_thresh = QSlider(Qt.Horizontal) self.lbl_thresh = QLabel('0.5') self.btn_select.setCheckable(True) self.btn_select.setEnabled(False) self.sl_thresh.setValue(75) self.sl_thresh.setVisible(False) self.lbl_thresh.setVisible(False) # cancel and close button self.btn_cancel = QPushButton('Cancel') self.btn_close = QPushButton('Apply') self.btn_close.setEnabled(False) vbox = QVBoxLayout() vbox.addWidget(self.template_fig) hbox = QHBoxLayout() hbox.addStretch(0) hbox.addWidget(self.btn_select_template) vbox.addLayout(hbox) fraction_layout = QGridLayout() fraction_layout.addWidget(QLabel('Fraction'), 0, 0) fraction_layout.addWidget(self.sl_fraction, 0, 1) fraction_layout.addWidget(self.lbl_fraction, 0, 2) fraction_layout.addWidget(QLabel('Increments'), 1, 0) fraction_layout.addWidget(self.sl_increments, 1, 1) fraction_layout.addWidget(self.lbl_increments, 1, 2) self.fraction_box.setLayout(fraction_layout) vbox.addWidget(self.fraction_box) vbox.addWidget(self.btn_correlate) vbox.addWidget(self.btn_plot_corr) vbox.addWidget(self.btn_select) thresh_box = QHBoxLayout() thresh_box.addWidget(self.sl_thresh) thresh_box.addWidget(self.lbl_thresh) vbox.addLayout(thresh_box) hbox = QHBoxLayout() hbox.addWidget(self.btn_cancel) hbox.addWidget(self.btn_close) vbox.addLayout(hbox) self.setLayout(vbox) self.btn_select_template.clicked.connect( self.toggle_template_selection) self.sl_fraction.valueChanged.connect( lambda i: self.lbl_fraction.setText(str(i / 100.))) self.sl_increments.valueChanged.connect( lambda i: self.lbl_increments.setText(str(i))) self.btn_correlate.clicked.connect(self.start_correlation) self.btn_plot_corr.clicked.connect(self.toggle_correlation_plot) self.sl_thresh.valueChanged.connect( lambda i: self.lbl_thresh.setText(str((i - 50) / 50.))) self.sl_thresh.valueChanged.connect(self.modify_selection) self.btn_select.clicked.connect(self.toggle_selection) self.btn_cancel.clicked.connect(self.cancel) self.btn_close.clicked.connect(self.close)
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())
class ImageRotator(StraditizerControlBase, QWidget): """Widget to rotate the image This control mainly adds a QLineEdit :attr:`txt_rotate` to the :class:`straditize.widgets.StraditizerWidgets` to rotate the image. It also enables the user to specify the rotation angle using two connected :class:`~straditize.cross_mark.CrossMarks`. Here the user can decide between a horizontal alignment (:attr:`btn_rotate_horizontal`) or a vertical alignment (:attr:`btn_rotate_vertical`)""" _rotating = False _ha = False _va = False #: A QPushButton for horizontal alignment btn_rotate_horizontal = None #: A QPushButton for vertical alignment btn_rotate_vertical = None #: A QLineEdit to display the rotation angle txt_rotate = None @docstrings.dedent def __init__(self, straditizer_widgets, item=None, *args, **kwargs): """ Parameters ---------- %(StraditizerControlBase.init_straditizercontrol.parameters)s""" super(ImageRotator, self).__init__(*args, **kwargs) self.txt_rotate = QLineEdit() self.txt_rotate.setValidator(QDoubleValidator()) self.btn_rotate_horizontal = QPushButton('Horizontal alignment') self.btn_rotate_horizontal.setToolTip( 'Mark two points that should be on the same horizontal level ' 'and rotate the picture to achieve this.') self.btn_rotate_vertical = QPushButton('Vertical alignment') self.btn_rotate_vertical.setToolTip( 'Mark two points that should be on the same vertical level ' 'and rotate the picture to achieve this.') self.init_straditizercontrol(straditizer_widgets, item) # --------------------------------------------------------------------- # --------------------------- Layouts --------------------------------- # --------------------------------------------------------------------- layout = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel('Rotate:')) hbox.addWidget(self.txt_rotate) layout.addLayout(hbox) layout.addWidget(self.btn_rotate_horizontal) layout.addWidget(self.btn_rotate_vertical) self.setLayout(layout) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.txt_rotate.textChanged.connect(self.start_rotation) self.btn_rotate_horizontal.clicked.connect( self.start_horizontal_alignment) self.btn_rotate_vertical.clicked.connect( self.start_vertical_alignment) self.widgets2disable = [self.txt_rotate, self.btn_rotate_horizontal, self.btn_rotate_vertical] def should_be_enabled(self, w): if not self._rotating: return self.straditizer is not None elif w is self.txt_rotate: return True return False def start_rotation(self): """Start the rotation (if not already started)""" if not self._rotating: self._rotating = True self.connect2apply(self.rotate_image) self.connect2cancel(self.remove_marks, self.straditizer.draw_figure) self.straditizer_widgets.apply_button.setText('Rotate') def draw_figure(self): self.straditizer.draw_figure() def start_horizontal_alignment(self): """Start the horizontal alignment""" self.start_rotation() self._ha = True self._start_alignment() def start_vertical_alignment(self): """Start the vertical alignment""" self.start_rotation() self._va = True self._start_alignment() def _start_alignment(self): def new_mark(pos): if len(self.straditizer.marks) == 2: return mark = cm.CrossMarks(pos, ax=stradi.ax, c='b') if self.straditizer.marks: marks = [mark] + self.straditizer.marks mark.connect_marks(marks, visible=True) self.update_txt_rotate(marks=marks) mark.moved.connect(self.update_txt_rotate) return [mark] stradi = self.straditizer stradi.marks = [] stradi.mark_cids.add(stradi.fig.canvas.mpl_connect( 'button_press_event', stradi._add_mark_event(new_mark))) stradi.mark_cids.add(stradi.fig.canvas.mpl_connect( 'button_press_event', stradi._remove_mark_event)) def update_txt_rotate(self, *args, marks=[], **kwargs): """Update the :attr:`txt_rotate` from the displayed cross marks""" stradi = self.straditizer marks = marks or stradi.marks if len(marks) != 2: return x0, y0 = marks[0].pos x1, y1 = marks[1].pos dx = x0 - x1 dy = y0 - y1 if self._ha: angle = np.arctan(dy / dx) if dx else np.sign(dy) * np.pi / 2. self.txt_rotate.setText('%1.3g' % np.rad2deg(angle)) elif self._va: angle = np.arctan(dx / dy) if dy else np.sign(dx) * np.pi / 2. self.txt_rotate.setText('%1.3g' % -np.rad2deg(angle)) def enable_or_disable_widgets(self, b): super(ImageRotator, self).enable_or_disable_widgets(b) if self._rotating: self.txt_rotate.setEnabled(self.should_be_enabled(self.txt_rotate)) @property def angle(self): """The rotation angle from :attr:`txt_rotate` as a float""" angle = self.txt_rotate.text() if not angle.strip(): return return float(angle.strip()) def rotate_image(self): """Rotate the image based on the specified :attr:`angle`""" angle = self.angle if angle is None: return sw = self.straditizer_widgets answer = QMessageBox.Yes if sw.always_yes else QMessageBox.question( self, 'Restart project?', 'This will close the straditizer and create new figures. ' 'Are you sure, you want to continue?') if answer == QMessageBox.Yes: image = self.straditizer.image.rotate(angle, expand=True) attrs = self.straditizer.attrs self.straditizer_widgets.close_straditizer() self.straditizer_widgets.menu_actions.open_straditizer( image, attrs=attrs) self._rotating = self._ha = self._va = False def remove_marks(self): """Remove the cross marks used for the rotation angle""" self._rotating = self._ha = self._va = False self.straditizer.remove_marks() self.straditizer_widgets.apply_button.setText('Apply')
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 PlotControl(StraditizerControlBase, QWidget): """A widget for controlling the plot This widgets holds a :class:`PlotControlTable` to display visual diagnostics in the plot. Additionally it contains zoom buttons (:attr:`btn_view_global` and :attr:`btn_view_data`) and a widget to plot the results (:attr:`results_plot`)""" #: A :class:`PlotControlTable` to display visual diagnostics table = None #: A button to zoom out to the entire stratigraphic diagram #: (see :meth:`zoom_global`) btn_view_global = None #: A button to zoom to the data #: (see :meth:`zoom_data`) btn_view_data = None #: A :class:`ResultsPlot` to plot the digitized data in a new diagram results_plot = None def __init__(self, straditizer_widgets, item, *args, **kwargs): super(PlotControl, self).__init__(*args, **kwargs) self.btn_view_global = QPushButton('Zoom out') self.btn_view_data = QPushButton('Zoom to data') self.table = PlotControlTable(straditizer_widgets) self.results_plot = ResultsPlot(straditizer_widgets) self.init_straditizercontrol(straditizer_widgets, item) # --------------------------------------------------------------------- # ------------------------------ Layout ------------------------------- # --------------------------------------------------------------------- hbox = QHBoxLayout() hbox.addWidget(self.btn_view_global) hbox.addWidget(self.btn_view_data) vbox = QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(self.table) self.setLayout(vbox) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.btn_view_global.clicked.connect(self.zoom_global) self.btn_view_data.clicked.connect(self.zoom_data) def setup_children(self, item): super().setup_children(item) child = QTreeWidgetItem(0) item.addChild(child) self.results_plot.setup_children(child) def zoom_global(self): """Zoom out to the full straditgraphic diagram See Also -------- straditize.straditizer.Straditizer.show_full_image""" self.straditizer.show_full_image() self.straditizer.draw_figure() def zoom_data(self): """Zoom to the data part See Also -------- straditize.straditizer.Straditizer.show_data_diagram""" self.straditizer.show_data_diagram() self.straditizer.draw_figure() def refresh(self): self.table.refresh() self.results_plot.refresh() if self.straditizer is None: self.btn_view_global.setEnabled(False) self.btn_view_data.setEnabled(False) else: self.btn_view_global.setEnabled(True) self.btn_view_data.setEnabled( self.straditizer.data_xlim is not None and self.straditizer.data_ylim is not None)
class MultiCrossMarksEditor(DockMixin, QWidget): """An editor for cross marks in multiple axes""" #: The QDockWidget for the :class:`DataFrameEditor` dock_cls = DataFrameDock #: A :class:`weakref` to the #: :attr:`~straditize.widgets.StraditizerWidgets.straditizer` straditizer = None def __init__(self, straditizer, axes=None, *args, **kwargs): """ Parameters ---------- straditizer: weakref.ref The reference to the straditizer axes: matplotlib.axes.Axes The matplotlib axes corresponding to the marks """ super(MultiCrossMarksEditor, self).__init__(*args, **kwargs) self.straditizer = straditizer straditizer = straditizer() self.error_msg = PyErrorMessage(self) #: Plot the reconstructed data self.cb_plot_lines = QCheckBox('Plot reconstruction') self.cb_plot_lines.setChecked(True) # A Checkbox to automatically zoom to the selection self.cb_zoom_to_selection = QCheckBox('Zoom to selection') # A Checkbox to automaticall hide the other marks self.cb_selection_only = QCheckBox('Selection only') # A Checkbox to automatically fit the selected cells to the selected # data self.cb_fit2selection = QCheckBox( 'Fit selected cells to selected data') self.cb_fit2selection.setToolTip( 'If checked, select cells from the table and click on one of the ' 'plots to update the table with the data at the selected position.' ) # The table to display the DataFrame self.table = self.create_view(axes=axes) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) self.btn_save = QPushButton('Save') self.btn_save.setToolTip('Save the samples and continue editing') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_zoom_to_selection) hbox.addWidget(self.cb_selection_only) hbox.addWidget(self.cb_fit2selection) hbox.addWidget(self.cb_plot_lines) hbox.addStretch(0) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_save) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_save.clicked.connect(self.save_samples) straditizer.mark_added.connect(self.table.model().load_new_marks) straditizer.mark_removed.connect(self.table.model().remove_mark) self.table.selectionModel().selectionChanged.connect( self.maybe_zoom_to_selection) self.table.frozen_table_view.selectionModel().selectionChanged.connect( self.maybe_zoom_to_selection) self.table.selectionModel().selectionChanged.connect( self.maybe_show_selection_only) self.table.frozen_table_view.selectionModel().selectionChanged.connect( self.maybe_show_selection_only) self.cb_zoom_to_selection.stateChanged.connect( self.toggle_cb_zoom_to_selection) self.cb_selection_only.stateChanged.connect( self.toggle_cb_selection_only) self.cb_fit2selection.stateChanged.connect(self.toggle_fit2selection) self.cb_plot_lines.stateChanged.connect(self.toggle_plot_lines) self.toggle_plot_lines() def create_view(self, axes=None): """Create the :class:`MultiCrossMarksView` of the editor Parameters ---------- axes: list of :class:`matplotlib.axes.Axes` The matplotlib axes for the marks""" stradi = self.straditizer() reader = stradi.data_reader df = getattr(stradi, '_plotted_full_df', reader._full_df).copy() df.columns = [ str(i) if str(i) == colname else '%s (%i)' % (colname, i) for i, colname in enumerate(stradi.colnames_reader.column_names + ['nextrema']) ] return MultiCrossMarksView(stradi.marks, df, df.columns, self.straditizer, axes=axes, occurences_value=reader.occurences_value) def save_samples(self): """Save the samples to the :attr:`straditizer` without removing them""" self.straditizer().update_samples_sep(remove=False) def maybe_zoom_to_selection(self): if self.cb_zoom_to_selection.isChecked(): self.table.zoom_to_selection() def maybe_show_selection_only(self): if self.cb_selection_only.isChecked(): self.table.show_selected_marks_only() def toggle_cb_zoom_to_selection(self): if self.cb_zoom_to_selection.isChecked(): self.table.zoom_to_selection() def toggle_cb_selection_only(self): if self.cb_selection_only.isChecked(): self.table.show_selected_marks_only() else: self.table.show_all_marks() def toggle_fit2selection(self): """Enable the fitting so selected digitized data""" model = self.table.model() fig = model.fig if self.cb_fit2selection.isChecked(): self._fit2selection_cid = fig.canvas.mpl_connect( 'button_press_event', self._fit2selection) elif self._fit2selection_cid is not None: fig.canvas.mpl_disconnect(self._fit2selection_cid) del self._fit2selection_cid def _fit2selection(self, event): model = self.table.model() if (not event.inaxes or event.button != 1 or model.fig.canvas.manager.toolbar.mode != ''): return y = int(np.round(event.ydata)) data = self.table.full_df.loc[y] indexes = list(self.table.selectedIndexes()) mark = None for index in indexes: row = index.row() col = index.column() if col == 0: # index column continue mark = model.get_cell_mark(row, col) old_pos = mark.pos xa = data[col - 1] if np.isnan(xa): xa = 0 mark.set_pos((xa, mark.ya)) mark.moved.emit(old_pos, mark) if mark is not None: mark.fig.canvas.draw() def toggle_fmt_button(self, text): try: text % 1.1 except (TypeError, ValueError): self.btn_change_format.setEnabled(False) else: self.btn_change_format.setEnabled( text.strip() != self.table.model()._format) def toggle_plot_lines(self): model = self.table.model() if self.cb_plot_lines.isChecked(): model.plot_lines() else: model.remove_lines() def update_format(self): """Update the format of the table""" self.table.model().set_format(self.format_editor.text().strip()) def to_dock(self, main, title=None, position=None, docktype='df', *args, **kwargs): if position is None: if main.centralWidget() is not main.help_explorer: position = main.dockWidgetArea(main.help_explorer.dock) else: position = Qt.RightDockWidgetArea connect = self.dock is None ret = super(MultiCrossMarksEditor, self).to_dock(main, title, position, docktype=docktype, *args, **kwargs) if connect: self.dock.toggleViewAction().triggered.connect(self.maybe_tabify) return ret def maybe_tabify(self): main = self.dock.parent() if self.is_shown and main.dockWidgetArea( main.help_explorer.dock) == main.dockWidgetArea(self.dock): main.tabifyDockWidget(main.help_explorer.dock, self.dock)
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, parent, fmto, artist=None, base=None): """ Parameters ---------- %(FontWeightWidget.parameters)s """ QWidget.__init__(self, parent) hbox = QHBoxLayout() if artist is not None: self.current_font = self.artist_to_qfont(artist) self.current_color = QtGui.QColor.fromRgbF( *mcol.to_rgba(artist.get_color())) else: self.current_color = QtGui.QColor(Qt.black) self.fmto_name = fmto.name or fmto.key # choose font button button = QPushButton('Choose font') button.clicked.connect(partial(self.choose_font, None)) hbox.addWidget(button) # font size spin box 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(self.modify_size) hbox.addWidget(spin_box) # font color button self.btn_font_color = button = QToolButton(self) button.setIcon(QIcon(get_icon('font_color.png'))) button.clicked.connect(partial(self.choose_color, None)) hbox.addWidget(button) # bold button self.btn_bold = button = QToolButton(self) button.setIcon(QIcon(get_icon('bold.png'))) button.clicked.connect(self.toggle_bold) button.setCheckable(True) if artist is not None: button.setChecked(self.current_font.weight() > 50) hbox.addWidget(button) # italic button self.btn_italic = button = QToolButton(self) button.setIcon(QIcon(get_icon('italic.png'))) button.clicked.connect(self.toggle_italic) button.setCheckable(True) if artist is not None: button.setChecked(self.current_font.italic()) hbox.addWidget(button) if base is not None: # add a button to change to the base formatoption fmtos = [ base, getattr(fmto.plotter, base.key + 'size', None), getattr(fmto.plotter, base.key + 'weight', None), ] fmtos = list(filter(None, fmtos)) hbox.addWidget(Switch2FmtButton(parent, *fmtos)) self.setLayout(hbox)
class StackedReader(DataReader, StraditizerControlBase): """A DataReader for stacked area plots This reader only works within the straditizer GUI because the digitization (see :meth:`digitize`) is interactive. The user has to manually distinguish the stacked variables.""" #: The QTreeWidgetItem that holds the digitization widgets digitize_child = None #: A QPushButton to select the previous variable during the digitization #: (see :meth:`decrease_current_col`) btn_prev = None #: A QPushButton to select the next variable during the digitization #: (see :meth:`increase_current_col`) btn_next = None #: A QPushButton to select the features in the image for the current #: variable (see :meth:`select_current_column`) btn_edit = None #: A QPushButton to add a new variable to the current ones #: (see :meth:`select_and_add_current_column`) btn_add = None #: A QLabel to display the current column lbl_col = None strat_plot_identifier = 'stacked' _current_col = 0 def digitize(self): """Digitize the data interactively This method creates a new child item for the digitize button in the straditizer control to manually distinguish the variables in the stacked diagram.""" if getattr(self, 'straditizer_widgets', None) is None: self.init_straditizercontrol(get_straditizer_widgets()) digitizer = self.straditizer_widgets.digitizer digitizing = digitizer.btn_digitize.isChecked() if digitizing and self.digitize_child is None: raise ValueError("Apparently another digitization is in progress!") elif not digitizing and self.digitize_child is None: if len(self.columns) == 1 or self._current_col not in self.columns: self._current_col = self.columns[0] if len(self.columns) == 1: super(StackedReader, self).digitize() # start digitization digitizer.btn_digitize.setCheckable(True) digitizer.btn_digitize.setChecked(True) self._init_digitize_child() # Disable the changing of readers digitizer.cb_readers.setEnabled(False) digitizer.tree.expandItem(digitizer.digitize_item) self.enable_or_disable_navigation_buttons() self.reset_lbl_col() elif not digitizing: # stop digitization digitizer.btn_digitize.setChecked(False) digitizer.btn_digitize.setCheckable(False) self._remove_digitze_child(digitizer) digitizer.cb_readers.setEnabled( digitizer.should_be_enabled(digitizer.cb_readers)) del self.straditizer_widgets def _init_digitize_child(self): self.lbl_col = QLabel('') self.btn_prev = QPushButton('<') self.btn_next = QPushButton('>') self.btn_edit = QPushButton('Edit') self.btn_add = QPushButton('+') self.reset_lbl_col() self.btn_box = w = QWidget() vbox = QVBoxLayout() vbox.addWidget(self.lbl_col) hbox = QHBoxLayout() hbox.addWidget(self.btn_prev) hbox.addWidget(self.btn_next) hbox.addWidget(self.btn_edit) hbox.addWidget(self.btn_add) vbox.addLayout(hbox) w.setLayout(vbox) self.digitize_child = QTreeWidgetItem(0) self.straditizer_widgets.digitizer.digitize_item.addChild( self.digitize_child) self.straditizer_widgets.digitizer.tree.setItemWidget( self.digitize_child, 0, w) self.widgets2disable = [self.btn_prev, self.btn_next, self.btn_edit, self.btn_add] self.btn_next.clicked.connect(self.increase_current_col) self.btn_prev.clicked.connect(self.decrease_current_col) self.btn_edit.clicked.connect(self.select_current_column) self.btn_add.clicked.connect(self.select_and_add_current_column) def reset_lbl_col(self): """Reset the :attr:`lbl_col` to display the current column""" self.lbl_col.setText('Part %i of %i' % ( self.columns.index(self._current_col) + 1, len(self.columns))) def increase_current_col(self): """Take the next column as the current column""" self._current_col = min(self.columns[-1], self._current_col + 1) self.reset_lbl_col() self.enable_or_disable_navigation_buttons() def decrease_current_col(self): """Take the previous column as the current column""" self._current_col = max(self.columns[0], self._current_col - 1) self.reset_lbl_col() self.enable_or_disable_navigation_buttons() def _remove_digitze_child(self, digitizer): digitizer.digitize_item.takeChild( digitizer.digitize_item.indexOfChild( self.digitize_child)) digitizer.btn_digitize.setChecked(False) digitizer.btn_digitize.setCheckable(False) for btn in self.widgets2disable: btn.clicked.disconnect() del (self.digitize_child, self.btn_prev, self.btn_next, self.btn_add, self.btn_edit, self.lbl_col, self.btn_box) self.widgets2disable.clear() def enable_or_disable_navigation_buttons(self): """Enable or disable :attr:`btn_prev` and :attr:`btn_next` Depending on the current column, we disable the navigation buttons :attr:`btn_prev` and :attr:`btn_next`""" disable_all = self.columns is None or len(self.columns) == 1 self.btn_prev.setEnabled(not disable_all and self._current_col != self.columns[0]) self.btn_next.setEnabled(not disable_all and self._current_col != self.columns[-1]) def select_and_add_current_column(self): """Select the features for a column and create it as a new one""" return self._select_current_column(True) def select_current_column(self): """Select the features of the current column""" return self._select_current_column() def _select_current_column(self, add_on_apply=False): image = self.to_grey_pil(self.image).astype(int) + 1 start = self.start_of_current_col end = start + self.full_df[self._current_col].values all_end = start + self.full_df.loc[:, self._current_col:].values.sum( axis=1) x = np.meshgrid(*map(np.arange, image.shape[::-1]))[0] image[(x < start[:, np.newaxis]) | (x > all_end[:, np.newaxis])] = 0 labels = skim.label(image, 8) self.straditizer_widgets.selection_toolbar.data_obj = self self.apply_button.clicked.connect( self.add_col if add_on_apply else self.update_col) self.apply_button.clicked.connect(self.update_plotted_full_df) self.straditizer_widgets.selection_toolbar.start_selection( labels, rgba=self.image_array(), remove_on_apply=False) self.select_all_labels() # set values outside the current column to 0 self._selection_arr[(x < start[:, np.newaxis]) | (x >= end[:, np.newaxis])] = -1 self._select_img.set_array(self._selection_arr) self.draw_figure() @property def start_of_current_col(self): """The first x-pixel of the current column""" if self._current_col == self.columns[0]: start = np.zeros(self.binary.shape[:1]) else: idx = self.columns.index(self._current_col) start = self.full_df.iloc[:, :idx].values.sum(axis=1) start += self.column_starts[0] return start def update_plotted_full_df(self): """Update the plotted full_df if it is shown See Also -------- plot_full_df""" pc = self.straditizer_widgets.plot_control.table if pc.can_plot_full_df() and pc.get_full_df_lines(): pc.remove_full_df_plot() pc.plot_full_df() def update_col(self): """Update the current column based on the selection. This method updates the end of the current column and adds or removes the changes from the columns to the right.""" current = self._current_col start = self.start_of_current_col selected = self.selected_part end = (self.binary.shape[1] - selected[:, ::-1].argmax(axis=1) - start) not_selected = ~selected.any() end[not_selected] = 0 diff_end = self.parent._full_df.loc[:, current] - end self.parent._full_df.loc[:, current] = end if current != self.columns[-1]: self.parent._full_df.loc[:, current + 1] += diff_end def get_binary_for_col(self, col): s, e = self.column_bounds[self.columns.index(col)] if self.parent._full_df is None: return self.binary[:, s:e] else: vals = self.full_df.loc[:, col].values ret = np.zeros((self.binary.shape[0], int(vals.max()))) dist = np.tile(np.arange(ret.shape[1])[np.newaxis], (len(ret), 1)) ret[dist <= vals[:, np.newaxis]] = 1 return ret def add_col(self): """Create a column out of the current selection""" def increase_col_nums(df): df_cols = df.columns.values df_cols[df_cols >= current] += 1 df.columns = df_cols current = self._current_col start = self.start_of_current_col selected = self.selected_part end = (self.binary.shape[1] - selected[:, ::-1].argmax(axis=1) - start) not_selected = ~selected.any() end[not_selected] = 0 # ----- Update of reader column numbers ----- for reader in self.iter_all_readers: for i, col in enumerate(reader.columns): if col >= current: reader.columns[i] += 1 self.columns.insert(self.columns.index(current + 1), current) self.parent._column_starts = np.insert( self.parent._column_starts, current, self._column_starts[current]) if self.parent._column_ends is not None: self.parent._column_ends = np.insert( self.parent._column_ends, current, self.parent._column_ends[current]) # ----- Update of column numbers in dataframes ----- # increase column numbers in full_df full_df = self.parent._full_df increase_col_nums(full_df) # increase column numbers in samples samples = self.parent._sample_locs if samples is not None: increase_col_nums(samples) # ----- Update of DataFrames ----- # update the current column in full_df and add the new one full_df.loc[:, current + 1] -= end full_df[current] = end full_df.sort_index(axis=1, inplace=True) # update the current column in samples and add the new one if samples is not None: new_samples = full_df.loc[samples.index, current] samples.loc[:, current + 1] -= new_samples samples[current] = new_samples samples.sort_index(axis=1, inplace=True) rough_locs = self.parent.rough_locs if rough_locs is not None: rough_locs[(current + 1, 'vmin')] = rough_locs[(current, 'vmin')] rough_locs[(current + 1, 'vmax')] = rough_locs[(current, 'vmax')] rough_locs.loc[:, current] = -1 rough_locs.sort_index(inplace=True, level=0) self.reset_lbl_col() self.enable_or_disable_navigation_buttons() def plot_full_df(self, ax=None): """Plot the lines for the digitized diagram""" vals = self.full_df.values starts = self.column_starts self.lines = lines = [] y = np.arange(np.shape(self.image)[0]) ax = ax or self.ax if self.extent is not None: y += self.extent[-1] starts += self.extent[0] x = np.zeros_like(vals[:, 0]) + starts[0] for i in range(vals.shape[1]): x += vals[:, i] lines.extend(ax.plot(x.copy(), y, lw=2.0)) def plot_potential_samples(self, excluded=False, ax=None, plot_kws={}, *args, **kwargs): """Plot the ranges for potential samples""" vals = self.full_df.values.copy() starts = self.column_starts.copy() self.sample_ranges = lines = [] y = np.arange(np.shape(self.image)[0]) ax = ax or self.ax plot_kws = dict(plot_kws) plot_kws.setdefault('marker', '+') if self.extent is not None: y += self.extent[-1] starts = starts + self.extent[0] x = np.zeros(vals.shape[0]) + starts[0] for i, (col, arr) in enumerate(zip(self.columns, vals.T)): all_indices, excluded_indices = self.find_potential_samples( i, *args, **kwargs) if excluded: all_indices = excluded_indices if not all_indices: x += arr continue mask = np.ones(arr.size, dtype=bool) for imin, imax in all_indices: mask[imin:imax] = False for imin, imax in all_indices: lines.extend(ax.plot( np.where(mask, np.nan, arr)[imin:imax] + x[imin:imax], y[imin:imax], **plot_kws)) x += arr def resize_axes(self, grouper, bounds): """Reimplemented to do nothing""" xmin = bounds.min() xmax = bounds.max() grouper.plotters[0].update(xlim=(xmin, xmax)) return
def __init__(self, *args, **kwargs): super(DataFrameEditor, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) # Label for displaying the DataFrame size self.lbl_size = QLabel() # A Checkbox for enabling and disabling the editability of the index self.cb_index_editable = QCheckBox('Index editable') # A checkbox for enabling and disabling the change of data types self.cb_dtypes_changeable = QCheckBox('Datatypes changeable') # A checkbox for enabling and disabling sorting self.cb_enable_sort = QCheckBox('Enable sorting') # A button to open a dataframe from the file self.btn_open_df = QToolButton(parent=self) self.btn_open_df.setIcon(QIcon(get_icon('run_arrow.png'))) self.btn_open_df.setToolTip('Open a DataFrame from your disk') self.btn_from_console = LoadFromConsoleButton(pd.DataFrame) self.btn_from_console.setToolTip('Show a DataFrame from the console') # The table to display the DataFrame self.table = DataFrameView(pd.DataFrame(), self) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) # table clearing button self.btn_clear = QPushButton('Clear') self.btn_clear.setToolTip( 'Clear the table and disconnect from the DataFrame') # refresh button self.btn_refresh = QToolButton() self.btn_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.btn_refresh.setToolTip('Refresh the table') # close button self.btn_close = QPushButton('Close') self.btn_close.setToolTip('Close this widget permanentely') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_index_editable) hbox.addWidget(self.cb_dtypes_changeable) hbox.addWidget(self.cb_enable_sort) hbox.addWidget(self.lbl_size) hbox.addStretch(0) hbox.addWidget(self.btn_open_df) hbox.addWidget(self.btn_from_console) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_clear) hbox.addWidget(self.btn_close) hbox.addWidget(self.btn_refresh) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.cb_dtypes_changeable.stateChanged.connect( self.set_dtypes_changeable) self.cb_index_editable.stateChanged.connect(self.set_index_editable) self.btn_from_console.object_loaded.connect(self._open_ds_from_console) self.rows_inserted.connect(lambda i, n: self.set_lbl_size_text()) self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_clear.clicked.connect(self.clear_table) self.btn_close.clicked.connect(self.clear_table) self.btn_close.clicked.connect(lambda: self.close()) self.btn_refresh.clicked.connect(self.table.reset_model) self.btn_open_df.clicked.connect(self._open_dataframe) self.table.set_index_action.triggered.connect( self.update_index_editable) self.table.append_index_action.triggered.connect( self.update_index_editable) self.cb_enable_sort.stateChanged.connect( self.table.setSortingEnabled)
class DependenciesDialog(QDialog): """A dialog for displaying the dependencies""" #: description label label = None #: the QVBoxLayout containing all the widgets vbox = None #: The :class:`DependenciesTree` that contains the package infos tree = None #: The QPushButton used for copying selected packages to the clipboard bt_copy = None #: A simple info label for info messages info_label = None #: A QTimer that clears the :attr:`info_label` after some time timer = None @docstrings.dedent def __init__(self, versions, *args, **kwargs): """ Parameters ---------- %(DependenciesTree.parameters)s """ super(DependenciesDialog, self).__init__(*args, **kwargs) self.setWindowTitle('Dependencies') self.versions = versions self.vbox = layout = QVBoxLayout() self.label = QLabel(""" psyplot and the plugins depend on several python libraries. The tree widget below lists the versions of the plugins and the requirements. You can select the items in the tree and copy them to clipboard.""", parent=self) layout.addWidget(self.label) self.tree = DependenciesTree(versions, parent=self) self.tree.setSelectionMode(QAbstractItemView.MultiSelection) layout.addWidget(self.tree) # copy button self.bt_copy = QPushButton('Copy selection to clipboard') self.bt_copy.setToolTip( 'Copy the selected packages in the above table to the clipboard.') self.bt_copy.clicked.connect(lambda: self.copy_selected()) self.bbox = QDialogButtonBox(QDialogButtonBox.Ok) self.bbox.accepted.connect(self.accept) hbox = QHBoxLayout() hbox.addWidget(self.bt_copy) hbox.addStretch(1) hbox.addWidget(self.bbox) layout.addLayout(hbox) #: A label for simple status update self.info_label = QLabel('', self) layout.addWidget(self.info_label) self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.clear_label) self.setLayout(layout) def copy_selected(self, label=None): """Copy the selected versions and items to the clipboard""" d = {} items = self.tree.selectedItems() if not items: QMessageBox.warning(self, "No packages selected!", "Please select packages in the tree!") return for item in items: d[item.text(0)] = item.text(1) if label is None: label = QApplication.clipboard() label.setText("\n".join( '%s: %s' % t for t in d.items())) self.info_label.setText('Packages copied to clipboard.') self.timer.start(3000) def clear_label(self): """Clear the info label""" self.info_label.setText('')
class ResultsPlot(StraditizerControlBase): """A widget for plotting the final results This widgets contains a QPushButton :attr:`btn_plot` to plot the results using the :meth:`straditize.binary.DataReader.plot_results` method""" #: The QPushButton to call the :meth:`plot_results` method btn_plot = None #: A QCheckBox whether x- and y-axis should be translated from pixel to #: data units cb_transformed = None #: A QCheckBox whether the samples or the full digitized data shall be #: plotted cb_final = None def __init__(self, straditizer_widgets): self.init_straditizercontrol(straditizer_widgets) self.btn_plot = QPushButton("Plot results") self.cb_final = QCheckBox("Samples") self.cb_final.setToolTip( "Create the diagram based on the samples only, not on the full " "digized data") self.cb_final.setChecked(True) self.cb_final.setEnabled(False) self.cb_transformed = QCheckBox("Translated") self.cb_transformed.setToolTip("Use the x-axis and y-axis translation") self.cb_transformed.setChecked(True) self.cb_transformed.setEnabled(False) self.btn_plot.clicked.connect(self.plot_results) def setup_children(self, item): tree = self.straditizer_widgets.tree tree.setItemWidget(item, 0, self.btn_plot) child = QTreeWidgetItem(0) item.addChild(child) widget = QWidget() vbox = QVBoxLayout() vbox.addWidget(self.cb_final) vbox.addWidget(self.cb_transformed) widget.setLayout(vbox) tree.setItemWidget(child, 0, widget) def refresh(self): try: self.straditizer.yaxis_px self.straditizer.data_reader.xaxis_px except (AttributeError, ValueError): self.cb_transformed.setEnabled(False) else: self.cb_transformed.setEnabled(True) try: assert self.straditizer.data_reader.sample_locs is not None except (AssertionError, AttributeError): self.cb_final.setEnabled(False) else: self.cb_final.setEnabled(True) try: self.btn_plot.setEnabled( self.straditizer.data_reader._full_df is not None) except AttributeError: self.btn_plot.setEnabled(False) def plot_results(self): """Plot the results What is plotted depends on the :attr:`cb_transformed` and the :attr:`cb_final` :attr:`cb_transformed` and :attr:`cb_final` are checked Plot the :attr:`straditize.straditizer.Straditizer.final_df` :attr:`cb_transformed` is checked but not :attr:`cb_final` Plot the :attr:`straditize.straditizer.Straditizer.full_df` :attr:`cb_transformed` is not checked but :attr:`cb_final` Plot the :attr:`straditize.binary.DataReader.sample_locs` :attr:`cb_transformed` and :attr:`cb_final` are both not checked Plot the :attr:`straditize.binary.DataReader.full_df`""" transformed = self.cb_transformed.isEnabled() and \ self.cb_transformed.isChecked() if self.cb_final.isEnabled() and self.cb_final.isChecked(): df = self.straditizer.final_df if transformed else \ self.straditizer.data_reader.sample_locs else: df = self.straditizer.full_df if transformed else \ self.straditizer.data_reader._full_df return self.straditizer.data_reader.plot_results( df, transformed=transformed)
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())