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
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 #3
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 #4
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")