def __init__(self, parent, fname=None, rst=None, name=None): """ Parameters ---------- parent: QWidget The parent widget fname: str The name of the rst file. If None, specify the `rst` directly rst: str The restructured text to render when this button is clicked. If None, the `fname` has to be provided name: str The name to use for the document in the help_explorer """ if fname is None and rst is None: raise ValueError("Either `fname` or `rst` must be specified!") elif fname is not None and rst is not None: raise ValueError("Either `fname` or `rst` must be specified! " "Not both!") elif rst is not None and name is None: raise ValueError("A title must be specified for the rst document!") self.fname = fname self.rst = rst self.files = doc_files self.name = name QToolButton.__init__(self, parent) self.setIcon(QIcon(get_psy_icon('info.png'))) self.clicked.connect(self.show_docs)
def __init__(self, *args, **kwargs): self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp()) self.build_dir = osp.join(self.sphinx_dir, '_build', 'html') super(UrlHelp, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) if with_sphinx: self.sphinx_thread = SphinxThread(self.sphinx_dir) self.sphinx_thread.html_ready[str].connect(self.browse) self.sphinx_thread.html_error[str].connect( self.error_msg.showTraceback) self.sphinx_thread.html_error[str].connect(logger.debug) rcParams.connect('help_explorer.render_docs_parallel', self.reset_sphinx) rcParams.connect('help_explorer.use_intersphinx', self.reset_sphinx) rcParams.connect('help_explorer.online', self.reset_sphinx) else: self.sphinx_thread = None self.bt_connect_console = QToolButton(self) self.bt_connect_console.setCheckable(True) if rcParams['console.connect_to_help']: self.bt_connect_console.setIcon( QIcon(get_icon('ipython_console.png'))) self.bt_connect_console.click() else: self.bt_connect_console.setIcon( QIcon(get_icon('ipython_console_t.png'))) self.bt_connect_console.clicked.connect(self.toogle_connect_console) rcParams.connect('console.connect_to_help', self.update_connect_console) self.toogle_connect_console() # menu button with different urls self.bt_url_menus = QToolButton(self) self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png'))) self.bt_url_menus.setToolTip('Browse documentations') self.bt_url_menus.setPopupMode(QToolButton.InstantPopup) docu_menu = QMenu(self) for name, url in six.iteritems(self.doc_urls): def to_url(b, url=url): self.browse(url) action = QAction(name, self) action.triggered.connect(to_url) docu_menu.addAction(action) self.bt_url_menus.setMenu(docu_menu) self.button_box.addWidget(self.bt_connect_console) self.button_box.addWidget(self.bt_url_menus) # toogle the lock again to set the bt_url_menus enabled state self.toogle_url_lock()
def __init__(self, *args, **kwargs): self._temp_dir = 'sphinx_dir' not in kwargs self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp(prefix='psyplot_')) self.build_dir = osp.join(self.sphinx_dir, '_build', 'html') super(UrlHelp, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) if with_sphinx: self.sphinx_thread = SphinxThread(self.sphinx_dir) self.sphinx_thread.html_ready[str].connect(self.browse) self.sphinx_thread.html_error[str].connect( self.error_msg.showTraceback) self.sphinx_thread.html_error[str].connect(logger.debug) rcParams.connect('help_explorer.render_docs_parallel', self.reset_sphinx) rcParams.connect('help_explorer.use_intersphinx', self.reset_sphinx) rcParams.connect('help_explorer.online', self.reset_sphinx) self.bt_connect_console = QToolButton(self) self.bt_connect_console.setCheckable(True) if rcParams['console.connect_to_help']: self.bt_connect_console.setIcon(QIcon(get_icon( 'ipython_console.png'))) self.bt_connect_console.click() else: self.bt_connect_console.setIcon(QIcon(get_icon( 'ipython_console_t.png'))) self.bt_connect_console.clicked.connect(self.toogle_connect_console) rcParams.connect('console.connect_to_help', self.update_connect_console) self.toogle_connect_console() # menu button with different urls self.bt_url_menus = QToolButton(self) self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png'))) self.bt_url_menus.setToolTip('Browse documentations') self.bt_url_menus.setPopupMode(QToolButton.InstantPopup) docu_menu = QMenu(self) for name, url in six.iteritems(self.doc_urls): def to_url(b, url=url): self.browse(url) action = QAction(name, self) action.triggered.connect(to_url) docu_menu.addAction(action) self.bt_url_menus.setMenu(docu_menu) self.button_box.addWidget(self.bt_connect_console) self.button_box.addWidget(self.bt_url_menus) # toogle the lock again to set the bt_url_menus enabled state self.toogle_url_lock()
def __init__(self, df, straditizer, fname=None, *args, **kwargs): """ Parameters ---------- df: pandas.DataFrame The DataFrame to be exported straditizer: straditize.straditizer.Straditizer The source straditizer fname: str The file name to export to """ super().__init__(*args, **kwargs) self.df = df self.stradi = straditizer self.txt_fname = QLineEdit() self.bt_open_file = QToolButton() self.bt_open_file.setIcon(QIcon(get_icon('run_arrow.png'))) self.bt_open_file.setToolTip('Select the export file on your drive') self.cb_include_meta = QCheckBox('Include meta data') self.cb_include_meta.setChecked(True) self.bbox = bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) # --------------------------------------------------------------------- # --------------------------- Layouts --------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel('Export to:')) hbox.addWidget(self.txt_fname) hbox.addWidget(self.bt_open_file) vbox.addLayout(hbox) vbox.addWidget(self.cb_include_meta) vbox.addWidget(bbox) self.setLayout(vbox) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- bbox.accepted.connect(self._export) bbox.rejected.connect(self.reject) self.bt_open_file.clicked.connect(self.get_open_file_name) if fname is not None: self.txt_fname.setText(fname) self._export()
def __init__(self, *args, **kwargs): self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp()) self.build_dir = osp.join(self.sphinx_dir, '_build', 'html') super(UrlHelp, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) if with_sphinx: self.sphinx_thread = SphinxThread(self.sphinx_dir) self.sphinx_thread.html_ready[str].connect(self.browse) self.sphinx_thread.html_error[str].connect( self.error_msg.showTraceback) self.sphinx_thread.html_error[str].connect(logger.debug) else: self.sphinx_thread = None #: menu button with different urls self.bt_url_menus = QToolButton(self) self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png'))) self.bt_url_menus.setToolTip('Browse documentations') self.bt_url_menus.setPopupMode(QToolButton.InstantPopup) docu_menu = QMenu(self) for name, url in six.iteritems(self.doc_urls): def to_url(b, url=url): self.browse(url) action = QAction(name, self) action.triggered.connect(to_url) docu_menu.addAction(action) self.bt_url_menus.setMenu(docu_menu) self.button_box.addWidget(self.bt_url_menus)
def __init__(self, parent, *fmtos): """ Parameters ---------- parent: psyplot_gui.fmt_widget.FormatoptionWidget The formatoption widget that contains the button ``*fmtos`` Instances of the :class:`psyplot.plotter.Formatoption` for which the links should be created """ QToolButton.__init__(self, parent=parent) self.setText('fmt') self.setPopupMode(QToolButton.InstantPopup) menu = QMenu() for fmto in fmtos: name = parent.get_name(fmto) menu.addAction(name, partial(parent.set_fmto, name)) self.setMenu(menu)
def setup_children(self, item): tree = self.straditizer_widgets.tree # import menu import_child = QTreeWidgetItem(0) item.addChild(import_child) self.btn_import = QToolButton() self.btn_import.setText('Import images') self.btn_import.setMenu(self.import_images_menu) self.btn_import.setPopupMode(QToolButton.InstantPopup) tree.setItemWidget(import_child, 0, self.btn_import) # export menu export_child = QTreeWidgetItem(0) item.addChild(export_child) self.btn_export = QToolButton() self.btn_export.setText('Export images') self.btn_export.setMenu(self.export_images_menu) self.btn_export.setPopupMode(QToolButton.InstantPopup) tree.setItemWidget(export_child, 0, self.btn_export)
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__(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)
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 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 StraditizerMenuActions(StraditizerControlBase): """An object to control the main functionality of a Straditizer This object is creates menu actions to load the straditizer""" #: The QActions to save the straditizer, straditizer image, etc. save_actions = [] #: The QActions to save the exported DataFrames and data images data_actions = [] #: The QActions to save the column names images text_actions = [] #: The action to #: :meth:`~straditize.widgets.StraditizerWidgets.switch_to_straditizer_layout` window_layout_action = None #: The path to a directory from where to open a straditizer. Is set in the #: tutorial _dirname_to_use = None @property def all_actions(self): """:attr:`save_actions`, :attr:`data_actions` and :attr:`text_actions` """ return chain(self.save_actions, self.data_actions, self.text_actions) def __init__(self, straditizer_widgets): self.init_straditizercontrol(straditizer_widgets) def setup_menu_actions(self, main): """Create the actions for the file menu Parameters ---------- main: psyplot_gui.main.MainWindow The mainwindow whose menubar shall be adapted""" # load buttons self.open_menu = menu = QMenu('Open straditizer') main.open_project_menu.addMenu(self.open_menu) self.load_stradi_action = self._add_action( menu, 'Project or image', self.open_straditizer, tooltip='Reload a digitization project or load a picture') self.load_clipboard_action = self._add_action( menu, 'From clipboard', self.from_clipboard, tooltip='Load a picture from the clipboard') # save and export data buttons self.save_straditizer_action = self._add_action( main.save_project_menu, 'Save straditizer', self.save_straditizer, tooltip='Save the digitization project') self.save_straditizer_as_action = self._add_action( main.save_project_as_menu, 'Save straditizer as', self.save_straditizer_as, tooltip='Save the digitization project to a different file') self.export_data_menu = menu = QMenu('Straditizer data') main.export_project_menu.addMenu(menu) self.export_full_action = self._add_action( menu, 'Full data', self.export_full, tooltip='Export the full digitized data') self.export_final_action = self._add_action( menu, 'Samples', self.export_final, tooltip='Export the data at the sample locations') # close menu self.close_straditizer_action = self._add_action( main.close_project_menu, 'Close straditizer', self.straditizer_widgets.close_straditizer, tooltip='Close the current straditize project') self.close_all_straditizer_action = self._add_action( main.close_project_menu, 'Close all straditizers', self.straditizer_widgets.close_all_straditizers, tooltip='Close all open straditize projects') # export image buttons self.export_images_menu = menu = QMenu('Straditizer image(s)') menu.setToolTipsVisible(True) main.export_project_menu.addMenu(menu) self.export_full_image_action = self._add_action( menu, 'Full image', self.save_full_image, tooltip='Save the full image to a file') self.export_data_image_action = self._add_action( menu, 'Save data image', self.save_data_image, tooltip='Save the binary image that represents the data part') self.export_text_image_action = self._add_action( menu, 'Save text image', self.save_text_image, tooltip='Save the image part with the rotated column descriptions') # import image buttons self.import_images_menu = menu = QMenu('Import straditizer image(s)') menu.setToolTipsVisible(True) self.import_full_image_action = self._add_action( menu, 'Full image', self.import_full_image, tooltip='Import the diagram into the current project') self.import_data_image_action = self._add_action( menu, 'Data image', self.import_data_image, tooltip='Import the data part image') self.import_binary_image_action = self._add_action( menu, 'Binary data image', self.import_binary_image, tooltip='Import the binary image for the data part') self.import_text_image_action = self._add_action( menu, 'Text image', self.import_text_image, tooltip='Import the image for the column names') self.window_layout_action = main.window_layouts_menu.addAction( 'Straditizer layout', self.straditizer_widgets.switch_to_straditizer_layout) self.save_actions = [ self.export_full_image_action, self.save_straditizer_action, self.import_full_image_action ] self.data_actions = [ self.export_data_image_action, self.export_full_action, self.export_final_action, self.import_binary_image_action, self.import_data_image_action ] self.text_actions = [ self.export_text_image_action, self.import_text_image_action ] self.widgets2disable = [ self.load_stradi_action, self.load_clipboard_action ] self.refresh() def setup_shortcuts(self, main): """Setup the shortcuts when switched to the straditizer layout Parameters ---------- main: psyplot_gui.main.MainWindow The psyplot mainwindow""" main.register_shortcut(self.save_straditizer_action, QKeySequence.Save) main.register_shortcut(self.save_straditizer_as_action, QKeySequence.SaveAs) main.register_shortcut(self.close_straditizer_action, QKeySequence.Close) main.register_shortcut( self.close_all_straditizer_action, QKeySequence('Ctrl+Shift+W', QKeySequence.NativeText)) main.register_shortcut(self.export_final_action, QKeySequence('Ctrl+E', QKeySequence.NativeText)) main.register_shortcut( self.export_full_action, QKeySequence('Ctrl+Shift+E', QKeySequence.NativeText)) main.register_shortcut(self.load_stradi_action, [QKeySequence.Open, QKeySequence.New]) def _add_action(self, menu, *args, **kwargs): tooltip = kwargs.pop('tooltip', None) a = menu.addAction(*args, **kwargs) if tooltip: a.setToolTip(tooltip) return a @property def _start_directory(self): def check_current(): dirname = osp.dirname(current) if osp.exists(dirname) and osp.isdir(dirname): return dirname if self.straditizer is not None: current = None for attr in 'project_file', 'image_file': try: current = self.straditizer.get_attr(attr) except KeyError: pass else: start = check_current() if start is not None: break if current: return osp.splitext(current)[0] return os.getcwd() @docstrings.get_sectionsf('StraditizerMenuActions._open_image') def _open_image(self, fname=None): """Open an image file Parameters ---------- fname: :class:`str`, :class:`PIL.Image.Image` or ``None`` The path of the image file or the :class:`PIL.Image.Image`. If None, a QFileDialog is opened to request the file from the user. Returns ------- PIL.Image.Image The image file or None if the operation has been cancelled by the user""" if fname is None or (not isinstance(fname, six.string_types) and np.ndim(fname) < 2): fname = QFileDialog.getOpenFileName( self.straditizer_widgets, 'Stratigraphic diagram', self._dirname_to_use or self._start_directory, 'All images ' '(*.jpeg *.jpg *.pdf *.png *.raw *.rgba *.tif *.tiff);;' 'Joint Photographic Experts Group (*.jpeg *.jpg);;' 'Portable Document Format (*.pdf);;' 'Portable Network Graphics (*.png);;' 'Tagged Image File Format(*.tif *.tiff);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not np.ndim(fname) and not fname: return elif np.ndim(fname) >= 2: return fname else: from PIL import Image with Image.open(fname) as _image: return Image.fromarray(np.array(_image.convert('RGBA'))) @docstrings.with_indent(8) def import_full_image(self, fname=None): """Import the straditizer image from an external file This method imports the :attr:`straditize.straditizer.Straditizer.image` from an external file and sets it to the current straditizer. Parameters ---------- %(StraditizerMenuActions._open_image.parameters)s""" image = self._open_image(fname) if image is not None: if self.straditizer is None: self.open_straditizer(image) else: self.straditizer.reset_image(image) @docstrings.with_indent(8) def import_data_image(self, fname=None): """Import the data reader image from an external file This method imports the :attr:`straditize.binary.DataReader.image` from an external file and sets it to the current :attr:`~straditize.straditizer.Straditizer.data_reader`. Parameters ---------- %(StraditizerMenuActions._open_image.parameters)s See Also -------- import_binary_image: To import the binary file""" image = self._open_image(fname) if image is not None: self.straditizer.data_reader.reset_image(image) @docstrings.with_indent(8) def import_binary_image(self, fname=None): """Import the binary data reader image from an external file This method imports the :attr:`straditize.binary.DataReader.binary` from an external file and sets it to the current :attr:`~straditize.straditizer.Straditizer.data_reader`. Parameters ---------- %(StraditizerMenuActions._open_image.parameters)s""" image = self._open_image(fname) if image is not None: self.straditizer.data_reader.reset_image(image, binary=True) @docstrings.with_indent(8) def import_text_image(self, fname): """Import the column names reader image from an external file This method imports the :attr:`straditize.colnames.ColNamesReader.highres_image` from an external file and sets it to the current :attr:`~straditize.straditizer.Straditizer.colnames_reader`. Parameters ---------- %(StraditizerMenuActions._open_image.parameters)s""" image = self._open_image(fname) if image is not None: self.straditizer.colnames_reader.highres_image = image def open_straditizer(self, fname=None, *args, **kwargs): """Open a straditizer from an image or project file Parameters ---------- fname: :class:`str`, :class:`PIL.Image.Image` or ``None`` The path to the file to import. If None, a QFileDialog is opened and the user is asked for a file name. The action then depends on the ending of ``fname``: ``'.nc'`` or ``'.nc4'`` we expect a netCDF file and open it with :func:`xarray.open_dataset` and load the straditizer with the :meth:`straditize.straditizer.Straditizer.from_dataset` constructor ``'.pkl'`` We expect a pickle file and load the straditizer with :func:`pickle.load` any other ending We expect an image file and use the :func:`PIL.Image.open` function At the end, the loading is finished with the :meth:`finish_loading` method""" from straditize.straditizer import Straditizer if fname is None or (not isinstance(fname, six.string_types) and np.ndim(fname) < 2): fname = QFileDialog.getOpenFileName( self.straditizer_widgets, 'Straditizer project', self._dirname_to_use or self._start_directory, 'Projects and images ' '(*.nc *.nc4 *.pkl *.jpeg *.jpg *.pdf *.png *.raw *.rgba *.tif' ' *.tiff);;' 'NetCDF files (*.nc *.nc4);;' 'Pickle files (*.pkl);;' 'All images ' '(*.jpeg *.jpg *.pdf *.png *.raw *.rgba *.tif *.tiff);;' 'Joint Photographic Experts Group (*.jpeg *.jpg);;' 'Portable Document Format (*.pdf);;' 'Portable Network Graphics (*.png);;' 'Raw RGBA bitmap (*.raw *.rbga);;' 'Tagged Image File Format(*.tif *.tiff);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not np.ndim(fname) and not fname: return elif np.ndim(fname) >= 2: stradi = Straditizer(fname, *args, **kwargs) elif fname.endswith('.nc') or fname.endswith('.nc4'): import xarray as xr ds = xr.open_dataset(fname) stradi = Straditizer.from_dataset(ds.load(), *args, **kwargs) stradi.set_attr('project_file', fname) ds.close() stradi.set_attr('loaded', str(dt.datetime.now())) elif fname.endswith('.pkl'): stradi = Straditizer.load(fname, *args, **kwargs) stradi.set_attr('project_file', fname) stradi.set_attr('loaded', str(dt.datetime.now())) else: from PIL import Image with Image.open(fname) as _image: image = Image.fromarray(np.array(_image.convert('RGBA')), 'RGBA') w, h = image.size im_size = w * h if im_size > 20e6: recom_frac = 17403188.0 / im_size answer = ( QMessageBox.Yes if self.straditizer_widgets.always_yes else QMessageBox.question( self.straditizer_widgets, "Large straditizer image", "This is a rather large image with %1.0f pixels. " "Shall I reduce it to %1.0f%% of it's size for a " "better interactive experience?<br>" "If not, you can rescale it via<br><br>" "Transform source image → Rescale image" % (im_size, 100. * recom_frac))) if answer == QMessageBox.Yes: image = image.resize((int(round(w * recom_frac)), int(round(h * recom_frac)))) stradi = Straditizer(image, *args, **kwargs) stradi.set_attr('image_file', fname) self.finish_loading(stradi) self._dirname_to_use = None def finish_loading(self, stradi): """Finish the opening of a straditizer This method sets the :attr:`straditizer.widgets.StraditizerWidgets.straditizer`, shows the straditizer image, creates the navigation sliders and sets the straditizer in the console Parameters ---------- stradi: straditize.straditizer.Straditizer The straditizer that just has been opened""" self.straditizer = stradi stradi.show_full_image() self.create_sliders(stradi) self.set_stradi_in_console() self.stack_zoom_window() self.straditizer_widgets.refresh() def create_sliders(self, stradi): """Create sliders to navigate in the given axes Parameters ---------- stradi: straditize.straditizer.Straditizer The straditizer that just has been opened""" ax = stradi.ax try: manager = ax.figure.canvas.manager dock = manager.window fig_widget = manager.parent_widget except AttributeError: return from psyplot_gui.backend import FigureWidget import matplotlib.colors as mcol xs, ys = stradi.image.size fc = ax.figure.get_facecolor() rgb = tuple(np.round(np.array(mcol.to_rgb(fc)) * 255).astype(int)) slh = QtWidgets.QSlider(Qt.Horizontal) slv = QtWidgets.QSlider(Qt.Vertical) slh.setStyleSheet("background-color:rgb{};".format(rgb)) slv.setStyleSheet("background-color:rgb{};".format(rgb)) slh.setMaximum(xs) slv.setMaximum(ys) slv.setInvertedAppearance(True) vbox = QVBoxLayout() hbox = QHBoxLayout() vbox.setSpacing(0) hbox.setSpacing(0) hbox.addWidget(fig_widget) hbox.addWidget(slv) vbox.addLayout(hbox) vbox.addWidget(slh) w = FigureWidget() w.dock = dock w.setLayout(vbox) dock.setWidget(w) ax.callbacks.connect('xlim_changed', self.update_x_navigation_sliders) ax.callbacks.connect('ylim_changed', self.update_y_navigation_sliders) self.update_x_navigation_sliders(ax) self.update_y_navigation_sliders(ax) ref = weakref.ref(ax) slh.valueChanged.connect(self.set_ax_xlim(ref)) slv.valueChanged.connect(self.set_ax_ylim(ref)) @staticmethod def set_ax_xlim(ax_ref): """Define a function to update xlim from a given centered value Parameters ---------- ax_ref: matplotlib.axes.Axes The axes whose x-limits to update when the returned function is called Returns ------- callable The function that can be called with a `val` to set the x-center of the `ax_ref`""" def update(val): ax = ax_ref() if ax in _updating or ax is None: return _updating.append(ax) lims = ax.get_xlim() diff = (lims[1] - lims[0]) / 2 ax.set_xlim(val - diff, val + diff) ax.figure.canvas.draw_idle() _updating.remove(ax) return update @staticmethod def set_ax_ylim(ax_ref): """Define a function to update ylim from a given centered value Parameters ---------- ax_ref: matplotlib.axes.Axes The axes whose y-limits to update when the returned function is called Returns ------- callable The function that can be called with a `val` to set the y-center of the `ax_ref`""" def update(val): ax = ax_ref() if ax in _updating or ax is None: return _updating.append(ax) lims = ax.get_ylim() diff = (lims[1] - lims[0]) / 2 ax.set_ylim(val - diff, val + diff) ax.figure.canvas.draw_idle() _updating.remove(ax) return update @staticmethod def update_x_navigation_sliders(ax): """Update the horizontal navigation slider for the given `ax`` Set the horizontal slider of the straditizer figure depending on the x-limits of it's corresponding axes Parameters ---------- ax: matplotlib.axes.Axes The :attr:`straditize.straditizer.Straditizer.ax` attribute of a straditizer """ w = ax.figure.canvas.manager.window.widget() slh = w.layout().itemAt(1).widget() xc = np.mean(ax.get_xlim()) slh.setValue(max(0, min(slh.maximum(), int(round(xc))))) @staticmethod def update_y_navigation_sliders(ax): """Update the vertical navigation slider for the given `ax`` Set the vertical slider of the straditizer figure depending on the y-limits of it's corresponding axes Parameters ---------- ax: matplotlib.axes.Axes The :attr:`straditize.straditizer.Straditizer.ax` attribute of a straditizer """ w = ax.figure.canvas.manager.window.widget() slv = w.layout().itemAt(0).itemAt(1).widget() yc = np.mean(ax.get_ylim()) slv.setValue(max(0, min(slv.maximum(), int(round(yc))))) def stack_zoom_window(self): """Stack the magnifier image above the help explorer""" from psyplot_gui.main import mainwindow if mainwindow.figures: found = False for stradi in self.straditizer_widgets._straditizers: if stradi is not self.straditizer and stradi.magni: ref_dock = stradi.magni.ax.figure.canvas.manager.window found = True break if not found: ref_dock = mainwindow.help_explorer.dock dock = self.straditizer.magni.ax.figure.canvas.manager.window pos = mainwindow.dockWidgetArea(ref_dock) mainwindow.addDockWidget(pos, dock) if not found: mainwindow.addDockWidget(pos, ref_dock) else: mainwindow.tabifyDockWidget(ref_dock, dock) # show the zoom figure dock.widget().show_plugin() dock.raise_() mainwindow.figures.insert(-1, mainwindow.figures.pop(-1)) # show the straditizer figure mainwindow.figures[-1].widget().show_plugin() mainwindow.figures[-1].raise_() def from_clipboard(self): """Open a straditizer from an Image in the clipboard This method uses the :func:`PIL.ImageGrab.grabclipboard` function to open a new straditizer from the clipboard.""" from PIL import ImageGrab from straditize.straditizer import Straditizer image = ImageGrab.grabclipboard() if np.shape(image)[-1] == 3: image.putalpha(255) stradi = Straditizer(image) return self.finish_loading(stradi) def save_straditizer(self): """Save the straditizer to a file""" try: fname = self.straditizer.attrs.loc['project_file', 0] except KeyError: fname = None return self.save_straditizer_as(fname) def save_straditizer_as(self, fname=None): """Save the straditizer to a file Parameters ---------- fname: str or ``None`` If None, a QFileDialog is opened and the user has to provide a filename. The final action then depends on the ending of the chose file: ``'.pkl'`` We save the straditizer with :meth:`pickle.dump` else We use the :meth:`straditize.straditizer.Straditizer.to_dataset` method and save the resulting dataset using the :meth:`xarray.Dataset.to_netcdf` method""" if fname is None or not isinstance(fname, six.string_types): fname = QFileDialog.getSaveFileName( self.straditizer_widgets, 'Straditizer file destination', self._start_directory, ('NetCDF files (*.nc *.nc4);;Pickle files (*.pkl);;' 'All files (*)')) if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return ending = os.path.splitext(fname)[1] self.straditizer.set_attr('saved', str(dt.datetime.now())) self.straditizer.set_attr('project_file', fname) if ending == '.pkl': self.straditizer.save(fname) else: ds = self.straditizer.to_dataset() # -- Compression with a level of 4. Requires netcdf4 engine comp = dict(zlib=True, complevel=4) encoding = {var: comp for var in ds.data_vars} ds.to_netcdf(fname, encoding=encoding, engine='netcdf4') @docstrings.get_sectionsf('StraditizerMenuActions._save_image') def _save_image(self, image, fname=None): """Save an image to a file Parameters ---------- image: PIL.Image.Image The image to save fname: str or None The path of the target filename where to save the `image`. If None, A QFileDialog is opened and we ask the user for a filename""" if fname is None or not isinstance(fname, six.string_types): fname = QFileDialog.getSaveFileName( self.straditizer_widgets, 'Straditizer file destination', self._start_directory, 'All images ' '(*.png *.jpeg *.jpg *.pdf *.tif *.tiff);;' 'Joint Photographic Experts Group (*.jpeg *.jpg);;' 'Portable Document Format (*.pdf);;' 'Portable Network Graphics (*.png);;' 'Tagged Image File Format(*.tif *.tiff);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return ext = osp.splitext(fname)[1] if ext.lower() in ['.jpg', '.jpeg', '.pdf'] and image.mode == 'RGBA': image = rgba2rgb(image) image.save(fname) docstrings.keep_params('StraditizerMenuActions._save_image.parameters', 'fname') @docstrings.with_indent(8) def save_text_image(self, fname=None): """Save the image of the colnames reader Save the :attr:`straditize.colnames.ColNamesReader.image` of the current :attr:`~straditize.straditizer.Straditizer.colnames_reader` Parameters ---------- %(StraditizerMenuActions._save_image.parameters.fname)s """ reader = self.straditizer.colnames_reader self._save_image(reader.highres_image, fname) def save_full_image(self, fname=None): """Save the image of the straditizer Save the :attr:`straditize.straditizer.Straditizer.image` of the current :attr:`~straditize.widgets.StraditizerWidgets.straditizer` Parameters ---------- %(StraditizerMenuActions._save_image.parameters.fname)s """ self._save_image(self.straditizer.image, fname) def save_data_image(self, fname=None): """Save the binary image of the data reader Save the :attr:`straditize.binary.DataReader.binary` of the current :attr:`~straditize.straditizer.Straditizer.data_reader` Parameters ---------- %(StraditizerMenuActions._save_image.parameters.fname)s """ arr = np.tile(self.straditizer.data_reader.binary[:, :, np.newaxis], (1, 1, 4)) arr[..., 3] *= 255 arr[..., :3] = 0 image = Image.fromarray(arr.astype(np.uint8), 'RGBA') self._save_image(image, fname) def set_stradi_in_console(self): """Set the straditizer in the console of the GUI mainwindow This sets the current :attr:`~straditize.widgets.StraditizerWidgets.straditizer` in psyplots :attr:`~psyplot_gui.main.MainWindow.console` as the ``stradi`` variable """ from psyplot_gui.main import mainwindow global straditizer straditizer = self.straditizer if mainwindow is not None: mainwindow.console.run_command_in_shell( 'from %s import straditizer as stradi' % __name__) straditizer = None @docstrings.get_sectionsf('StraditizerMenuActions._export_df') def _export_df(self, df, fname=None): """Export a data frame to a file This method opens an :class:`ExportDfDialog` to save a data frame Parameters ---------- df: pandas.DataFrame The dataframe to save fname: str or None The path of the target filename where to save the `df` (see the :meth:`ExportDialog.export_df`)""" ExportDfDialog.export_df(self.straditizer_widgets, df, self.straditizer, fname) docstrings.keep_params('StraditizerMenuActions._export_df.parameters', 'fname') @docstrings.with_indent(8) def export_final(self, fname=None): """Export the final results This method exports the :attr:`straditize.straditizer.Straditizer.final_df` of the current :attr:`~straditize.widgets.StraditizerWidgets.straditizer` Parameters ---------- %(StraditizerMenuActions._export_df.parameters.fname)s""" try: df = self.straditizer.final_df except Exception as e: self.straditizer_widgets.error_msg.showTraceback( e.message if six.PY2 else str(e)) else: self._export_df(df, fname) @docstrings.with_indent(8) def export_full(self, fname=None): """Export the full digitized data This method exports the :attr:`straditize.straditizer.Straditizer.full_df` of the current :attr:`~straditize.widgets.StraditizerWidgets.straditizer` Parameters ---------- %(StraditizerMenuActions._export_df.parameters.fname)s""" self._export_df(self.straditizer.full_df, fname) def refresh(self): stradi = self.straditizer import_stradi_action = getattr(self, 'import_full_image_action', None) if stradi is None: for w in filter(lambda w: w is not import_stradi_action, self.all_actions): w.setEnabled(False) else: if stradi.data_reader is None: for w in self.data_actions: w.setEnabled(False) else: reader = stradi.data_reader self.export_data_image_action.setEnabled(True) self.import_binary_image_action.setEnabled(True) self.import_data_image_action.setEnabled(True) self.export_full_action.setEnabled(reader.full_df is not None) self.export_final_action.setEnabled(reader.full_df is not None) for w in self.text_actions: w.setEnabled(stradi.colnames_reader is not None) for w in self.save_actions: w.setEnabled(True) def setup_children(self, item): tree = self.straditizer_widgets.tree # import menu import_child = QTreeWidgetItem(0) item.addChild(import_child) self.btn_import = QToolButton() self.btn_import.setText('Import images') self.btn_import.setMenu(self.import_images_menu) self.btn_import.setPopupMode(QToolButton.InstantPopup) tree.setItemWidget(import_child, 0, self.btn_import) # export menu export_child = QTreeWidgetItem(0) item.addChild(export_child) self.btn_export = QToolButton() self.btn_export.setText('Export images') self.btn_export.setMenu(self.export_images_menu) self.btn_export.setPopupMode(QToolButton.InstantPopup) tree.setItemWidget(export_child, 0, self.btn_export)
class ExportDfDialog(QDialog): """A QDialog to export a :class:`pandas.DataFrame` to Excel or CSV""" @docstrings.get_sectionsf('ExportDfDialog') def __init__(self, df, straditizer, fname=None, *args, **kwargs): """ Parameters ---------- df: pandas.DataFrame The DataFrame to be exported straditizer: straditize.straditizer.Straditizer The source straditizer fname: str The file name to export to """ super().__init__(*args, **kwargs) self.df = df self.stradi = straditizer self.txt_fname = QLineEdit() self.bt_open_file = QToolButton() self.bt_open_file.setIcon(QIcon(get_icon('run_arrow.png'))) self.bt_open_file.setToolTip('Select the export file on your drive') self.cb_include_meta = QCheckBox('Include meta data') self.cb_include_meta.setChecked(True) self.bbox = bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) # --------------------------------------------------------------------- # --------------------------- Layouts --------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel('Export to:')) hbox.addWidget(self.txt_fname) hbox.addWidget(self.bt_open_file) vbox.addLayout(hbox) vbox.addWidget(self.cb_include_meta) vbox.addWidget(bbox) self.setLayout(vbox) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- bbox.accepted.connect(self._export) bbox.rejected.connect(self.reject) self.bt_open_file.clicked.connect(self.get_open_file_name) if fname is not None: self.txt_fname.setText(fname) self._export() def get_open_file_name(self): """Ask the user for a filename for saving the data frame""" def check_current(): dirname = osp.dirname(current) if osp.exists(dirname) and osp.isdir(dirname): return dirname current = self.txt_fname.text().strip() start = None if current: start = check_current() if start is None: for attr in 'project_file', 'image_file': try: current = self.stradi.get_attr(attr) except KeyError: pass else: start = check_current() if start is not None: break if start is None: start = os.getcwd() fname = QFileDialog.getSaveFileName( self, 'DataFrame file destination', start, 'Excel files (*.xlsx *.xls);;' 'csv files (*.csv);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return self.txt_fname.setText(fname) def _export(self): fname = self.txt_fname.text() ending = osp.splitext(fname)[1] self.stradi.set_attr('exported', str(dt.datetime.now())) meta = self.stradi.valid_attrs if ending in ['.xls', '.xlsx']: with pd.ExcelWriter(fname) as writer: self.df.to_excel(writer, 'Data') if self.cb_include_meta.isChecked() and len(meta): meta.to_excel(writer, 'Metadata', header=False) else: with open(fname, 'w') as f: if self.cb_include_meta.isChecked(): for t in meta.iloc[:, 0].items(): f.write('# %s: %s\n' % t) self.df.to_csv(fname, mode='a') self.accept() def cancel(self): del self.stradi, self.df super().cancel() def accept(self): del self.stradi, self.df super().accept() @classmethod @docstrings.dedent def export_df(cls, parent, df, straditizer, fname=None, exec_=True): """Open a dialog for exporting a DataFrame Parameters ---------- parent: QWidget The parent widget %(ExportDfDialog.parameters)s""" dialog = cls(df, straditizer, fname, parent=parent) if fname is None: available_width = QDesktopWidget().availableGeometry().width() / 3. width = dialog.sizeHint().width() height = dialog.sizeHint().height() # The plot creator window should cover at least one third of the # screen dialog.resize(max(available_width, width), height) if exec_: dialog.exec_() else: return dialog
class UrlHelp(UrlBrowser, HelpMixin): """Class to convert rst docstrings to html and show browsers""" #: Object containing the necessary fields to describe an object given to #: the help widget. The descriptor is set up by the :meth:`describe_object` #: method and contains an additional objtype attribute object_descriptor = namedtuple( 'ObjectDescriptor', ['obj', 'name', 'objtype']) can_document_object = with_sphinx can_show_rst = with_sphinx def __init__(self, *args, **kwargs): self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp()) self.build_dir = osp.join(self.sphinx_dir, '_build', 'html') super(UrlHelp, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) if with_sphinx: self.sphinx_thread = SphinxThread(self.sphinx_dir) self.sphinx_thread.html_ready[str].connect(self.browse) self.sphinx_thread.html_error[str].connect( self.error_msg.showTraceback) self.sphinx_thread.html_error[str].connect(logger.debug) else: self.sphinx_thread = None #: menu button with different urls self.bt_url_menus = QToolButton(self) self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png'))) self.bt_url_menus.setToolTip('Browse documentations') self.bt_url_menus.setPopupMode(QToolButton.InstantPopup) docu_menu = QMenu(self) for name, url in six.iteritems(self.doc_urls): def to_url(b, url=url): self.browse(url) action = QAction(name, self) action.triggered.connect(to_url) docu_menu.addAction(action) self.bt_url_menus.setMenu(docu_menu) self.button_box.addWidget(self.bt_url_menus) @docstrings.dedent def show_intro(self, text=''): """ Show the intro text in the explorer Parameters ---------- %(HelpMixin.show_intro.parameters)s""" if self.sphinx_thread is not None: with open(self.sphinx_thread.index_file, 'a') as f: f.write(text) self.sphinx_thread.render(None, None) def show_rst(self, text, oname='', descriptor=None): """Render restructured text with sphinx and show it Parameters ---------- %(HelpMixin.show_rst.parameters)s""" if not oname and descriptor: oname = descriptor.name self.sphinx_thread.render(text, oname) def describe_object(self, obj, oname=''): """Describe an object using additionaly the object type from the :meth:`get_objtype` method Returns ------- instance of :attr:`object_descriptor` The descriptor of the object""" return self.object_descriptor(obj, oname, self.get_objtype(obj)) def browse(self, url): """Reimplemented to add file paths to the url string""" html_file = osp.join(self.sphinx_dir, '_build', 'html', url + '.html') if osp.exists(html_file): url = 'file://' + html_file super(UrlHelp, self).browse(url) def url_changed(self, url): """Reimplemented to remove file paths from the url string""" try: url = url.toString() except AttributeError: pass if url.startswith('file://'): fname = url[7:] if osp.samefile(self.build_dir, osp.commonprefix([ fname, self.build_dir])): url = osp.splitext(osp.basename(fname))[0] super(UrlHelp, self).url_changed(url) def header(self, descriptor, sig): return '%(name)s\n%(bars)s\n\n.. py:%(type)s:: %(name)s%(sig)s\n' % { 'name': descriptor.name, 'bars': '-' * len(descriptor.name), 'type': descriptor.objtype, 'sig': sig} def get_objtype(self, obj): """Get the object type of the given object and determine wheter the object is considered a class, a module, a function, method or data Parameters ---------- obj: object Returns ------- str One out of {'class', 'module', 'function', 'method', 'data'}""" if inspect.isclass(obj): return 'class' if inspect.ismodule(obj): return 'module' if inspect.isfunction(obj) or isinstance(obj, type(all)): return 'function' if inspect.ismethod(obj) or isinstance(obj, type(str.upper)): return 'method' return 'data' def is_importable(self, modname): """Determine whether members of the given module can be documented with sphinx by using the :func:`sphinx.util.get_module_source` function Parameters ---------- modname: str The __name__ attribute of the module to import Returns ------- bool True if sphinx can import the module""" try: get_module_source(modname) return True except: return False def get_doc(self, descriptor): """Reimplemented to (potentially) use the features from sphinx.ext.autodoc""" obj = descriptor.obj if inspect.ismodule(obj): module = obj else: module = inspect.getmodule(obj) if module is not None and (re.match('__.*__', module.__name__) or not self.is_importable(module.__name__)): module = None isclass = inspect.isclass(obj) # If the module is available, we try to use autodoc if module is not None: doc = '.. currentmodule:: ' + module.__name__ + '\n\n' # a module --> use automodule if inspect.ismodule(obj): doc += self.header(descriptor, '') doc += '.. automodule:: ' + obj.__name__ # an importable class --> use autoclass elif isclass and getattr(module, obj.__name__, None) is not None: doc += self.header(descriptor, '') doc += '.. autoclass:: ' + obj.__name__ # an instance and the class can be imported # --> use super get_doc and autoclass for the tyoe elif descriptor.objtype == 'data' and getattr( module, type(obj).__name__, None) is not None: doc += '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", '.. autoclass:: ' + type(obj).__name__]) # an instance --> use super get_doc for instance and the type elif descriptor.objtype == 'data': cls_doc = super(UrlHelp, self).get_doc(self.describe_object( type(obj), type(obj).__name__)) doc += '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", cls_doc]) # a function or method --> use super get_doc else: doc += super(UrlHelp, self).get_doc(descriptor) # otherwise the object has been defined in this session else: # an instance --> use super get_doc for instance and the type if descriptor.objtype == 'data': cls_doc = super(UrlHelp, self).get_doc(self.describe_object( type(obj), type(obj).__name__)) doc = '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", cls_doc]) # a function or method --> use super get_doc else: doc = super(UrlHelp, self).get_doc(descriptor) return doc.rstrip() + '\n' def process_docstring(self, lines, descriptor): """Process the lines with the napoleon sphinx extension""" lines = list(chain(*(l.splitlines() for l in lines))) lines = NumpyDocstring( lines, what=descriptor.objtype, name=descriptor.name, obj=descriptor.obj).lines() lines = GoogleDocstring( lines, what=descriptor.objtype, name=descriptor.name, obj=descriptor.obj).lines() return indent(super(UrlHelp, self).process_docstring( lines, descriptor))
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 RcParamsWidget(ConfigPage, QWidget): """A configuration page for RcParams instances This page displays the :class:`psyplot.config.rcsetup.RcParams` instance in the :attr:`rc` attribute and let's the user modify it. Notes ----- After the initialization, you have to call the :meth:`initialize` method""" #: the rcParams to use (must be implemented by subclasses) rc = None #: the :class:`RcParamsTree` that is used to display the rcParams tree = None @property def propose_changes(self): """A signal that is emitted if the user changes the values in the rcParams""" return self.tree.propose_changes @property def validChanged(self): """A signal that is emitted if the user changes the valid state of this page""" return self.tree.validChanged @property def changed(self): """True if any changes are proposed by this config page""" return bool(next(self.tree.changed_rc(), None)) @property def is_valid(self): """True if all the settings are valid""" return self.tree.is_valid @property def icon(self): """The icon of this instance in the :class:`Preferences` dialog""" return QIcon(get_icon('rcParams.png')) 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 save_settings_action(self, update=False, target=None): """Create an action to save the selected settings in the :attr:`tree` Parameters ---------- update: bool If True, it is expected that the file already exists and it will be updated. Otherwise, existing files will be overwritten """ def func(): if update: meth = QFileDialog.getOpenFileName else: meth = QFileDialog.getSaveFileName if target is None: fname = meth( self, 'Select a file to %s' % ( 'update' if update else 'create'), self.default_path, 'YAML files (*.yml);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] else: fname = target if not fname: return if update: rc = self.rc.__class__(defaultParams=self.rc.defaultParams) rc.load_from_file(fname) old_keys = list(rc) selected = dict(self.tree.selected_rc()) new_keys = list(selected) rc.update(selected) rc.dump(fname, include_keys=old_keys + new_keys, exclude_keys=[]) else: rc = self.rc.__class__(self.tree.selected_rc(), defaultParams=self.rc.defaultParams) rc.dump(fname, exclude_keys=[]) action = QAction('Update...' if update else 'Overwrite...', self) action.triggered.connect(func) return action def initialize(self, rcParams=None, validators=None, descriptions=None): """Initialize the config page Parameters ---------- rcParams: dict The rcParams to use. If None, the :attr:`rc` attribute of this instance is used validators: dict A mapping from the `rcParams` key to the corresponding validation function for the value. If None, the :attr:`~psyplot.config.rcsetup.RcParams.validate` attribute of the :attr:`rc` attribute is used descriptions: dict A mapping from the `rcParams` key to it's description. If None, the :attr:`~psyplot.config.rcsetup.RcParams.descriptions` attribute of the :attr:`rc` attribute is used""" if rcParams is not None: self.rc = rcParams self.tree.rc = rcParams if validators is not None: self.tree.validators = validators if descriptions is not None: self.tree.descriptions = descriptions self.tree.initialize() def apply_changes(self): """Apply the changes in the config page""" self.tree.apply_changes()
def __init__(self, *args, **kwargs): super(UrlBrowser, self).__init__(*args, **kwargs) # --------------------------------------------------------------------- # ---------------------------- upper buttons -------------------------- # --------------------------------------------------------------------- # adress line self.tb_url = UrlCombo(self) # button to go to previous url self.bt_back = QToolButton(self) # button to go to next url self.bt_ahead = QToolButton(self) # refresh the current url self.bt_refresh = QToolButton(self) # button to go lock to the current url self.bt_lock = QToolButton(self) # button to disable browsing in www self.bt_url_lock = QToolButton(self) # ---------------------------- buttons settings ----------------------- self.bt_back.setIcon(QIcon(get_icon('previous.png'))) self.bt_back.setToolTip('Go back one page') self.bt_ahead.setIcon(QIcon(get_icon('next.png'))) self.bt_back.setToolTip('Go forward one page') self.bt_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.bt_refresh.setToolTip('Refresh the current page') self.bt_lock.setCheckable(True) self.bt_url_lock.setCheckable(True) if not with_qt5 and rcParams['help_explorer.online'] is None: # We now that the browser can crash with Qt4, therefore we disable # the browing in the internet self.bt_url_lock.click() rcParams['help_explorer.online'] = False elif rcParams['help_explorer.online'] is False: self.bt_url_lock.click() elif rcParams['help_explorer.online'] is None: rcParams['help_explorer.online'] = True rcParams.connect('help_explorer.online', self.update_url_lock_from_rc) self.bt_url_lock.clicked.connect(self.toogle_url_lock) self.bt_lock.clicked.connect(self.toogle_lock) # tooltip and icons of lock and url_lock are set in toogle_lock and # toogle_url_lock self.toogle_lock() self.toogle_url_lock() # --------------------------------------------------------------------- # --------- initialization and connection of the web view ------------- # --------------------------------------------------------------------- #: The actual widget showing the html content self.html = QWebEngineView(parent=self) self.html.loadStarted.connect(self.completed) self.html.loadFinished.connect(self.completed) self.tb_url.currentIndexChanged[str].connect(self.browse) self.bt_back.clicked.connect(self.html.back) self.bt_ahead.clicked.connect(self.html.forward) self.bt_refresh.clicked.connect(self.html.reload) self.html.urlChanged.connect(self.url_changed) # --------------------------------------------------------------------- # ---------------------------- layouts -------------------------------- # --------------------------------------------------------------------- # The upper part of the browser containing all the buttons self.button_box = button_box = QHBoxLayout() button_box.addWidget(self.bt_back) button_box.addWidget(self.bt_ahead) button_box.addWidget(self.tb_url) button_box.addWidget(self.bt_refresh) button_box.addWidget(self.bt_lock) button_box.addWidget(self.bt_url_lock) # The upper most layout aranging the button box and the html widget self.vbox = vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) vbox.addLayout(button_box) vbox.addWidget(self.html) self.setLayout(vbox) if self.default_url is not None: self.tb_url.addItem(self.default_url)
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): super(UrlBrowser, self).__init__(*args, **kwargs) # --------------------------------------------------------------------- # ---------------------------- upper buttons -------------------------- # --------------------------------------------------------------------- # adress line self.tb_url = UrlCombo(self) # button to go to previous url self.bt_back = QToolButton(self) # button to go to next url self.bt_ahead = QToolButton(self) # refresh the current url self.bt_refresh = QToolButton(self) # button to go lock to the current url self.bt_lock = QToolButton(self) # button to disable browsing in www self.bt_url_lock = QToolButton(self) # ---------------------------- buttons settings ----------------------- self.bt_back.setIcon(QIcon(get_icon('previous.png'))) self.bt_back.setToolTip('Go back one page') self.bt_ahead.setIcon(QIcon(get_icon('next.png'))) self.bt_back.setToolTip('Go forward one page') self.bt_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.bt_refresh.setToolTip('Refresh the current page') self.bt_lock.setCheckable(True) self.bt_url_lock.setCheckable(True) if not with_qt5 and rcParams['help_explorer.online'] is None: # We now that the browser can crash with Qt4, therefore we disable # the browing in the internet self.bt_url_lock.click() rcParams['help_explorer.online'] = False elif rcParams['help_explorer.online'] is False: self.bt_url_lock.click() elif rcParams['help_explorer.online'] is None: rcParams['help_explorer.online'] = True rcParams.connect('help_explorer.online', self.update_url_lock_from_rc) self.bt_url_lock.clicked.connect(self.toogle_url_lock) self.bt_lock.clicked.connect(self.toogle_lock) # tooltip and icons of lock and url_lock are set in toogle_lock and # toogle_url_lock self.toogle_lock() self.toogle_url_lock() # --------------------------------------------------------------------- # --------- initialization and connection of the web view ------------- # --------------------------------------------------------------------- #: The actual widget showing the html content self.html = QWebEngineView(parent=self) self.html.loadStarted.connect(self.completed) self.html.loadFinished.connect(self.completed) self.tb_url.currentIndexChanged[str].connect(self.browse) self.bt_back.clicked.connect(self.html.back) self.bt_ahead.clicked.connect(self.html.forward) self.bt_refresh.clicked.connect(self.html.reload) self.html.urlChanged.connect(self.url_changed) # --------------------------------------------------------------------- # ---------------------------- layouts -------------------------------- # --------------------------------------------------------------------- # The upper part of the browser containing all the buttons self.button_box = button_box = QHBoxLayout() button_box.addWidget(self.bt_back) button_box.addWidget(self.bt_ahead) button_box.addWidget(self.tb_url) button_box.addWidget(self.bt_refresh) button_box.addWidget(self.bt_lock) button_box.addWidget(self.bt_url_lock) # The upper most layout aranging the button box and the html widget self.vbox = vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) vbox.addLayout(button_box) vbox.addWidget(self.html) self.setLayout(vbox) if self.default_url is not None: self.tb_url.addItem(self.default_url)
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 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())
class UrlBrowser(QFrame): """Very simple browser with session history and autocompletion based upon the :class:`PyQt5.QtWebEngineWidgets.QWebEngineView` class Warnings -------- This class is known to crash under PyQt4 when new web page domains are loaded. Hence it should be handled with care""" completed = _temp_bool_prop( 'completed', "Boolean whether the html page loading is completed.", default=True) url_like_re = re.compile('^\w+://') doc_urls = OrderedDict([ ('startpage', 'https://startpage.com/'), ('psyplot', 'http://psyplot.readthedocs.org/en/latest/'), ('pyplot', 'http://matplotlib.org/api/pyplot_api.html'), ('seaborn', 'http://stanford.edu/~mwaskom/software/seaborn/api.html'), ('cartopy', 'http://scitools.org.uk/cartopy/docs/latest/index.html'), ('xarray', 'http://xarray.pydata.org/en/stable/'), ('pandas', 'http://pandas.pydata.org/pandas-docs/stable/'), ('numpy', 'https://docs.scipy.org/doc/numpy/reference/routines.html'), ]) #: The initial url showed in the webview. If None, nothing will be #: displayed default_url = None #: adress line tb_url = None #: button to go to previous url bt_back = None #: button to go to next url bt_ahead = None #: refresh the current url bt_refresh = None #: button to go lock to the current url bt_lock = None #: button to disable browsing in www bt_url_lock = None #: The upper part of the browser containing all the buttons button_box = None #: The upper most layout aranging the button box and the html widget vbox = None def __init__(self, *args, **kwargs): super(UrlBrowser, self).__init__(*args, **kwargs) # --------------------------------------------------------------------- # ---------------------------- upper buttons -------------------------- # --------------------------------------------------------------------- # adress line self.tb_url = UrlCombo(self) # button to go to previous url self.bt_back = QToolButton(self) # button to go to next url self.bt_ahead = QToolButton(self) # refresh the current url self.bt_refresh = QToolButton(self) # button to go lock to the current url self.bt_lock = QToolButton(self) # button to disable browsing in www self.bt_url_lock = QToolButton(self) # ---------------------------- buttons settings ----------------------- self.bt_back.setIcon(QIcon(get_icon('previous.png'))) self.bt_back.setToolTip('Go back one page') self.bt_ahead.setIcon(QIcon(get_icon('next.png'))) self.bt_back.setToolTip('Go forward one page') self.bt_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.bt_refresh.setToolTip('Refresh the current page') self.bt_lock.setCheckable(True) self.bt_url_lock.setCheckable(True) if not with_qt5 and rcParams['help_explorer.online'] is None: # We now that the browser can crash with Qt4, therefore we disable # the browing in the internet self.bt_url_lock.click() rcParams['help_explorer.online'] = False elif rcParams['help_explorer.online'] is False: self.bt_url_lock.click() elif rcParams['help_explorer.online'] is None: rcParams['help_explorer.online'] = True rcParams.connect('help_explorer.online', self.update_url_lock_from_rc) self.bt_url_lock.clicked.connect(self.toogle_url_lock) self.bt_lock.clicked.connect(self.toogle_lock) # tooltip and icons of lock and url_lock are set in toogle_lock and # toogle_url_lock self.toogle_lock() self.toogle_url_lock() # --------------------------------------------------------------------- # --------- initialization and connection of the web view ------------- # --------------------------------------------------------------------- #: The actual widget showing the html content self.html = QWebEngineView(parent=self) self.html.loadStarted.connect(self.completed) self.html.loadFinished.connect(self.completed) self.tb_url.currentIndexChanged[str].connect(self.browse) self.bt_back.clicked.connect(self.html.back) self.bt_ahead.clicked.connect(self.html.forward) self.bt_refresh.clicked.connect(self.html.reload) self.html.urlChanged.connect(self.url_changed) # --------------------------------------------------------------------- # ---------------------------- layouts -------------------------------- # --------------------------------------------------------------------- # The upper part of the browser containing all the buttons self.button_box = button_box = QHBoxLayout() button_box.addWidget(self.bt_back) button_box.addWidget(self.bt_ahead) button_box.addWidget(self.tb_url) button_box.addWidget(self.bt_refresh) button_box.addWidget(self.bt_lock) button_box.addWidget(self.bt_url_lock) # The upper most layout aranging the button box and the html widget self.vbox = vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) vbox.addLayout(button_box) vbox.addWidget(self.html) self.setLayout(vbox) if self.default_url is not None: self.tb_url.addItem(self.default_url) def browse(self, url): """Make a web browse on the given url and show the page on the Webview widget. """ if self.bt_lock.isChecked(): return if not self.url_like_re.match(url): url = 'https://' + url if self.bt_url_lock.isChecked() and url.startswith('http'): return if not self.completed: logger.debug('Stopping current load...') self.html.stop() self.completed = True logger.debug('Loading %s', url) # we use :meth:`PyQt5.QtWebEngineWidgets.QWebEngineView.setUrl` instead # of :meth:`PyQt5.QtWebEngineWidgets.QWebEngineView.load` because that # changes the url directly and is more useful for unittests self.html.setUrl(QtCore.QUrl(url)) def url_changed(self, url): """Triggered when the url is changed to update the adress line""" try: url = url.toString() except AttributeError: pass logger.debug('url changed to %s', url) try: self.tb_url.setCurrentText(url) except AttributeError: # Qt4 self.tb_url.setEditText(url) self.tb_url.add_text_on_top(url, block=True) def update_url_lock_from_rc(self, online): if (online and self.bt_url_lock.isChecked() or not online and not self.bt_url_lock.isChecked()): self.bt_url_lock.click() def toogle_url_lock(self): """Disable (or enable) the loading of web pages in www""" bt = self.bt_url_lock offline = bt.isChecked() bt.setIcon(QIcon(get_icon( 'world_red.png' if offline else 'world.png'))) online_message = "Go online" if not with_qt5: online_message += ("\nWARNING: This mode is unstable under Qt4 " "and might result in a complete program crash!") bt.setToolTip(online_message if offline else "Offline mode") if rcParams['help_explorer.online'] is offline: rcParams['help_explorer.online'] = not offline def toogle_lock(self): """Disable (or enable) the changing of the current webpage""" bt = self.bt_lock bt.setIcon(QIcon(get_icon( 'lock.png' if bt.isChecked() else 'lock_open.png'))) bt.setToolTip("Unlock" if bt.isChecked() else "Lock to current page")
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, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode(QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [ self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb ]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc)
def __init__(self, *args, **kwargs): """ 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 UrlHelp(UrlBrowser, HelpMixin): """Class to convert rst docstrings to html and show browsers""" #: Object containing the necessary fields to describe an object given to #: the help widget. The descriptor is set up by the :meth:`describe_object` #: method and contains an additional objtype attribute object_descriptor = namedtuple( 'ObjectDescriptor', ['obj', 'name', 'objtype']) can_document_object = with_sphinx can_show_rst = with_sphinx #: menu button with different urls bt_url_menus = None #: sphinx_thread = None def __init__(self, *args, **kwargs): self._temp_dir = 'sphinx_dir' not in kwargs self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp(prefix='psyplot_')) self.build_dir = osp.join(self.sphinx_dir, '_build', 'html') super(UrlHelp, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) if with_sphinx: self.sphinx_thread = SphinxThread(self.sphinx_dir) self.sphinx_thread.html_ready[str].connect(self.browse) self.sphinx_thread.html_error[str].connect( self.error_msg.showTraceback) self.sphinx_thread.html_error[str].connect(logger.debug) rcParams.connect('help_explorer.render_docs_parallel', self.reset_sphinx) rcParams.connect('help_explorer.use_intersphinx', self.reset_sphinx) rcParams.connect('help_explorer.online', self.reset_sphinx) self.bt_connect_console = QToolButton(self) self.bt_connect_console.setCheckable(True) if rcParams['console.connect_to_help']: self.bt_connect_console.setIcon(QIcon(get_icon( 'ipython_console.png'))) self.bt_connect_console.click() else: self.bt_connect_console.setIcon(QIcon(get_icon( 'ipython_console_t.png'))) self.bt_connect_console.clicked.connect(self.toogle_connect_console) rcParams.connect('console.connect_to_help', self.update_connect_console) self.toogle_connect_console() # menu button with different urls self.bt_url_menus = QToolButton(self) self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png'))) self.bt_url_menus.setToolTip('Browse documentations') self.bt_url_menus.setPopupMode(QToolButton.InstantPopup) docu_menu = QMenu(self) for name, url in six.iteritems(self.doc_urls): def to_url(b, url=url): self.browse(url) action = QAction(name, self) action.triggered.connect(to_url) docu_menu.addAction(action) self.bt_url_menus.setMenu(docu_menu) self.button_box.addWidget(self.bt_connect_console) self.button_box.addWidget(self.bt_url_menus) # toogle the lock again to set the bt_url_menus enabled state self.toogle_url_lock() def update_connect_console(self, connect): if (connect and not self.bt_connect_console.isChecked() or not connect and self.bt_connect_console.isChecked()): self.bt_connect_console.click() def toogle_connect_console(self): """Disable (or enable) the loading of web pages in www""" bt = self.bt_connect_console connect = bt.isChecked() bt.setIcon(QIcon(get_icon( 'ipython_console.png' if connect else 'ipython_console_t.png'))) bt.setToolTip("%sonnect the console to the help explorer" % ( "Don't c" if connect else "C")) if rcParams['console.connect_to_help'] is not connect: rcParams['console.connect_to_help'] = connect def reset_sphinx(self, value): """Method that is called if the configuration changes""" if with_sphinx and hasattr(self.sphinx_thread, 'app'): del self.sphinx_thread.app @docstrings.dedent def show_help(self, obj, oname='', files=None): """ Render the rst docu for the given object with sphinx and show it Parameters ---------- %(HelpMixin.show_help.parameters)s """ if self.bt_lock.isChecked(): return return super(UrlHelp, self).show_help(obj, oname=oname, files=files) @docstrings.dedent def show_intro(self, text=''): """ Show the intro text in the explorer Parameters ---------- %(HelpMixin.show_intro.parameters)s""" if self.sphinx_thread is not None: with open(self.sphinx_thread.index_file, 'a') as f: f.write('\n' + text.strip() + '\n\n' + 'Table of Contents\n' '=================\n\n.. toctree::\n') self.sphinx_thread.render(None, None) def show_rst(self, text, oname='', descriptor=None, files=None): """Render restructured text with sphinx and show it Parameters ---------- %(HelpMixin.show_rst.parameters)s""" if self.bt_lock.isChecked() or self.sphinx_thread is None: return False if not oname and descriptor: oname = descriptor.name for f in files or []: shutil.copyfile(f, osp.join(self.sphinx_dir, osp.basename(f))) self.sphinx_thread.render(text, oname) return True def describe_object(self, obj, oname=''): """Describe an object using additionaly the object type from the :meth:`get_objtype` method Returns ------- instance of :attr:`object_descriptor` The descriptor of the object""" return self.object_descriptor(obj, oname, self.get_objtype(obj)) def browse(self, url): """Reimplemented to add file paths to the url string""" url = asstring(url) html_file = osp.join(self.sphinx_dir, '_build', 'html', url + '.html') if osp.exists(html_file): url = file2html(html_file) super(UrlHelp, self).browse(url) def toogle_url_lock(self): """Disable (or enable) the loading of web pages in www""" super(UrlHelp, self).toogle_url_lock() # enable or disable documentation button bt = self.bt_url_lock offline = bt.isChecked() try: self.bt_url_menus.setEnabled(not offline) except AttributeError: # not yet initialized pass def url_changed(self, url): """Reimplemented to remove file paths from the url string""" try: url = asstring(url.toString()) except AttributeError: pass if url.startswith('file://'): fname = html2file(url) if osp.samefile(self.build_dir, osp.commonprefix([ fname, self.build_dir])): url = osp.splitext(osp.basename(fname))[0] super(UrlHelp, self).url_changed(url) def header(self, descriptor, sig): return '%(name)s\n%(bars)s\n\n.. py:%(type)s:: %(name)s%(sig)s\n' % { 'name': descriptor.name, 'bars': '-' * len(descriptor.name), 'type': descriptor.objtype, 'sig': sig} def get_objtype(self, obj): """Get the object type of the given object and determine wheter the object is considered a class, a module, a function, method or data Parameters ---------- obj: object Returns ------- str One out of {'class', 'module', 'function', 'method', 'data'}""" if inspect.isclass(obj): return 'class' if inspect.ismodule(obj): return 'module' if inspect.isfunction(obj) or isinstance(obj, type(all)): return 'function' if inspect.ismethod(obj) or isinstance(obj, type(str.upper)): return 'method' return 'data' def is_importable(self, modname): """Determine whether members of the given module can be documented with sphinx by using the :func:`sphinx.util.get_module_source` function Parameters ---------- modname: str The __name__ attribute of the module to import Returns ------- bool True if sphinx can import the module""" try: get_module_source(modname) return True except Exception: return False def get_doc(self, descriptor): """Reimplemented to (potentially) use the features from sphinx.ext.autodoc""" obj = descriptor.obj if inspect.ismodule(obj): module = obj else: module = inspect.getmodule(obj) if module is not None and (re.match('__.*__', module.__name__) or not self.is_importable(module.__name__)): module = None isclass = inspect.isclass(obj) # If the module is available, we try to use autodoc if module is not None: doc = '.. currentmodule:: ' + module.__name__ + '\n\n' # a module --> use automodule if inspect.ismodule(obj): doc += self.header(descriptor, '') doc += '.. automodule:: ' + obj.__name__ # an importable class --> use autoclass elif isclass and getattr(module, obj.__name__, None) is not None: doc += self.header(descriptor, '') doc += '.. autoclass:: ' + obj.__name__ # an instance and the class can be imported # --> use super get_doc and autoclass for the tyoe elif descriptor.objtype == 'data' and getattr( module, type(obj).__name__, None) is not None: doc += '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", '.. autoclass:: ' + type(obj).__name__]) # an instance --> use super get_doc for instance and the type elif descriptor.objtype == 'data': cls_doc = super(UrlHelp, self).get_doc(self.describe_object( type(obj), type(obj).__name__)) doc += '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", cls_doc]) # a function or method --> use super get_doc else: doc += super(UrlHelp, self).get_doc(descriptor) # otherwise the object has been defined in this session else: # an instance --> use super get_doc for instance and the type if descriptor.objtype == 'data': cls_doc = super(UrlHelp, self).get_doc(self.describe_object( type(obj), type(obj).__name__)) doc = '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", cls_doc]) # a function or method --> use super get_doc else: doc = super(UrlHelp, self).get_doc(descriptor) return doc.rstrip() + '\n' def process_docstring(self, lines, descriptor): """Process the lines with the napoleon sphinx extension""" lines = list(chain(*(l.splitlines() for l in lines))) lines = NumpyDocstring( lines, what=descriptor.objtype, name=descriptor.name, obj=descriptor.obj).lines() lines = GoogleDocstring( lines, what=descriptor.objtype, name=descriptor.name, obj=descriptor.obj).lines() return indent(super(UrlHelp, self).process_docstring( lines, descriptor)) def close(self, *args, **kwargs): if self.sphinx_thread is not None: try: del self.sphinx_thread.app except AttributeError: pass shutil.rmtree(self.build_dir, ignore_errors=True) if self._temp_dir: shutil.rmtree(self.sphinx_dir, ignore_errors=True) del self.sphinx_thread return super(UrlHelp, self).close(*args, **kwargs)
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 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 UrlBrowser(QFrame): """Very simple browser with session history and autocompletion based upon the :class:`PyQt5.QtWebEngineWidgets.QWebEngineView` class Warnings -------- This class is known to crash under PyQt4 when new web page domains are loaded. Hence it should be handled with care""" completed = _temp_bool_prop( 'completed', "Boolean whether the html page loading is completed.", default=True) url_like_re = re.compile('^\w+://') doc_urls = OrderedDict([ ('startpage', 'https://startpage.com/'), ('psyplot', 'http://psyplot.readthedocs.org/en/latest/'), ('pyplot', 'http://matplotlib.org/api/pyplot_api.html'), ('seaborn', 'http://stanford.edu/~mwaskom/software/seaborn/api.html'), ('cartopy', 'http://scitools.org.uk/cartopy/docs/latest/index.html'), ('xarray', 'http://xarray.pydata.org/en/stable/'), ('pandas', 'http://pandas.pydata.org/pandas-docs/stable/'), ('numpy', 'https://docs.scipy.org/doc/numpy/reference/routines.html'), ]) #: The initial url showed in the webview. If None, nothing will be #: displayed default_url = None #: adress line tb_url = None #: button to go to previous url bt_back = None #: button to go to next url bt_ahead = None #: refresh the current url bt_refresh = None #: button to go lock to the current url bt_lock = None #: button to disable browsing in www bt_url_lock = None #: The upper part of the browser containing all the buttons button_box = None #: The upper most layout aranging the button box and the html widget vbox = None def __init__(self, *args, **kwargs): super(UrlBrowser, self).__init__(*args, **kwargs) # --------------------------------------------------------------------- # ---------------------------- upper buttons -------------------------- # --------------------------------------------------------------------- # adress line self.tb_url = UrlCombo(self) # button to go to previous url self.bt_back = QToolButton(self) # button to go to next url self.bt_ahead = QToolButton(self) # refresh the current url self.bt_refresh = QToolButton(self) # button to go lock to the current url self.bt_lock = QToolButton(self) # button to disable browsing in www self.bt_url_lock = QToolButton(self) # ---------------------------- buttons settings ----------------------- self.bt_back.setIcon(QIcon(get_icon('previous.png'))) self.bt_back.setToolTip('Go back one page') self.bt_ahead.setIcon(QIcon(get_icon('next.png'))) self.bt_back.setToolTip('Go forward one page') self.bt_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.bt_refresh.setToolTip('Refresh the current page') self.bt_lock.setCheckable(True) self.bt_url_lock.setCheckable(True) if not with_qt5 and rcParams['help_explorer.online'] is None: # We now that the browser can crash with Qt4, therefore we disable # the browing in the internet self.bt_url_lock.click() rcParams['help_explorer.online'] = False elif rcParams['help_explorer.online'] is False: self.bt_url_lock.click() elif rcParams['help_explorer.online'] is None: rcParams['help_explorer.online'] = True rcParams.connect('help_explorer.online', self.update_url_lock_from_rc) self.bt_url_lock.clicked.connect(self.toogle_url_lock) self.bt_lock.clicked.connect(self.toogle_lock) # tooltip and icons of lock and url_lock are set in toogle_lock and # toogle_url_lock self.toogle_lock() self.toogle_url_lock() # --------------------------------------------------------------------- # --------- initialization and connection of the web view ------------- # --------------------------------------------------------------------- #: The actual widget showing the html content self.html = QWebEngineView(parent=self) self.html.loadStarted.connect(self.completed) self.html.loadFinished.connect(self.completed) self.tb_url.currentIndexChanged[str].connect(self.browse) self.bt_back.clicked.connect(self.html.back) self.bt_ahead.clicked.connect(self.html.forward) self.bt_refresh.clicked.connect(self.html.reload) self.html.urlChanged.connect(self.url_changed) # --------------------------------------------------------------------- # ---------------------------- layouts -------------------------------- # --------------------------------------------------------------------- # The upper part of the browser containing all the buttons self.button_box = button_box = QHBoxLayout() button_box.addWidget(self.bt_back) button_box.addWidget(self.bt_ahead) button_box.addWidget(self.tb_url) button_box.addWidget(self.bt_refresh) button_box.addWidget(self.bt_lock) button_box.addWidget(self.bt_url_lock) # The upper most layout aranging the button box and the html widget self.vbox = vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) vbox.addLayout(button_box) vbox.addWidget(self.html) self.setLayout(vbox) if self.default_url is not None: self.tb_url.addItem(self.default_url) def browse(self, url): """Make a web browse on the given url and show the page on the Webview widget. """ if self.bt_lock.isChecked(): return if not self.url_like_re.match(url): url = 'https://' + url if self.bt_url_lock.isChecked() and url.startswith('http'): return if not self.completed: logger.debug('Stopping current load...') self.html.stop() self.completed = True logger.debug('Loading %s', url) # we use :meth:`PyQt5.QtWebEngineWidgets.QWebEngineView.setUrl` instead # of :meth:`PyQt5.QtWebEngineWidgets.QWebEngineView.load` because that # changes the url directly and is more useful for unittests self.html.setUrl(QtCore.QUrl(url)) def url_changed(self, url): """Triggered when the url is changed to update the adress line""" try: url = url.toString() except AttributeError: pass logger.debug('url changed to %s', url) try: self.tb_url.setCurrentText(url) except AttributeError: # Qt4 self.tb_url.setEditText(url) self.tb_url.add_text_on_top(url, block=True) def update_url_lock_from_rc(self, online): if (online and self.bt_url_lock.isChecked() or not online and not self.bt_url_lock.isChecked()): self.bt_url_lock.click() def toogle_url_lock(self): """Disable (or enable) the loading of web pages in www""" bt = self.bt_url_lock offline = bt.isChecked() bt.setIcon(QIcon( get_icon('world_red.png' if offline else 'world.png'))) online_message = "Go online" if not with_qt5: online_message += ("\nWARNING: This mode is unstable under Qt4 " "and might result in a complete program crash!") bt.setToolTip(online_message if offline else "Offline mode") if rcParams['help_explorer.online'] is offline: rcParams['help_explorer.online'] = not offline def toogle_lock(self): """Disable (or enable) the changing of the current webpage""" bt = self.bt_lock bt.setIcon( QIcon(get_icon('lock.png' if bt.isChecked() else 'lock_open.png'))) bt.setToolTip("Unlock" if bt.isChecked() else "Lock to current page")
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 UrlHelp(UrlBrowser, HelpMixin): """Class to convert rst docstrings to html and show browsers""" #: Object containing the necessary fields to describe an object given to #: the help widget. The descriptor is set up by the :meth:`describe_object` #: method and contains an additional objtype attribute object_descriptor = namedtuple('ObjectDescriptor', ['obj', 'name', 'objtype']) can_document_object = with_sphinx can_show_rst = with_sphinx #: menu button with different urls bt_url_menus = None #: sphinx_thread = None def __init__(self, *args, **kwargs): self._temp_dir = 'sphinx_dir' not in kwargs self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp(prefix='psyplot_')) self.build_dir = osp.join(self.sphinx_dir, '_build', 'html') super(UrlHelp, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) if with_sphinx: self.sphinx_thread = SphinxThread(self.sphinx_dir) self.sphinx_thread.html_ready[str].connect(self.browse) self.sphinx_thread.html_error[str].connect( self.error_msg.showTraceback) self.sphinx_thread.html_error[str].connect(logger.debug) rcParams.connect('help_explorer.render_docs_parallel', self.reset_sphinx) rcParams.connect('help_explorer.use_intersphinx', self.reset_sphinx) rcParams.connect('help_explorer.online', self.reset_sphinx) self.bt_connect_console = QToolButton(self) self.bt_connect_console.setCheckable(True) if rcParams['console.connect_to_help']: self.bt_connect_console.setIcon( QIcon(get_icon('ipython_console.png'))) self.bt_connect_console.click() else: self.bt_connect_console.setIcon( QIcon(get_icon('ipython_console_t.png'))) self.bt_connect_console.clicked.connect(self.toogle_connect_console) rcParams.connect('console.connect_to_help', self.update_connect_console) self.toogle_connect_console() # menu button with different urls self.bt_url_menus = QToolButton(self) self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png'))) self.bt_url_menus.setToolTip('Browse documentations') self.bt_url_menus.setPopupMode(QToolButton.InstantPopup) docu_menu = QMenu(self) for name, url in six.iteritems(self.doc_urls): def to_url(b, url=url): self.browse(url) action = QAction(name, self) action.triggered.connect(to_url) docu_menu.addAction(action) self.bt_url_menus.setMenu(docu_menu) self.button_box.addWidget(self.bt_connect_console) self.button_box.addWidget(self.bt_url_menus) # toogle the lock again to set the bt_url_menus enabled state self.toogle_url_lock() def update_connect_console(self, connect): if (connect and not self.bt_connect_console.isChecked() or not connect and self.bt_connect_console.isChecked()): self.bt_connect_console.click() def toogle_connect_console(self): """Disable (or enable) the loading of web pages in www""" bt = self.bt_connect_console connect = bt.isChecked() bt.setIcon( QIcon( get_icon('ipython_console.png' if connect else 'ipython_console_t.png'))) bt.setToolTip("%sonnect the console to the help explorer" % ("Don't c" if connect else "C")) if rcParams['console.connect_to_help'] is not connect: rcParams['console.connect_to_help'] = connect def reset_sphinx(self, value): """Method that is called if the configuration changes""" if with_sphinx and hasattr(self.sphinx_thread, 'app'): del self.sphinx_thread.app @docstrings.dedent def show_help(self, obj, oname='', files=None): """ Render the rst docu for the given object with sphinx and show it Parameters ---------- %(HelpMixin.show_help.parameters)s """ if self.bt_lock.isChecked(): return return super(UrlHelp, self).show_help(obj, oname=oname, files=files) @docstrings.dedent def show_intro(self, text=''): """ Show the intro text in the explorer Parameters ---------- %(HelpMixin.show_intro.parameters)s""" if self.sphinx_thread is not None: with open(self.sphinx_thread.index_file, 'a') as f: f.write('\n' + text.strip() + '\n\n' + 'Table of Contents\n' '=================\n\n.. toctree::\n') self.sphinx_thread.render(None, None) def show_rst(self, text, oname='', descriptor=None, files=None): """Render restructured text with sphinx and show it Parameters ---------- %(HelpMixin.show_rst.parameters)s""" if self.bt_lock.isChecked() or self.sphinx_thread is None: return False if not oname and descriptor: oname = descriptor.name for f in files or []: shutil.copyfile(f, osp.join(self.sphinx_dir, osp.basename(f))) self.sphinx_thread.render(text, oname) return True def describe_object(self, obj, oname=''): """Describe an object using additionaly the object type from the :meth:`get_objtype` method Returns ------- instance of :attr:`object_descriptor` The descriptor of the object""" return self.object_descriptor(obj, oname, self.get_objtype(obj)) def browse(self, url): """Reimplemented to add file paths to the url string""" url = asstring(url) html_file = osp.join(self.sphinx_dir, '_build', 'html', url + '.html') if osp.exists(html_file): url = file2html(html_file) super(UrlHelp, self).browse(url) def toogle_url_lock(self): """Disable (or enable) the loading of web pages in www""" super(UrlHelp, self).toogle_url_lock() # enable or disable documentation button bt = self.bt_url_lock offline = bt.isChecked() try: self.bt_url_menus.setEnabled(not offline) except AttributeError: # not yet initialized pass def url_changed(self, url): """Reimplemented to remove file paths from the url string""" try: url = asstring(url.toString()) except AttributeError: pass if url.startswith('file://'): fname = html2file(url) if osp.samefile(self.build_dir, osp.commonprefix([fname, self.build_dir])): url = osp.splitext(osp.basename(fname))[0] super(UrlHelp, self).url_changed(url) def header(self, descriptor, sig): return '%(name)s\n%(bars)s\n\n.. py:%(type)s:: %(name)s%(sig)s\n' % { 'name': descriptor.name, 'bars': '-' * len(descriptor.name), 'type': descriptor.objtype, 'sig': sig } def get_objtype(self, obj): """Get the object type of the given object and determine wheter the object is considered a class, a module, a function, method or data Parameters ---------- obj: object Returns ------- str One out of {'class', 'module', 'function', 'method', 'data'}""" if inspect.isclass(obj): return 'class' if inspect.ismodule(obj): return 'module' if inspect.isfunction(obj) or isinstance(obj, type(all)): return 'function' if inspect.ismethod(obj) or isinstance(obj, type(str.upper)): return 'method' return 'data' def is_importable(self, modname): """Determine whether members of the given module can be documented with sphinx by using the :func:`sphinx.util.get_module_source` function Parameters ---------- modname: str The __name__ attribute of the module to import Returns ------- bool True if sphinx can import the module""" try: get_module_source(modname) return True except Exception: return False def get_doc(self, descriptor): """Reimplemented to (potentially) use the features from sphinx.ext.autodoc""" obj = descriptor.obj if inspect.ismodule(obj): module = obj else: module = inspect.getmodule(obj) if module is not None and (re.match('__.*__', module.__name__) or not self.is_importable(module.__name__)): module = None isclass = inspect.isclass(obj) # If the module is available, we try to use autodoc if module is not None: doc = '.. currentmodule:: ' + module.__name__ + '\n\n' # a module --> use automodule if inspect.ismodule(obj): doc += self.header(descriptor, '') doc += '.. automodule:: ' + obj.__name__ # an importable class --> use autoclass elif isclass and getattr(module, obj.__name__, None) is not None: doc += self.header(descriptor, '') doc += '.. autoclass:: ' + obj.__name__ # an instance and the class can be imported # --> use super get_doc and autoclass for the tyoe elif descriptor.objtype == 'data' and getattr( module, type(obj).__name__, None) is not None: doc += '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", '.. autoclass:: ' + type(obj).__name__ ]) # an instance --> use super get_doc for instance and the type elif descriptor.objtype == 'data': cls_doc = super(UrlHelp, self).get_doc( self.describe_object(type(obj), type(obj).__name__)) doc += '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", cls_doc ]) # a function or method --> use super get_doc else: doc += super(UrlHelp, self).get_doc(descriptor) # otherwise the object has been defined in this session else: # an instance --> use super get_doc for instance and the type if descriptor.objtype == 'data': cls_doc = super(UrlHelp, self).get_doc( self.describe_object(type(obj), type(obj).__name__)) doc = '\n\n'.join([ super(UrlHelp, self).get_doc(descriptor), "Class docstring\n===============", cls_doc ]) # a function or method --> use super get_doc else: doc = super(UrlHelp, self).get_doc(descriptor) return doc.rstrip() + '\n' def process_docstring(self, lines, descriptor): """Process the lines with the napoleon sphinx extension""" lines = list(chain(*(l.splitlines() for l in lines))) lines = NumpyDocstring(lines, what=descriptor.objtype, name=descriptor.name, obj=descriptor.obj).lines() lines = GoogleDocstring(lines, what=descriptor.objtype, name=descriptor.name, obj=descriptor.obj).lines() return indent( super(UrlHelp, self).process_docstring(lines, descriptor)) def close(self, *args, **kwargs): if self.sphinx_thread is not None: try: del self.sphinx_thread.app except AttributeError: pass shutil.rmtree(self.build_dir, ignore_errors=True) if self._temp_dir: shutil.rmtree(self.sphinx_dir, ignore_errors=True) del self.sphinx_thread return super(UrlHelp, self).close(*args, **kwargs)
def add_item(self, what, get_artists, plot_func=None, remove_func=None, can_be_plotted=None): """Add a plot object to the table Parameters ---------- what: str The description of the plot object get_artists: function A function that takes no arguments and returns the artists plot_func: function, optional A function that takes no arguments and makes the plot. remove_func: function, optional A function that takes no arguments and removes the plot. can_be_plotted: function, optional A function that takes no argument and returns True if the plot can be made. """ def hide_or_show(checked): checked = checked is True or checked == Qt.Checked artist = None for artist in get_artists(): artist.set_visible(checked) if artist is not None: self.draw_figs(get_artists()) def trigger_plot_btn(): a = next(iter(get_artists()), None) if a is None: if can_be_plotted is None or can_be_plotted(): plot_func() cb.setChecked(True) btn.setIcon(QIcon(get_icon('invalid.png'))) btn.setToolTip('Remove ' + what) self.draw_figs(get_artists()) cb.setEnabled(True) else: fig = a.axes.figure figs = {a.axes.figure for a in get_artists()} remove_func() btn.setIcon(QIcon(get_icon('valid.png'))) btn.setToolTip('Show ' + what) for fig in figs: fig.canvas.draw_idle() cb.setEnabled(False) self.get_artists_funcs[what] = get_artists a = next(iter(get_artists()), None) cb = QCheckBox() cb.label = what self.hide_funcs[what] = hide_or_show cb.setChecked( Qt.Checked if a is not None and a.get_visible() else Qt.Unchecked) cb.stateChanged.connect(hide_or_show) row = self.rowCount() self.setRowCount(row + 1) self.setVerticalHeaderLabels(list(self.get_artists_funcs)) self.setCellWidget(row, 0, cb) if plot_func is not None: btn = QToolButton() btn.setIcon( QIcon(get_icon(('in' if a is None else '') + 'valid.png'))) btn.clicked.connect(trigger_plot_btn) btn.setEnabled(can_be_plotted is None or can_be_plotted()) btn.setToolTip(('Remove ' if a else 'Show ') + what) self.can_be_plotted_funcs[what] = can_be_plotted btn.label = what self.setCellWidget(row, 1, btn)
class RcParamsWidget(ConfigPage, QWidget): """A configuration page for RcParams instances This page displays the :class:`psyplot.config.rcsetup.RcParams` instance in the :attr:`rc` attribute and let's the user modify it. Notes ----- After the initialization, you have to call the :meth:`initialize` method""" #: the rcParams to use (must be implemented by subclasses) rc = None #: the :class:`RcParamsTree` that is used to display the rcParams tree = None @property def propose_changes(self): """A signal that is emitted if the user changes the values in the rcParams""" return self.tree.propose_changes @property def validChanged(self): """A signal that is emitted if the user changes the valid state of this page""" return self.tree.validChanged @property def changed(self): """True if any changes are proposed by this config page""" return bool(next(self.tree.changed_rc(), None)) @property def is_valid(self): """True if all the settings are valid""" return self.tree.is_valid @property def icon(self): """The icon of this instance in the :class:`Preferences` dialog""" return QIcon(get_icon('rcParams.png')) 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 save_settings_action(self, update=False, target=None): """Create an action to save the selected settings in the :attr:`tree` Parameters ---------- update: bool If True, it is expected that the file already exists and it will be updated. Otherwise, existing files will be overwritten """ def func(): if update: meth = QFileDialog.getOpenFileName else: meth = QFileDialog.getSaveFileName if target is None: fname = meth( self, 'Select a file to %s' % ('update' if update else 'create'), self.default_path, 'YAML files (*.yml);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] else: fname = target if not fname: return if update: rc = self.rc.__class__(defaultParams=self.rc.defaultParams) rc.load_from_file(fname) old_keys = list(rc) selected = dict(self.tree.selected_rc()) new_keys = list(selected) rc.update(selected) rc.dump(fname, include_keys=old_keys + new_keys, exclude_keys=[]) else: rc = self.rc.__class__(self.tree.selected_rc(), defaultParams=self.rc.defaultParams) rc.dump(fname, exclude_keys=[]) action = QAction('Update...' if update else 'Overwrite...', self) action.triggered.connect(func) return action def initialize(self, rcParams=None, validators=None, descriptions=None): """Initialize the config page Parameters ---------- rcParams: dict The rcParams to use. If None, the :attr:`rc` attribute of this instance is used validators: dict A mapping from the `rcParams` key to the corresponding validation function for the value. If None, the :attr:`~psyplot.config.rcsetup.RcParams.validate` attribute of the :attr:`rc` attribute is used descriptions: dict A mapping from the `rcParams` key to it's description. If None, the :attr:`~psyplot.config.rcsetup.RcParams.descriptions` attribute of the :attr:`rc` attribute is used""" if rcParams is not None: self.rc = rcParams self.tree.rc = rcParams if validators is not None: self.tree.validators = validators if descriptions is not None: self.tree.descriptions = descriptions self.tree.initialize() def apply_changes(self): """Apply the changes in the config page""" self.tree.apply_changes()
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())