Ejemplo n.º 1
0
class PydocBrowser(PluginMainWidget):
    """PyDoc browser widget."""

    ENABLE_SPINNER = True

    # --- Signals
    # ------------------------------------------------------------------------
    sig_load_finished = Signal()
    """
    This signal is emitted to indicate the help page has finished loading.
    """
    def __init__(self, name=None, plugin=None, parent=None):
        super().__init__(name, plugin, parent=parent)

        self._is_running = False
        self.home_url = None
        self.server = None

        # Widgets
        self.label = QLabel(_("Package:"))
        self.url_combo = UrlComboBox(self)
        self.webview = FrameWebView(self,
                                    handle_links=self.get_conf('handle_links'))
        self.find_widget = FindReplace(self)

        # Setup
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()
        self.url_combo.setMaxCount(self.get_conf('max_history_entries'))
        tip = _('Write a package name here, e.g. pandas')
        self.url_combo.lineEdit().setPlaceholderText(tip)
        self.url_combo.lineEdit().setToolTip(tip)
        self.webview.setup()
        self.webview.set_zoom_factor(self.get_conf('zoom_factor'))

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.webview)
        layout.addSpacing(1)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Signals
        self.url_combo.valid.connect(
            lambda x: self._handle_url_combo_activation())
        self.webview.loadStarted.connect(self._start)
        self.webview.loadFinished.connect(self._finish)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self._change_url)

        if not WEBENGINE:
            self.webview.iconChanged.connect(self._handle_icon_change)

    # --- PluginMainWidget API
    # ------------------------------------------------------------------------
    def get_title(self):
        return _('Online help')

    def get_focus_widget(self):
        self.url_combo.lineEdit().selectAll()
        return self.url_combo

    def setup(self):
        # Actions
        home_action = self.create_action(
            PydocBrowserActions.Home,
            text=_("Home"),
            tip=_("Home"),
            icon=self.create_icon('home'),
            triggered=self.go_home,
        )
        find_action = self.create_action(
            PydocBrowserActions.Find,
            text=_("Find"),
            tip=_("Find text"),
            icon=self.create_icon('find'),
            toggled=self.toggle_find_widget,
            initial=False,
        )
        stop_action = self.get_action(WebViewActions.Stop)
        refresh_action = self.get_action(WebViewActions.Refresh)

        # Toolbar
        toolbar = self.get_main_toolbar()
        for item in [
                self.get_action(WebViewActions.Back),
                self.get_action(WebViewActions.Forward),
                refresh_action,
                stop_action,
                home_action,
                self.label,
                self.url_combo,
                self.get_action(WebViewActions.ZoomIn),
                self.get_action(WebViewActions.ZoomOut),
                find_action,
        ]:
            self.add_item_to_toolbar(
                item,
                toolbar=toolbar,
                section=PydocBrowserMainToolbarSections.Main,
            )

        # Signals
        self.find_widget.visibility_changed.connect(find_action.setChecked)

        for __, action in self.get_actions().items():
            if action:
                # IMPORTANT: Since we are defining the main actions in here
                # and the context is WidgetWithChildrenShortcut we need to
                # assign the same actions to the children widgets in order
                # for shortcuts to work
                self.webview.addAction(action)

        self.sig_toggle_view_changed.connect(self.initialize)

    def update_actions(self):
        stop_action = self.get_action(WebViewActions.Stop)
        refresh_action = self.get_action(WebViewActions.Refresh)

        refresh_action.setVisible(not self._is_running)
        stop_action.setVisible(self._is_running)

    # --- Private API
    # ------------------------------------------------------------------------
    def _start(self):
        """Webview load started."""
        self._is_running = True
        self.start_spinner()
        self.update_actions()

    def _finish(self, code):
        """Webview load finished."""
        self._is_running = False
        self.stop_spinner()
        self.update_actions()
        self.sig_load_finished.emit()

    def _continue_initialization(self):
        """Load home page."""
        self.go_home()
        QApplication.restoreOverrideCursor()

    def _handle_url_combo_activation(self):
        """Load URL from combo box first item."""
        if not self._is_running:
            text = str(self.url_combo.currentText())
            self.go_to(self.text_to_url(text))
        else:
            self.get_action(WebViewActions.Stop).trigger()

        self.get_focus_widget().setFocus()

    def _change_url(self, url):
        """
        Displayed URL has changed -> updating URL combo box.
        """
        self.url_combo.add_text(self.url_to_text(url))

    def _handle_icon_change(self):
        """
        Handle icon changes.
        """
        self.url_combo.setItemIcon(self.url_combo.currentIndex(),
                                   self.webview.icon())
        self.setWindowIcon(self.webview.icon())

    # --- Qt overrides
    # ------------------------------------------------------------------------
    def closeEvent(self, event):
        self.server.quit_server()
        event.accept()

    # --- Public API
    # ------------------------------------------------------------------------
    def load_history(self, history):
        """
        Load history.

        Parameters
        ----------
        history: list
            List of searched items.
        """
        self.url_combo.addItems(history)

    @Slot(bool)
    def initialize(self, checked=True):
        """
        Start pydoc server.

        Parameters
        ----------
        checked: bool, optional
            This method is connected to the `sig_toggle_view_changed` signal,
            so that the first time the widget is made visible it will start
            the server. Default is True.
        """
        if checked and self.server is None:
            self.sig_toggle_view_changed.disconnect(self.initialize)
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            QApplication.processEvents()
            self.start_server()

    def is_server_running(self):
        """Return True if pydoc server is already running."""
        return self.server is not None

    def start_server(self):
        """Start pydoc server."""
        if self.server is None:
            self.set_home_url('http://127.0.0.1:{}/'.format(PORT))
        elif self.server.isRunning():
            self.server.sig_server_started.disconnect(
                self._continue_initialization)
            self.server.quit()

        self.server = PydocServer(port=PORT)
        self.server.sig_server_started.connect(self._continue_initialization)
        self.server.start()

    def quit_server(self):
        """Quit the server."""
        if self.server is None:
            return

        if self.server.is_running():
            self.server.sig_server_started.disconnect(
                self._continue_initialization)
            self.server.quit_server()
            self.server.quit()

    def get_label(self):
        """Return address label text"""
        return _("Package:")

    def reload(self):
        """Reload page."""
        if self.server:
            self.webview.reload()

    def text_to_url(self, text):
        """
        Convert text address into QUrl object.

        Parameters
        ----------
        text: str
            Url address.
        """
        if text != 'about:blank':
            text += '.html'

        if text.startswith('/'):
            text = text[1:]

        return QUrl(self.home_url.toString() + text)

    def url_to_text(self, url):
        """
        Convert QUrl object to displayed text in combo box.

        Parameters
        ----------
        url: QUrl
            Url address.
        """
        string_url = url.toString()
        if 'about:blank' in string_url:
            return 'about:blank'
        elif 'get?key=' in string_url or 'search?key=' in string_url:
            return url.toString().split('=')[-1]

        return osp.splitext(str(url.path()))[0][1:]

    def set_home_url(self, text):
        """
        Set home URL.

        Parameters
        ----------
        text: str
            Home url address.
        """
        self.home_url = QUrl(text)

    def set_url(self, url):
        """
        Set current URL.

        Parameters
        ----------
        url: QUrl or str
            Url address.
        """
        self._change_url(url)
        self.go_to(url)

    def go_to(self, url_or_text):
        """
        Go to page URL.
        """
        if isinstance(url_or_text, str):
            url = QUrl(url_or_text)
        else:
            url = url_or_text

        self.webview.load(url)

    @Slot()
    def go_home(self):
        """
        Go to home page.
        """
        if self.home_url is not None:
            self.set_url(self.home_url)

    def get_zoom_factor(self):
        """
        Get the current zoom factor.

        Returns
        -------
        int
            Zoom factor.
        """
        return self.webview.get_zoom_factor()

    def get_history(self):
        """
        Return the list of history items in the combobox.

        Returns
        -------
        list
            List of strings.
        """
        history = []
        for index in range(self.url_combo.count()):
            history.append(str(self.url_combo.itemText(index)))

        return history

    @Slot(bool)
    def toggle_find_widget(self, state):
        """
        Show/hide the find widget.

        Parameters
        ----------
        state: bool
            True to show and False to hide the find widget.
        """
        if state:
            self.find_widget.show()
        else:
            self.find_widget.hide()