From 5f78b96cb11fbc5d9c8619f7540366506ca89ca4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 21 Feb 2014 22:00:41 +0100 Subject: [PATCH] Move cur_* methods for TabbedBrowser to CurCommandDispatcher class --- TODO | 1 - qutebrowser/app.py | 36 +-- qutebrowser/widgets/browser.py | 573 +++++++++++++++++---------------- 3 files changed, 322 insertions(+), 288 deletions(-) diff --git a/TODO b/TODO index c4ec729be..80d68c8e5 100644 --- a/TODO +++ b/TODO @@ -40,7 +40,6 @@ set settings/colors/bindings via commandline more completions (URLs, ...) SSL handling add classes for ConfigOptions with section/name/desc/validate()/get() so we can validate config options, get transformed values and write a default config easily -clean up cur_* mess for browser Minor features ============== diff --git a/qutebrowser/app.py b/qutebrowser/app.py index fbdca8f3f..187fcfd92 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -122,7 +122,7 @@ class QuteBrowser(QApplication): self.mainwindow.tabs.setFocus) self.commandparser.error.connect(self.mainwindow.status.disp_error) self.searchparser.do_search.connect( - self.mainwindow.tabs.cur_search) + self.mainwindow.tabs.cur.search) self.keyparser.commandparser.error.connect( self.mainwindow.status.disp_error) self.keyparser.keystring_updated.connect( @@ -349,34 +349,34 @@ class QuteBrowser(QApplication): browser = self.mainwindow.tabs handlers = { - 'open': browser.openurl, + 'open': browser.cur.openurl, 'opencur': browser.opencur, 'tabopen': browser.tabopen, 'tabopencur': browser.tabopencur, 'quit': self.shutdown, - 'tabclose': browser.cur_close, + 'tabclose': browser.tabclose, 'tabprev': browser.switch_prev, 'tabnext': browser.switch_next, - 'reload': browser.cur_reload, - 'stop': browser.cur_stop, - 'back': browser.cur_back, - 'forward': browser.cur_forward, - 'print': browser.cur_print, - 'scroll': browser.cur_scroll, - 'scroll_page': browser.cur_scroll_page, - 'scroll_perc_x': browser.cur_scroll_percent_x, - 'scroll_perc_y': browser.cur_scroll_percent_y, + 'reload': browser.cur.reloadpage, + 'stop': browser.cur.stop, + 'back': browser.cur.back, + 'forward': browser.cur.forward, + 'print': browser.cur.printpage, + 'scroll': browser.cur.scroll, + 'scroll_page': browser.cur.scroll_page, + 'scroll_perc_x': browser.cur.scroll_percent_x, + 'scroll_perc_y': browser.cur.scroll_percent_y, 'undo': browser.undo_close, 'pyeval': self.pyeval, 'nextsearch': self.searchparser.nextsearch, - 'yank': browser.cur_yank, - 'yanktitle': browser.cur_yank_title, + 'yank': browser.cur.yank, + 'yanktitle': browser.cur.yank_title, 'paste': browser.paste, 'tabpaste': browser.tabpaste, 'crash': self.crash, - 'version': lambda: browser.openurl('qute:version'), - 'zoomin': browser.cur_zoom_in, - 'zoomout': browser.cur_zoom_out, + 'version': lambda: browser.cur.openurl('qute:version'), + 'zoomin': browser.cur.zoom_in, + 'zoomout': browser.cur.zoom_out, } handler = handlers[cmd] @@ -401,7 +401,7 @@ class QuteBrowser(QApplication): except Exception as e: # pylint: disable=broad-except out = ': '.join([e.__class__.__name__, str(e)]) qutescheme.pyeval_output = out - self.mainwindow.tabs.openurl('qute:pyeval') + self.mainwindow.tabs.cur.openurl('qute:pyeval') def crash(self): """Crash for debugging purposes. diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index faa85c110..11ec58cfd 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -27,7 +27,7 @@ import functools import sip from PyQt5.QtWidgets import QApplication, QShortcut, QSizePolicy -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QObject from PyQt5.QtGui import QClipboard from PyQt5.QtPrintSupport import QPrintPreviewDialog from PyQt5.QtNetwork import QNetworkReply @@ -61,6 +61,8 @@ class TabbedBrowser(TabWidget): _url_stack: Stack of URLs of closed tabs. _space: Space QShortcut to avoid garbage collection _tabs: A list of open tabs. + cur: A CurCommandDispatcher instance to dispatch commands to the + current tab. Signals: cur_progress: Progress of the current tab changed (loadProgress). @@ -68,7 +70,7 @@ class TabbedBrowser(TabWidget): cur_load_finished: Current tab finished loading (loadFinished) cur_statusbar_message: Current tab got a statusbar message (statusBarMessage) - cur_tmp_message: Current tab needs to show a temporary message. + cur_temp_message: Current tab needs to show a temporary message. cur_url_changed: Current URL changed (urlChanged) cur_link_hovered: Link hovered in current tab (linkHovered) cur_scroll_perc_changed: Scroll percentage of current tab changed. @@ -108,7 +110,9 @@ class TabbedBrowser(TabWidget): self._space = QShortcut(self) self._space.setKey(Qt.Key_Space) self._space.setContext(Qt.WidgetWithChildrenShortcut) - self._space.activated.connect(lambda: self.cur_scroll_page(0, 1)) + self._space.activated.connect(lambda: self.cur.scroll_page(0, 1)) + self.cur = CurCommandDispatcher(self) + self.cur.temp_message.connect(self.cur_temp_message) def _cb_tab_shutdown(self, tab): """Called after a tab has been shut down completely. @@ -129,28 +133,7 @@ class TabbedBrowser(TabWidget): logging.debug("Tab shutdown complete.") self.shutdown_complete.emit() - def _cur_scroll_percent(self, perc=None, count=None, orientation=None): - """Inner logic for cur_scroll_percent_(x|y). - - Args: - perc: How many percent to scroll, or None - count: How many percent to scroll, or None - orientation: Qt.Horizontal or Qt.Vertical - - """ - if perc is None and count is None: - perc = 100 - elif perc is None: - perc = int(count) - else: - perc = float(perc) - frame = self.currentWidget().page_.mainFrame() - m = frame.scrollBarMaximum(orientation) - if m == 0: - return - frame.setScrollBarValue(orientation, int(m * perc / 100)) - - def _widget(self, count=None): + def cntwidget(self, count=None): """Return a widget based on a count/idx. Args: @@ -263,6 +246,35 @@ class TabbedBrowser(TabWidget): tab.shutdown(callback=functools.partial(self._cb_tab_shutdown, tab)) + def tabclose(self, count=None): + """Close the current/[count]th tab. + + Command handler for :close. + + Args: + count: The tab index to close, or None + + Emit: + quit: If last tab was closed and last_close in config is set to + quit. + + """ + idx = self.currentIndex() if count is None else count - 1 + tab = self.cntwidget(count) + if tab is None: + return + last_close = config.config.get('tabbar', 'last_close') + if self.count() > 1: + # FIXME maybe we actually should store the webview objects here + self._url_stack.append(tab.url()) + self.removeTab(idx) + tab.shutdown(callback=functools.partial(self._cb_tab_shutdown, + tab)) + elif last_close == 'quit': + self.quit.emit() + elif last_close == 'blank': + tab.openurl('about:blank') + def tabopen(self, url): """Open a new tab with a given url. @@ -325,248 +337,6 @@ class TabbedBrowser(TabWidget): if self._url_stack: self.tabopen(self._url_stack.pop()) - def cur_close(self, count=None): - """Close the current/[count]th tab. - - Command handler for :close. - - Args: - count: The tab index to close, or None - - Emit: - quit: If last tab was closed and last_close in config is set to - quit. - - """ - idx = self.currentIndex() if count is None else count - 1 - tab = self._widget(count) - if tab is None: - return - last_close = config.config.get('tabbar', 'last_close') - if self.count() > 1: - # FIXME maybe we actually should store the webview objects here - self._url_stack.append(tab.url()) - self.removeTab(idx) - tab.shutdown(callback=functools.partial(self._cb_tab_shutdown, - tab)) - elif last_close == 'quit': - self.quit.emit() - elif last_close == 'blank': - tab.openurl('about:blank') - - def openurl(self, url, count=None): - """Open an url in the current/[count]th tab. - - Command handler for :open. - - Args: - url: The URL to open. - count: The tab index to open the URL in, or None. - - """ - tab = self._widget(count) - if tab is None: - if count is None: - # We want to open an URL in the current tab, but none exists - # yet. - self.tabopen(url) - else: - # Explicit count with a tab that doesn't exist. - return - else: - tab.openurl(url) - - def cur_reload(self, count=None): - """Reload the current/[count]th tab. - - Command handler for :reload. - - Args: - count: The tab index to reload, or None. - - """ - tab = self._widget(count) - if tab is not None: - tab.reload() - - def cur_stop(self, count=None): - """Stop loading in the current/[count]th tab. - - Command handler for :stop. - - Args: - count: The tab index to stop, or None. - - """ - tab = self._widget(count) - if tab is not None: - tab.stop() - - def cur_print(self, count=None): - """Print the current/[count]th tab. - - Command handler for :print. - - Args: - count: The tab index to print, or None. - - """ - # FIXME that does not what I expect - tab = self._widget(count) - if tab is not None: - preview = QPrintPreviewDialog(self) - preview.paintRequested.connect(tab.print) - preview.exec_() - - def cur_back(self, count=1): - """Go back in the history of the current tab. - - Command handler for :back. - - Args: - count: How many pages to go back. - - """ - # FIXME display warning if beginning of history - for i in range(count): # pylint: disable=unused-variable - self.currentWidget().back() - - def cur_forward(self, count=1): - """Go forward in the history of the current tab. - - Command handler for :forward. - - Args: - count: How many pages to go forward. - - """ - # FIXME display warning if end of history - for i in range(count): # pylint: disable=unused-variable - self.currentWidget().forward() - - @pyqtSlot(str, int) - def cur_search(self, text, flags): - """Search for text in the current page. - - Args: - text: The text to search for. - flags: The QWebPage::FindFlags. - - """ - self.currentWidget().findText(text, flags) - - def cur_scroll(self, dx, dy, count=1): - """Scroll the current tab by count * dx/dy. - - Command handler for :scroll. - - Args: - dx: How much to scroll in x-direction. - dy: How much to scroll in x-direction. - count: multiplier - - """ - dx = int(count) * float(dx) - dy = int(count) * float(dy) - self.currentWidget().page_.mainFrame().scroll(dx, dy) - - def cur_scroll_percent_x(self, perc=None, count=None): - """Scroll the current tab to a specific percent of the page. - - Command handler for :scroll_perc_x. - - Args: - perc: Percentage to scroll. - count: Percentage to scroll. - - """ - self._cur_scroll_percent(perc, count, Qt.Horizontal) - - def cur_scroll_percent_y(self, perc=None, count=None): - """Scroll the current tab to a specific percent of the page. - - Command handler for :scroll_perc_y - - Args: - perc: Percentage to scroll. - count: Percentage to scroll. - - """ - self._cur_scroll_percent(perc, count, Qt.Vertical) - - def cur_scroll_page(self, mx, my, count=1): - """Scroll the frame page-wise. - - Args: - mx: How many pages to scroll to the right. - my: How many pages to scroll down. - count: multiplier - - """ - # FIXME this might not work with HTML frames - page = self.currentWidget().page_ - size = page.viewportSize() - page.mainFrame().scroll(int(count) * float(mx) * size.width(), - int(count) * float(my) * size.height()) - - def cur_yank(self, sel=False): - """Yank the current url to the clipboard or primary selection. - - Command handler for :yank. - - Args: - sel: True to use primary selection, False to use clipboard - - Emit: - cur_temp_message to display a temporary message. - - """ - clip = QApplication.clipboard() - url = urlutils.urlstring(self.currentWidget().url()) - mode = QClipboard.Selection if sel else QClipboard.Clipboard - clip.setText(url, mode) - self.cur_temp_message.emit('URL yanked to {}'.format( - 'primary selection' if sel else 'clipboard')) - - def cur_yank_title(self, sel=False): - """Yank the current title to the clipboard or primary selection. - - Command handler for :yanktitle. - - Args: - sel: True to use primary selection, False to use clipboard - - Emit: - cur_temp_message to display a temporary message. - - """ - clip = QApplication.clipboard() - title = self.tabText(self.currentIndex()) - mode = QClipboard.Selection if sel else QClipboard.Clipboard - clip.setText(title, mode) - self.cur_temp_message.emit('Title yanked to {}'.format( - 'primary selection' if sel else 'clipboard')) - - def cur_zoom_in(self, count=1): - """Zoom in in the current tab. - - Args: - count: How many steps to take. - - """ - tab = self.currentWidget() - tab.zoom(count) - - def cur_zoom_out(self, count=1): - """Zoom out in the current tab. - - Args: - count: How many steps to take. - - """ - tab = self.currentWidget() - tab.zoom(-count) - def switch_prev(self, count=1): """Switch to the ([count]th) previous tab. @@ -613,7 +383,7 @@ class TabbedBrowser(TabWidget): mode = QClipboard.Selection if sel else QClipboard.Clipboard url = clip.text(mode) logging.debug("Clipboard contained: '{}'".format(url)) - self.openurl(url) + self.cur.openurl(url) def tabpaste(self, sel=False): """Open a page from the clipboard in a new tab. @@ -658,6 +428,271 @@ class TabbedBrowser(TabWidget): self.resized.emit(self.geometry()) +class CurCommandDispatcher(QObject): + + """Command dispatcher for TabbedBrowser. + + Contains all commands which are related to the current tab. + + Attributes: + tabs: The TabbedBrowser object. + + Signals: + temp_message: Connected to TabbedBrowser signal. + + """ + + # FIXME maybe subclassing would be more clean? + + temp_message = pyqtSignal(str) + + def __init__(self, parent): + """Constructor. + + Uses setattr to get some methods from parent. + + Args: + parent: The TabbedBrowser for this dispatcher. + + """ + super().__init__(parent) + self.tabs = parent + + def _scroll_percent(self, perc=None, count=None, orientation=None): + """Inner logic for scroll_percent_(x|y). + + Args: + perc: How many percent to scroll, or None + count: How many percent to scroll, or None + orientation: Qt.Horizontal or Qt.Vertical + + """ + if perc is None and count is None: + perc = 100 + elif perc is None: + perc = int(count) + else: + perc = float(perc) + frame = self.tabs.currentWidget().page_.mainFrame() + m = frame.scrollBarMaximum(orientation) + if m == 0: + return + frame.setScrollBarValue(orientation, int(m * perc / 100)) + + def openurl(self, url, count=None): + """Open an url in the current/[count]th tab. + + Command handler for :open. + + Args: + url: The URL to open. + count: The tab index to open the URL in, or None. + + """ + tab = self.tabs.cntwidget(count) + if tab is None: + if count is None: + # We want to open an URL in the current tab, but none exists + # yet. + self.tabs.tabopen(url) + else: + # Explicit count with a tab that doesn't exist. + return + else: + tab.openurl(url) + + def reloadpage(self, count=None): + """Reload the current/[count]th tab. + + Command handler for :reload. + + Args: + count: The tab index to reload, or None. + + """ + tab = self.tabs.cntwidget(count) + if tab is not None: + tab.reload() + + def stop(self, count=None): + """Stop loading in the current/[count]th tab. + + Command handler for :stop. + + Args: + count: The tab index to stop, or None. + + """ + tab = self.tabs.cntwidget(count) + if tab is not None: + tab.stop() + + def printpage(self, count=None): + """Print the current/[count]th tab. + + Command handler for :print. + + Args: + count: The tab index to print, or None. + + """ + # FIXME that does not what I expect + tab = self.tabs.cntwidget(count) + if tab is not None: + preview = QPrintPreviewDialog(self) + preview.paintRequested.connect(tab.print) + preview.exec_() + + def back(self, count=1): + """Go back in the history of the current tab. + + Command handler for :back. + + Args: + count: How many pages to go back. + + """ + # FIXME display warning if beginning of history + for i in range(count): # pylint: disable=unused-variable + self.tabs.currentWidget().back() + + def forward(self, count=1): + """Go forward in the history of the current tab. + + Command handler for :forward. + + Args: + count: How many pages to go forward. + + """ + # FIXME display warning if end of history + for i in range(count): # pylint: disable=unused-variable + self.tabs.currentWidget().forward() + + @pyqtSlot(str, int) + def search(self, text, flags): + """Search for text in the current page. + + Args: + text: The text to search for. + flags: The QWebPage::FindFlags. + + """ + self.tabs.currentWidget().findText(text, flags) + + def scroll(self, dx, dy, count=1): + """Scroll the current tab by count * dx/dy. + + Command handler for :scroll. + + Args: + dx: How much to scroll in x-direction. + dy: How much to scroll in x-direction. + count: multiplier + + """ + dx = int(count) * float(dx) + dy = int(count) * float(dy) + self.tabs.currentWidget().page_.mainFrame().scroll(dx, dy) + + def scroll_percent_x(self, perc=None, count=None): + """Scroll the current tab to a specific percent of the page. + + Command handler for :scroll_perc_x. + + Args: + perc: Percentage to scroll. + count: Percentage to scroll. + + """ + self._scroll_percent(perc, count, Qt.Horizontal) + + def scroll_percent_y(self, perc=None, count=None): + """Scroll the current tab to a specific percent of the page. + + Command handler for :scroll_perc_y + + Args: + perc: Percentage to scroll. + count: Percentage to scroll. + + """ + self._scroll_percent(perc, count, Qt.Vertical) + + def scroll_page(self, mx, my, count=1): + """Scroll the frame page-wise. + + Args: + mx: How many pages to scroll to the right. + my: How many pages to scroll down. + count: multiplier + + """ + # FIXME this might not work with HTML frames + page = self.tabs.currentWidget().page_ + size = page.viewportSize() + page.mainFrame().scroll(int(count) * float(mx) * size.width(), + int(count) * float(my) * size.height()) + + def yank(self, sel=False): + """Yank the current url to the clipboard or primary selection. + + Command handler for :yank. + + Args: + sel: True to use primary selection, False to use clipboard + + Emit: + temp_message to display a temporary message. + + """ + clip = QApplication.clipboard() + url = urlutils.urlstring(self.tabs.currentWidget().url()) + mode = QClipboard.Selection if sel else QClipboard.Clipboard + clip.setText(url, mode) + self.temp_message.emit('URL yanked to {}'.format( + 'primary selection' if sel else 'clipboard')) + + def yank_title(self, sel=False): + """Yank the current title to the clipboard or primary selection. + + Command handler for :yanktitle. + + Args: + sel: True to use primary selection, False to use clipboard + + Emit: + temp_message to display a temporary message. + + """ + clip = QApplication.clipboard() + title = self.tabs.tabText(self.tabs.currentIndex()) + mode = QClipboard.Selection if sel else QClipboard.Clipboard + clip.setText(title, mode) + self.temp_message.emit('Title yanked to {}'.format( + 'primary selection' if sel else 'clipboard')) + + def zoom_in(self, count=1): + """Zoom in in the current tab. + + Args: + count: How many steps to take. + + """ + tab = self.tabs.currentWidget() + tab.zoom(count) + + def zoom_out(self, count=1): + """Zoom out in the current tab. + + Args: + count: How many steps to take. + + """ + tab = self.tabs.currentWidget() + tab.zoom(-count) + + class BrowserTab(QWebView): """One browser tab in TabbedBrowser. @@ -759,7 +794,7 @@ class BrowserTab(QWebView): if self._open_new_tab: self.open_tab.emit(url) else: - self.openurl(url) + self.cur.openurl(url) def shutdown(self, callback=None): """Shut down the tab cleanly and remove it.