Example #1
0
 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)
Example #2
0
    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()
Example #3
0
    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()
Example #4
0
    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()
Example #5
0
    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)
Example #6
0
 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)
Example #7
0
    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)
Example #8
0
    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)
Example #9
0
    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)
Example #10
0
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())
Example #11
0
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)
Example #12
0
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 &rarr; 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)
Example #13
0
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
Example #14
0
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))
Example #15
0
    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)
Example #16
0
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()
Example #17
0
    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)
Example #18
0
    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)
Example #19
0
    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)
Example #20
0
    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)
Example #21
0
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())
Example #22
0
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")
Example #23
0
    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)
Example #24
0
    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)
Example #25
0
    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)
Example #26
0
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)
Example #27
0
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)
Example #28
0
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)
Example #29
0
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")
Example #30
0
    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)
Example #31
0
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)
Example #32
0
    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)
Example #33
0
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()
Example #34
0
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())