From 1095e24f98e03281b42ca81ff9f8924319533859 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 20 Jan 2014 15:58:49 +0100 Subject: [PATCH] Refactor ALL the things - Remove super() where unneeded - Add docstrings where applicable - Remove setObjectName calls - Lots and lots of smaller changes --- qutebrowser/app.py | 112 +++++++++++++++++------------- qutebrowser/commands/commands.py | 15 +++- qutebrowser/commands/keys.py | 81 +++++++++++++-------- qutebrowser/commands/utils.py | 62 +++++++++++------ qutebrowser/widgets/browser.py | 107 ++++++++++++++++------------ qutebrowser/widgets/mainwindow.py | 13 ++-- qutebrowser/widgets/statusbar.py | 94 +++++++++++++++---------- qutebrowser/widgets/tabbar.py | 2 +- 8 files changed, 301 insertions(+), 185 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3d1ee530c..ac408f1f7 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -1,16 +1,27 @@ import sys -import argparse import logging -import signal -from PyQt5.QtWidgets import QWidget, QApplication +from signal import signal, SIGINT +from argparse import ArgumentParser + +from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QUrl, QTimer + +import qutebrowser.commands.utils as cmdutils from qutebrowser.widgets.mainwindow import MainWindow from qutebrowser.commands.keys import KeyParser from qutebrowser.utils.config import Config -import qutebrowser.commands.utils as cmdutils from qutebrowser.utils.appdirs import AppDirs class QuteBrowser(QApplication): + """Main object for QuteBrowser""" + dirs = None # AppDirs - config/cache directories + config = None # Config(Parser) object + mainwindow = None + commandparser = None + keyparser = None + args = None # ArgumentParser + timer = None # QTimer for python hacks + def __init__(self): super().__init__(sys.argv) @@ -24,14 +35,15 @@ class QuteBrowser(QApplication): self.commandparser = cmdutils.CommandParser() self.keyparser = KeyParser(self.mainwindow) - super().aboutToQuit.connect(self.config.save) + self.aboutToQuit.connect(self.config.save) self.mainwindow.tabs.keypress.connect(self.keyparser.handle) self.keyparser.set_cmd_text.connect(self.mainwindow.status.cmd.set_cmd) self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.parse) - self.mainwindow.status.cmd.got_cmd.connect(self.mainwindow.tabs.setFocus) + self.mainwindow.status.cmd.got_cmd.connect( + self.mainwindow.tabs.setFocus) self.commandparser.error.connect(self.mainwindow.status.disp_error) self.keyparser.keystring_updated.connect( - self.mainwindow.status.txt.set_keystring) + self.mainwindow.status.txt.set_keystring) self.init_cmds() self.mainwindow.show() @@ -39,41 +51,37 @@ class QuteBrowser(QApplication): self.python_hacks() def python_hacks(self): - qapp = super(QApplication, self) - - ### Make python exceptions work + """Gets around some PyQt-oddities by evil hacks""" + ## Make python exceptions work sys._excepthook = sys.excepthook def exception_hook(exctype, value, traceback): sys._excepthook(exctype, value, traceback) # FIXME save open tabs here - qapp.exit(1) + self.exit(1) sys.excepthook = exception_hook - ### Quit on SIGINT - signal.signal(signal.SIGINT, lambda *args: - qapp.exit(128 + signal.SIGINT)) + ## Quit on SIGINT + signal(SIGINT, lambda *args: self.exit(128 + SIGINT)) - ### hack to make Ctrl+C work by passing control to the Python - ### interpreter once all 500ms (lambda to ignore args) + ## hack to make Ctrl+C work by passing control to the Python + ## interpreter once all 500ms (lambda to ignore args) self.timer = QTimer() self.timer.start(500) self.timer.timeout.connect(lambda: None) def parseopts(self): - parser = argparse.ArgumentParser("usage: %(prog)s [options]") + """Parse command line options""" + parser = ArgumentParser("usage: %(prog)s [options]") parser.add_argument('-l', '--log', dest='loglevel', - help='Set loglevel', default=0) + help='Set loglevel', default='info') self.args = parser.parse_args() def initlog(self): - """ Initialisation of the log """ - if self.args.loglevel: - loglevel = self.args.loglevel - else: - loglevel = 'info' + """Initialisation of the log""" + loglevel = self.args.loglevel numeric_level = getattr(logging, loglevel.upper(), None) if not isinstance(numeric_level, int): - raise ValueError('Invalid log level: %s' % loglevel) + raise ValueError('Invalid log level: {}'.format(loglevel)) logging.basicConfig( level=numeric_level, format='%(asctime)s [%(levelname)s] ' @@ -81,55 +89,63 @@ class QuteBrowser(QApplication): datefmt='%Y-%m-%d %H:%M:%S') def init_cmds(self): + """Initialisation of the qutebrowser commands""" cmdutils.register_all() - cmds = cmdutils.cmd_dict - for cmd in cmds.values(): + for cmd in cmdutils.cmd_dict.values(): cmd.signal.connect(self.cmd_handler) self.keyparser.from_config_sect(self.config['keybind']) def cmd_handler(self, tpl): + """Handler which gets called from all commands and delegates the + specific actions. + + tpl -- A tuple in the form (count, argv) where argv is [cmd, arg, ...] + + All handlers supporting a count should have a keyword argument count. + """ (count, argv) = tpl cmd = argv[0] args = argv[1:] handlers = { - 'open': self.mainwindow.tabs.openurl, - 'tabopen': self.mainwindow.tabs.tabopen, - 'quit': super().quit, - 'tabclose': self.mainwindow.tabs.close_act, - 'tabprev': self.mainwindow.tabs.switch_prev, - 'tabnext': self.mainwindow.tabs.switch_next, - 'reload': self.mainwindow.tabs.reload_act, - 'stop': self.mainwindow.tabs.stop_act, - 'back': self.mainwindow.tabs.back_act, - 'forward': self.mainwindow.tabs.forward_act, - 'print': self.mainwindow.tabs.print_act, - 'scrolldown': self.mainwindow.tabs.scroll_down_act, - 'scrollup': self.mainwindow.tabs.scroll_up_act, - 'scrollleft': self.mainwindow.tabs.scroll_left_act, - 'scrollright': self.mainwindow.tabs.scroll_right_act, - 'scrollstart': self.mainwindow.tabs.scroll_start_act, - 'scrollend': self.mainwindow.tabs.scroll_end_act, - 'undo': self.mainwindow.tabs.undo_close, - 'pyeval': self.pyeval + 'open': self.mainwindow.tabs.openurl, + 'tabopen': self.mainwindow.tabs.tabopen, + 'quit': self.quit, + 'tabclose': self.mainwindow.tabs.cur_close, + 'tabprev': self.mainwindow.tabs.switch_prev, + 'tabnext': self.mainwindow.tabs.switch_next, + 'reload': self.mainwindow.tabs.cur_reload, + 'stop': self.mainwindow.tabs.cur_stop, + 'back': self.mainwindow.tabs.cur_back, + 'forward': self.mainwindow.tabs.cur_forward, + 'print': self.mainwindow.tabs.cur_print, + 'scrolldown': self.mainwindow.tabs.cur_scroll_down, + 'scrollup': self.mainwindow.tabs.cur_scroll_up, + 'scrollleft': self.mainwindow.tabs.cur_scroll_left, + 'scrollright': self.mainwindow.tabs.cur_scroll_right, + 'scrollstart': self.mainwindow.tabs.cur_scroll_start, + 'scrollend': self.mainwindow.tabs.cur_scroll_end, + 'undo': self.mainwindow.tabs.undo_close, + 'pyeval': self.pyeval, } handler = handlers[cmd] - sender = self.sender() - if sender.count: + if self.sender().count: handler(*args, count=count) else: handler(*args) def pyeval(self, s): + """Evaluates a python string, handler for the pyeval command""" try: r = eval(s) out = repr(r) except Exception as e: out = ': '.join([e.__class__.__name__, str(e)]) - # FIXME we probably want some nicer interface to display these about: pages + # FIXME we probably want some nicer interface to display these about: + # pages tab = self.mainwindow.tabs.currentWidget() tab.setUrl(QUrl('about:pyeval')) tab.setContent(out.encode('UTF-8'), 'text/plain') diff --git a/qutebrowser/commands/commands.py b/qutebrowser/commands/commands.py index afb069f76..daa26f69e 100644 --- a/qutebrowser/commands/commands.py +++ b/qutebrowser/commands/commands.py @@ -1,6 +1,19 @@ -from PyQt5.QtCore import pyqtSignal from qutebrowser.commands.utils import Command +"""All command classes. These are automatically propagated from commands.utils +via inspect. + +A command class can set the following properties: + + nargs -- Number of arguments. Either a number, '?' (0 or 1), '+' (1 or + more), or '*' (any). Default: 0 + + split_args -- If arguments should be split or not. Default: True + + count -- If the command supports a count. Default: False +""" + + class Open(Command): nargs = 1 split_args = False diff --git a/qutebrowser/commands/keys.py b/qutebrowser/commands/keys.py index 73a6ce2a7..d42bee18f 100644 --- a/qutebrowser/commands/keys.py +++ b/qutebrowser/commands/keys.py @@ -1,26 +1,43 @@ -from PyQt5.QtCore import QObject, Qt, pyqtSignal -from PyQt5.QtWidgets import QShortcut -from PyQt5.QtGui import QKeySequence -import qutebrowser.commands.utils as cmdutils import logging import re +from PyQt5.QtCore import QObject, pyqtSignal + +import qutebrowser.commands.utils as cmdutils + class KeyParser(QObject): - keystring = '' + """Parser for vim-like key sequences""" + keystring = '' # The currently entered key sequence + # Signal emitted when the statusbar should set a partial command set_cmd_text = pyqtSignal(str) + # Signal emitted when the keystring is updated keystring_updated = pyqtSignal(str) + # Keybindings key_to_cmd = {} + MATCH_PARTIAL = 0 + MATCH_DEFINITIVE = 1 + MATCH_NONE = 2 + def from_config_sect(self, sect): + """Loads keybindings from a ConfigParser section, in the config format + key = command, e.g. + gg = scrollstart + """ for (key, cmd) in sect.items(): - logging.debug('registered: {} -> {}'.format(key, cmd)) + logging.debug('registered key: {} -> {}'.format(key, cmd)) self.key_to_cmd[key] = cmdutils.cmd_dict[cmd] def handle(self, e): + """Wrapper for _handle to emit keystring_updated after _handle""" self._handle(e) self.keystring_updated.emit(self.keystring) def _handle(self, e): + """Handle a new keypress. + + e -- the KeyPressEvent from Qt + """ logging.debug('Got key: {} / text: "{}"'.format(e.key(), e.text())) txt = e.text().strip() if not txt: @@ -44,18 +61,20 @@ class KeyParser(QObject): # If a keychain is ambigious, we probably should set up a QTimer with a # configurable timeout, which triggers cmd.run() when expiring. Then # when we enter _handle() again in time we stop the timer. - try: - cmd = self.key_to_cmd[cmdstr] - except KeyError: - if self._partial_match(cmdstr, txt): - logging.debug('No match for "{}" (added {})'.format( - self.keystring, txt)) - return - else: - logging.debug('Giving up with "{}", no matches'.format( - self.keystring)) - self.keystring = '' - return + + (match, cmd) = self._match_key(cmdstr) + + if match == self.MATCH_DEFINITIVE: + pass + elif match == self.MATCH_PARTIAL: + logging.debug('No match for "{}" (added {})'.format(self.keystring, + txt)) + return + elif match == self.MATCH_NONE: + logging.debug('Giving up with "{}", no matches'.format( + self.keystring)) + self.keystring = '' + return self.keystring = '' count = int(countstr) if countstr else None @@ -69,13 +88,19 @@ class KeyParser(QObject): else: cmd.run() - def _partial_match(self, cmdstr, txt): - pos = len(cmdstr) - for cmd in self.key_to_cmd: - try: - if cmdstr[-1] == cmd[pos-1]: - return True - except IndexError: - continue - else: - return False + def _match_key(self, cmdstr): + """Tries to match a given cmdstr with any defined command""" + try: + cmd = self.key_to_cmd[cmdstr] + return (self.MATCH_DEFINITIVE, cmd) + except KeyError: + # No definitive match, check if there's a chance of a partial match + for cmd in self.key_to_cmd: + try: + if cmdstr[-1] == cmd[len(cmdstr) - 1]: + return (self.MATCH_PARTIAL, None) + except IndexError: + # current cmd is shorter than our cmdstr, so it won't match + continue + # no definitive and no partial matches if we arrived here + return (self.MATCH_NONE, None) diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index 925ee4a85..da5478dbb 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -1,31 +1,37 @@ +"""Various command utils and the Command base class""" + import inspect -import sys import logging import shlex + from PyQt5.QtCore import QObject, pyqtSignal cmd_dict = {} -def register_all(): - import qutebrowser.commands.commands - def is_cmd(obj): - return (inspect.isclass(obj) and - obj.__module__ == 'qutebrowser.commands.commands') +class ArgumentCountError(TypeError): + pass - for (name, cls) in inspect.getmembers(qutebrowser.commands.commands, - is_cmd): - if cls.bind: - obj = cls() - cmd_dict[obj.name] = obj +def register_all(): + """Register and initialize all commands.""" + # We do this here to avoid a circular import, since commands.commands + # imports Command from this module. + import qutebrowser.commands.commands + for (name, cls) in inspect.getmembers( + qutebrowser.commands.commands, (lambda o: inspect.isclass(o) and + o.__module__ == 'qutebrowser.commands.commands')): + obj = cls() + cmd_dict[obj.name] = obj class CommandParser(QObject): - error = pyqtSignal(str) + """Parser for qutebrowser commandline commands""" + error = pyqtSignal(str) # Emitted if there's an error def parse(self, text): + """Parses a command and runs its handler""" parts = text.strip().split(maxsplit=1) - # FIXME maybe we should handle unambigious shorthands for commands here? - # Or at least we should add :q for :quit. + # FIXME maybe we should handle unambigious shorthands for commands + # here? Or at least we should add :q for :quit. cmd = parts[0] try: obj = cmd_dict[cmd] @@ -42,17 +48,19 @@ class CommandParser(QObject): try: obj.check(args) - except TypeError: + except ArgumentCountError: self.error.emit("{}: invalid argument count".format(cmd)) return obj.run(args) class Command(QObject): + """Base skeleton for a command. See the module help for + qutebrowser.commands.commands for details. + """ nargs = 0 name = None signal = None count = False - bind = True split_args = True signal = pyqtSignal(tuple) @@ -62,16 +70,28 @@ class Command(QObject): self.name = self.__class__.__name__.lower() def check(self, args): + """Check if the argument count is valid. Raise ArgumentCountError if + not. + """ if ((isinstance(self.nargs, int) and len(args) != self.nargs) or (self.nargs == '?' and len(args) > 1) or (self.nargs == '+' and len(args) < 1)): - raise TypeError("Invalid argument count!") + # for nargs == '*', anything is okay + raise ArgumentCountError def run(self, args=None, count=None): - countstr = ' * {}'.format(count) if count is not None else '' - argstr = ", ".join(args) if args else '' - logging.debug("Cmd called: {}({}){}".format(self.name, argstr, - countstr)) + """Runs the command. + + args -- Arguments to the command. + count -- Command repetition count. + """ + dbgout = ["command called:", self.name] + if args: + dbgout += args + if count is not None: + dbgout.append("* {}".format(count)) + logging.debug(' '.join(dbgout)) + argv = [self.name] if args is not None: argv += args diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index 4390623d5..da3e68b2b 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -1,106 +1,128 @@ -from PyQt5.QtCore import QObject, pyqtSlot, QUrl, pyqtSignal, Qt, QPoint -from PyQt5.QtPrintSupport import QPrintPreviewDialog -from PyQt5.QtWebKitWidgets import QWebView -from qutebrowser.widgets.tabbar import TabWidget import logging +from PyQt5.QtCore import QUrl, pyqtSignal, Qt, QPoint +from PyQt5.QtPrintSupport import QPrintPreviewDialog +from PyQt5.QtWebKitWidgets import QWebView + +from qutebrowser.widgets.tabbar import TabWidget + class TabbedBrowser(TabWidget): - cur_progress = pyqtSignal(int) - cur_load_finished = pyqtSignal(bool) + """A TabWidget with QWebViews inside""" + + cur_progress = pyqtSignal(int) # Progress of the current tab changed + cur_load_finished = pyqtSignal(bool) # Current tab finished loading keypress = pyqtSignal('QKeyEvent') - url_stack = [] + _url_stack = [] # Stack of URLs of closed tabs def __init__(self, parent): super().__init__(parent) - self.currentChanged.connect(self.index_changed) + self.currentChanged.connect(self._currentChanged_handler) self.tabopen("http://ddg.gg/") def tabopen(self, url): + """Opens a new tab with a given url""" tab = BrowserTab(self) tab.openurl(url) self.addTab(tab, url) self.setCurrentWidget(tab) - self.progress_changed(tab.progress) - tab.loadProgress.connect(self.progress_changed) - tab.loadFinished.connect(self.load_finished) + self.cur_progress.emit(tab.progress) + tab.loadProgress.connect( + lambda *args: self._filter_signals(self.cur_progress, *args)) + tab.loadFinished.connect( + lambda *args: self._filter_signals(self.cur_load_finished, *args)) # FIXME should we really bind this to loadStarted? Sometimes the URL # isn't set correctly at this point, e.g. when doing # setContent(..., baseUrl=QUrl('foo')) - tab.loadStarted.connect(self.init_title) - tab.titleChanged.connect(self.update_title) + tab.loadStarted.connect(self._loadStarted_handler) + tab.titleChanged.connect(self._titleChanged_handler) def openurl(self, url): + """Opens an url in the current tab""" tab = self.currentWidget() tab.openurl(url) def undo_close(self): - if self.url_stack: - self.tabopen(self.url_stack.pop()) + """Undos closing a tab""" + if self._url_stack: + self.tabopen(self._url_stack.pop()) - def close_act(self): + def cur_close(self): + """Closes the current tab""" if self.count() > 1: idx = self.currentIndex() tab = self.currentWidget() # FIXME maybe we should add the QUrl object here and deal with QUrls everywhere # FIXME maybe we actually should store the webview objects here - self.url_stack.append(tab.url().url()) + self._url_stack.append(tab.url().url()) self.removeTab(idx) else: # FIXME pass - def reload_act(self): + def cur_reload(self): + """Reloads the current tab""" self.currentWidget().reload() - def stop_act(self): + def cur_stop(self): + """Stops loading in the current tab""" self.currentWidget().stop() - def print_act(self): + def cur_print(self): + """Prints the current tab""" # FIXME that does not what I expect preview = QPrintPreviewDialog() preview.paintRequested.connect(self.currentWidget().print) preview.exec_() - def back_act(self): + def cur_back(self): + """Goes back in the history of the current tab""" # FIXME display warning if beginning of history self.currentWidget().back() - def forward_act(self): + def cur_forward(self): + """Goes forward in the history of the current tab""" # FIXME display warning if end of history self.currentWidget().forward() - def scroll_down_act(self, count=None): + def cur_scroll_down(self, count=None): + """Scrolls the current tab down""" if count is None: count = 50 self.currentWidget().page().mainFrame().scroll(0, count) - def scroll_up_act(self, count=None): + def cur_scroll_up(self, count=None): + """Scrolls the current tab up""" if count is None: count = 50 self.currentWidget().page().mainFrame().scroll(0, -count) - def scroll_left_act(self, count=None): + def cur_scroll_left(self, count=None): + """Scrolls the current tab left""" if count is None: count = 50 self.currentWidget().page().mainFrame().scroll(-count, 0) - def scroll_right_act(self, count=None): + def cur_scroll_right(self, count=None): + """Scrolls the current tab right""" if count is None: count = 50 self.currentWidget().page().mainFrame().scroll(count, 0) - def scroll_start_act(self): + def cur_scroll_start(self): + """Scrolls the current tab to the beginning""" frame = self.currentWidget().page().mainFrame() cur_pos = frame.scrollPosition() frame.setScrollPosition(QPoint(cur_pos.x(), 0)) - def scroll_end_act(self): + def cur_scroll_end(self): + """Scrolls the current tab to the end""" frame = self.currentWidget().page().mainFrame() cur_pos = frame.scrollPosition() size = frame.contentsSize() frame.setScrollPosition(QPoint(cur_pos.x(), size.height())) def switch_prev(self): + """Switches to the previous tab""" idx = self.currentIndex() if idx > 0: self.setCurrentIndex(idx - 1) @@ -109,6 +131,7 @@ class TabbedBrowser(TabWidget): pass def switch_next(self): + """Switches to the next tab""" idx = self.currentIndex() if idx < self.count() - 1: self.setCurrentIndex(idx + 1) @@ -116,21 +139,22 @@ class TabbedBrowser(TabWidget): # FIXME pass - def progress_changed(self, prog): - self.filter_signals(self.cur_progress, prog) + def keyPressEvent(self, e): + self.keypress.emit(e) + super().keyPressEvent(e) - def load_finished(self, ok): - self.filter_signals(self.cur_load_finished, ok) - - def update_title(self, text): + def _titleChanged_handler(self, text): if text: self.setTabText(self.indexOf(self.sender()), text) - def init_title(self): + def _loadStarted_handler(self): s = self.sender() self.setTabText(self.indexOf(s), s.url().toString()) - def filter_signals(self, signal, *args): + def _filter_signals(self, signal, *args): + """Filters signals, and triggers TabbedBrowser signals if the signal + was sent from the _current_ tab and not from any other one. + """ dbgstr = "{} ({})".format( signal.signal, ','.join([str(e) for e in args])) if self.currentWidget() == self.sender(): @@ -139,16 +163,12 @@ class TabbedBrowser(TabWidget): else: logging.debug('{} - ignoring'.format(dbgstr)) - def index_changed(self, idx): + def _currentChanged_handler(self, idx): tab = self.widget(idx) self.cur_progress.emit(tab.progress) - def keyPressEvent(self, e): - self.keypress.emit(e) - super().keyPressEvent(e) - class BrowserTab(QWebView): - parent = None + """One browser tab in TabbedBrowser""" progress = 0 def __init__(self, parent): @@ -160,9 +180,10 @@ class BrowserTab(QWebView): self.show() def openurl(self, url): + """Opens an URL in the browser""" if not url.startswith('http://'): url = 'http://' + url - super().load(QUrl(url)) + self.load(QUrl(url)) def set_progress(self, prog): self.progress = prog diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 50d13136a..bde609745 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -1,23 +1,24 @@ -from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget) +from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QWidget from qutebrowser.widgets.statusbar import StatusBar from qutebrowser.widgets.browser import TabbedBrowser class MainWindow(QMainWindow): + """The main window of QuteBrowser""" + cwidget = None + vbox = None + tabs = None + status = None + def __init__(self): super().__init__() - self.setObjectName(self.__class__.__name__) - self.cwidget = QWidget(self) - self.cwidget.setObjectName("cwidget") self.setCentralWidget(self.cwidget) self.vbox = QVBoxLayout(self.cwidget) - self.vbox.setObjectName("vbox") self.vbox.setContentsMargins(0, 0, 0, 0) self.vbox.setSpacing(0) self.tabs = TabbedBrowser(self) - self.tabs.setObjectName("tabs") self.vbox.addWidget(self.tabs) self.status = StatusBar(self) diff --git a/qutebrowser/widgets/statusbar.py b/qutebrowser/widgets/statusbar.py index 9ebb89c05..de77c08d2 100644 --- a/qutebrowser/widgets/statusbar.py +++ b/qutebrowser/widgets/statusbar.py @@ -1,20 +1,23 @@ -from PyQt5.QtWidgets import QLineEdit, QHBoxLayout, QLabel, QWidget, QShortcut, QProgressBar, QSizePolicy -from PyQt5.QtCore import pyqtSignal, Qt, QSize -from PyQt5.QtGui import QValidator, QKeySequence import logging +from PyQt5.QtWidgets import (QLineEdit, QHBoxLayout, QLabel, QWidget, + QShortcut, QProgressBar, QSizePolicy) +from PyQt5.QtCore import pyqtSignal, Qt, QSize +from PyQt5.QtGui import QValidator, QKeySequence + class StatusBar(QWidget): - has_error = False - parent = None + """The statusbar at the bottom of the mainwindow""" + has_error = False # Statusbar is currently in error mode + hbox = None + cmd = None + txt = None + prog = None # TODO: the statusbar should be a bit smaller def __init__(self, parent): super().__init__(parent) - self.parent = parent - self.setObjectName(self.__class__.__name__) self.set_color("white", "black") self.hbox = QHBoxLayout(self) - self.hbox.setObjectName("status_hbox") self.hbox.setContentsMargins(0, 0, 0, 0) self.hbox.setSpacing(0) @@ -28,6 +31,9 @@ class StatusBar(QWidget): self.hbox.addWidget(self.prog) def set_color(self, fg, bg): + """Sets background and foreground color of the statusbar""" + # FIXME maybe this would be easier with setColor()? + self.setStyleSheet(""" * { background: """ + bg + """; @@ -36,23 +42,25 @@ class StatusBar(QWidget): }""") def disp_error(self, text): + """Displays an error in the statusbar""" self.has_error = True self.set_color('white', 'red') self.txt.error = text def clear_error(self): + """Clears a displayed error from the status bar""" if self.has_error: self.has_error = False self.set_color('white', 'black') self.txt.error = '' class StatusProgress(QProgressBar): - parent = None + """ The progress bar part of the status bar""" + bar = None - def __init__(self, parent): - self.parent = parent - super().__init__(parent) - self.setObjectName(self.__class__.__name__) + def __init__(self, bar): + self.bar = bar + super().__init__(bar) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.setTextVisible(False) @@ -70,13 +78,15 @@ class StatusProgress(QProgressBar): self.hide() def minimumSizeHint(self): - status_size = self.parent.size() + status_size = self.bar.size() return QSize(100, status_size.height()) def sizeHint(self): return self.minimumSizeHint() def set_progress(self, prog): + """Sets the progress of the bar and shows/hides it if necessary""" + # TODO display failed loading in some meaningful way? if prog == 100: self.setValue(prog) self.hide() @@ -88,14 +98,14 @@ class StatusProgress(QProgressBar): self.hide() class StatusText(QLabel): + """The text part of the status bar, composedof several 'widgets'""" keystring = '' error = '' text = '' scrollperc = '' - def __init__(self, parent): - super().__init__(parent) - self.setObjectName(self.__class__.__name__) + def __init__(self, bar): + super().__init__(bar) self.setStyleSheet("padding-right: 1px") def __setattr__(self, name, value): @@ -103,23 +113,35 @@ class StatusText(QLabel): self.update() def set_keystring(self, s): + """Setter to be used as a Qt slot""" self.keystring = s def update(self): - super().setText(' '.join([self.keystring, self.error, self.text, - self.scrollperc])) + """Update the text displayed""" + self.setText(' '.join([self.keystring, self.error, self.text, + self.scrollperc])) + class StatusCommand(QLineEdit): - got_cmd = pyqtSignal(str) - parent = None - esc_pressed = pyqtSignal() + """The commandline part of the statusbar""" + class CmdValidator(QValidator): + """Validator to prevent the : from getting deleted""" + def validate(self, string, pos): + if string.startswith(':'): + return (QValidator.Acceptable, string, pos) + else: + return (QValidator.Invalid, string, pos) - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - self.setObjectName(self.__class__.__name__) + got_cmd = pyqtSignal(str) # Emitted when a command is triggered by the user + bar = None # The status bar object + esc_pressed = pyqtSignal() # Emitted when escape is pressed + esc = None # The esc QShortcut object + + def __init__(self, bar): + super().__init__(bar) + self.bar = bar self.setStyleSheet("border: 0px; padding-left: 1px") - self.setValidator(CmdValidator()) + self.setValidator(self.CmdValidator()) self.returnPressed.connect(self.process_cmd) self.esc = QShortcut(self) @@ -128,27 +150,25 @@ class StatusCommand(QLineEdit): self.esc.activated.connect(self.esc_pressed) def process_cmd(self): + """Handle the command in the status bar""" text = self.text().lstrip(':') self.setText('') self.got_cmd.emit(text) def set_cmd(self, text): + """Preset the statusbar to some text""" self.setText(text) self.setFocus() def focusOutEvent(self, e): + """Clear the statusbar text if it's explicitely unfocused""" if e.reason() in [Qt.MouseFocusReason, Qt.TabFocusReason, - Qt.BacktabFocusReason, Qt.OtherFocusReason]: + Qt.BacktabFocusReason, Qt.OtherFocusReason]: self.setText('') super().focusOutEvent(e) - def focusInEvent(self, event): - self.parent.clear_error() - super().focusInEvent(event) + def focusInEvent(self, e): + """Clear error message when the statusbar is focused""" + self.bar.clear_error() + super().focusInEvent(e) -class CmdValidator(QValidator): - def validate(self, string, pos): - if string.startswith(':'): - return (QValidator.Acceptable, string, pos) - else: - return (QValidator.Invalid, string, pos) diff --git a/qutebrowser/widgets/tabbar.py b/qutebrowser/widgets/tabbar.py index cbf894920..2a3bccec8 100644 --- a/qutebrowser/widgets/tabbar.py +++ b/qutebrowser/widgets/tabbar.py @@ -2,9 +2,9 @@ from PyQt5.QtWidgets import QTabWidget from PyQt5.QtCore import Qt class TabWidget(QTabWidget): + """The tabwidget used for TabbedBrowser""" def __init__(self, parent): super().__init__(parent) - self.setObjectName(self.__class__.__name__) self.setStyleSheet(""" QTabWidget::pane { position: absolute;