Example #1
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 #2
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 #3
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 #4
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)