Refactor ALL the things

- Remove super() where unneeded
 - Add docstrings where applicable
 - Remove setObjectName calls
 - Lots and lots of smaller changes
This commit is contained in:
Florian Bruhin 2014-01-20 15:58:49 +01:00
parent dfe7d6c7ef
commit 1095e24f98
8 changed files with 301 additions and 185 deletions

View File

@ -1,16 +1,27 @@
import sys import sys
import argparse
import logging import logging
import signal from signal import signal, SIGINT
from PyQt5.QtWidgets import QWidget, QApplication from argparse import ArgumentParser
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl, QTimer from PyQt5.QtCore import QUrl, QTimer
import qutebrowser.commands.utils as cmdutils
from qutebrowser.widgets.mainwindow import MainWindow from qutebrowser.widgets.mainwindow import MainWindow
from qutebrowser.commands.keys import KeyParser from qutebrowser.commands.keys import KeyParser
from qutebrowser.utils.config import Config from qutebrowser.utils.config import Config
import qutebrowser.commands.utils as cmdutils
from qutebrowser.utils.appdirs import AppDirs from qutebrowser.utils.appdirs import AppDirs
class QuteBrowser(QApplication): 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): def __init__(self):
super().__init__(sys.argv) super().__init__(sys.argv)
@ -24,11 +35,12 @@ class QuteBrowser(QApplication):
self.commandparser = cmdutils.CommandParser() self.commandparser = cmdutils.CommandParser()
self.keyparser = KeyParser(self.mainwindow) 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.mainwindow.tabs.keypress.connect(self.keyparser.handle)
self.keyparser.set_cmd_text.connect(self.mainwindow.status.cmd.set_cmd) 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.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.commandparser.error.connect(self.mainwindow.status.disp_error)
self.keyparser.keystring_updated.connect( self.keyparser.keystring_updated.connect(
self.mainwindow.status.txt.set_keystring) self.mainwindow.status.txt.set_keystring)
@ -39,41 +51,37 @@ class QuteBrowser(QApplication):
self.python_hacks() self.python_hacks()
def python_hacks(self): def python_hacks(self):
qapp = super(QApplication, self) """Gets around some PyQt-oddities by evil hacks"""
## Make python exceptions work
### Make python exceptions work
sys._excepthook = sys.excepthook sys._excepthook = sys.excepthook
def exception_hook(exctype, value, traceback): def exception_hook(exctype, value, traceback):
sys._excepthook(exctype, value, traceback) sys._excepthook(exctype, value, traceback)
# FIXME save open tabs here # FIXME save open tabs here
qapp.exit(1) self.exit(1)
sys.excepthook = exception_hook sys.excepthook = exception_hook
### Quit on SIGINT ## Quit on SIGINT
signal.signal(signal.SIGINT, lambda *args: signal(SIGINT, lambda *args: self.exit(128 + SIGINT))
qapp.exit(128 + signal.SIGINT))
### hack to make Ctrl+C work by passing control to the Python ## hack to make Ctrl+C work by passing control to the Python
### interpreter once all 500ms (lambda to ignore args) ## interpreter once all 500ms (lambda to ignore args)
self.timer = QTimer() self.timer = QTimer()
self.timer.start(500) self.timer.start(500)
self.timer.timeout.connect(lambda: None) self.timer.timeout.connect(lambda: None)
def parseopts(self): 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', parser.add_argument('-l', '--log', dest='loglevel',
help='Set loglevel', default=0) help='Set loglevel', default='info')
self.args = parser.parse_args() self.args = parser.parse_args()
def initlog(self): def initlog(self):
""" Initialisation of the log """ """Initialisation of the log"""
if self.args.loglevel:
loglevel = self.args.loglevel loglevel = self.args.loglevel
else:
loglevel = 'info'
numeric_level = getattr(logging, loglevel.upper(), None) numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int): if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % loglevel) raise ValueError('Invalid log level: {}'.format(loglevel))
logging.basicConfig( logging.basicConfig(
level=numeric_level, level=numeric_level,
format='%(asctime)s [%(levelname)s] ' format='%(asctime)s [%(levelname)s] '
@ -81,13 +89,20 @@ class QuteBrowser(QApplication):
datefmt='%Y-%m-%d %H:%M:%S') datefmt='%Y-%m-%d %H:%M:%S')
def init_cmds(self): def init_cmds(self):
"""Initialisation of the qutebrowser commands"""
cmdutils.register_all() cmdutils.register_all()
cmds = cmdutils.cmd_dict for cmd in cmdutils.cmd_dict.values():
for cmd in cmds.values():
cmd.signal.connect(self.cmd_handler) cmd.signal.connect(self.cmd_handler)
self.keyparser.from_config_sect(self.config['keybind']) self.keyparser.from_config_sect(self.config['keybind'])
def cmd_handler(self, tpl): 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 (count, argv) = tpl
cmd = argv[0] cmd = argv[0]
args = argv[1:] args = argv[1:]
@ -95,41 +110,42 @@ class QuteBrowser(QApplication):
handlers = { handlers = {
'open': self.mainwindow.tabs.openurl, 'open': self.mainwindow.tabs.openurl,
'tabopen': self.mainwindow.tabs.tabopen, 'tabopen': self.mainwindow.tabs.tabopen,
'quit': super().quit, 'quit': self.quit,
'tabclose': self.mainwindow.tabs.close_act, 'tabclose': self.mainwindow.tabs.cur_close,
'tabprev': self.mainwindow.tabs.switch_prev, 'tabprev': self.mainwindow.tabs.switch_prev,
'tabnext': self.mainwindow.tabs.switch_next, 'tabnext': self.mainwindow.tabs.switch_next,
'reload': self.mainwindow.tabs.reload_act, 'reload': self.mainwindow.tabs.cur_reload,
'stop': self.mainwindow.tabs.stop_act, 'stop': self.mainwindow.tabs.cur_stop,
'back': self.mainwindow.tabs.back_act, 'back': self.mainwindow.tabs.cur_back,
'forward': self.mainwindow.tabs.forward_act, 'forward': self.mainwindow.tabs.cur_forward,
'print': self.mainwindow.tabs.print_act, 'print': self.mainwindow.tabs.cur_print,
'scrolldown': self.mainwindow.tabs.scroll_down_act, 'scrolldown': self.mainwindow.tabs.cur_scroll_down,
'scrollup': self.mainwindow.tabs.scroll_up_act, 'scrollup': self.mainwindow.tabs.cur_scroll_up,
'scrollleft': self.mainwindow.tabs.scroll_left_act, 'scrollleft': self.mainwindow.tabs.cur_scroll_left,
'scrollright': self.mainwindow.tabs.scroll_right_act, 'scrollright': self.mainwindow.tabs.cur_scroll_right,
'scrollstart': self.mainwindow.tabs.scroll_start_act, 'scrollstart': self.mainwindow.tabs.cur_scroll_start,
'scrollend': self.mainwindow.tabs.scroll_end_act, 'scrollend': self.mainwindow.tabs.cur_scroll_end,
'undo': self.mainwindow.tabs.undo_close, 'undo': self.mainwindow.tabs.undo_close,
'pyeval': self.pyeval 'pyeval': self.pyeval,
} }
handler = handlers[cmd] handler = handlers[cmd]
sender = self.sender()
if sender.count: if self.sender().count:
handler(*args, count=count) handler(*args, count=count)
else: else:
handler(*args) handler(*args)
def pyeval(self, s): def pyeval(self, s):
"""Evaluates a python string, handler for the pyeval command"""
try: try:
r = eval(s) r = eval(s)
out = repr(r) out = repr(r)
except Exception as e: except Exception as e:
out = ': '.join([e.__class__.__name__, str(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 = self.mainwindow.tabs.currentWidget()
tab.setUrl(QUrl('about:pyeval')) tab.setUrl(QUrl('about:pyeval'))
tab.setContent(out.encode('UTF-8'), 'text/plain') tab.setContent(out.encode('UTF-8'), 'text/plain')

View File

@ -1,6 +1,19 @@
from PyQt5.QtCore import pyqtSignal
from qutebrowser.commands.utils import Command 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): class Open(Command):
nargs = 1 nargs = 1
split_args = False split_args = False

View File

@ -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 logging
import re import re
from PyQt5.QtCore import QObject, pyqtSignal
import qutebrowser.commands.utils as cmdutils
class KeyParser(QObject): 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) set_cmd_text = pyqtSignal(str)
# Signal emitted when the keystring is updated
keystring_updated = pyqtSignal(str) keystring_updated = pyqtSignal(str)
# Keybindings
key_to_cmd = {} key_to_cmd = {}
MATCH_PARTIAL = 0
MATCH_DEFINITIVE = 1
MATCH_NONE = 2
def from_config_sect(self, sect): 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(): 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] self.key_to_cmd[key] = cmdutils.cmd_dict[cmd]
def handle(self, e): def handle(self, e):
"""Wrapper for _handle to emit keystring_updated after _handle"""
self._handle(e) self._handle(e)
self.keystring_updated.emit(self.keystring) self.keystring_updated.emit(self.keystring)
def _handle(self, e): def _handle(self, e):
"""Handle a new keypress.
e -- the KeyPressEvent from Qt
"""
logging.debug('Got key: {} / text: "{}"'.format(e.key(), e.text())) logging.debug('Got key: {} / text: "{}"'.format(e.key(), e.text()))
txt = e.text().strip() txt = e.text().strip()
if not txt: if not txt:
@ -44,14 +61,16 @@ class KeyParser(QObject):
# If a keychain is ambigious, we probably should set up a QTimer with a # If a keychain is ambigious, we probably should set up a QTimer with a
# configurable timeout, which triggers cmd.run() when expiring. Then # configurable timeout, which triggers cmd.run() when expiring. Then
# when we enter _handle() again in time we stop the timer. # when we enter _handle() again in time we stop the timer.
try:
cmd = self.key_to_cmd[cmdstr] (match, cmd) = self._match_key(cmdstr)
except KeyError:
if self._partial_match(cmdstr, txt): if match == self.MATCH_DEFINITIVE:
logging.debug('No match for "{}" (added {})'.format( pass
self.keystring, txt)) elif match == self.MATCH_PARTIAL:
logging.debug('No match for "{}" (added {})'.format(self.keystring,
txt))
return return
else: elif match == self.MATCH_NONE:
logging.debug('Giving up with "{}", no matches'.format( logging.debug('Giving up with "{}", no matches'.format(
self.keystring)) self.keystring))
self.keystring = '' self.keystring = ''
@ -69,13 +88,19 @@ class KeyParser(QObject):
else: else:
cmd.run() cmd.run()
def _partial_match(self, cmdstr, txt): def _match_key(self, cmdstr):
pos = len(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: for cmd in self.key_to_cmd:
try: try:
if cmdstr[-1] == cmd[pos-1]: if cmdstr[-1] == cmd[len(cmdstr) - 1]:
return True return (self.MATCH_PARTIAL, None)
except IndexError: except IndexError:
# current cmd is shorter than our cmdstr, so it won't match
continue continue
else: # no definitive and no partial matches if we arrived here
return False return (self.MATCH_NONE, None)

View File

@ -1,31 +1,37 @@
"""Various command utils and the Command base class"""
import inspect import inspect
import sys
import logging import logging
import shlex import shlex
from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtCore import QObject, pyqtSignal
cmd_dict = {} cmd_dict = {}
def register_all(): class ArgumentCountError(TypeError):
import qutebrowser.commands.commands pass
def is_cmd(obj):
return (inspect.isclass(obj) and
obj.__module__ == 'qutebrowser.commands.commands')
for (name, cls) in inspect.getmembers(qutebrowser.commands.commands, def register_all():
is_cmd): """Register and initialize all commands."""
if cls.bind: # 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() obj = cls()
cmd_dict[obj.name] = obj cmd_dict[obj.name] = obj
class CommandParser(QObject): class CommandParser(QObject):
error = pyqtSignal(str) """Parser for qutebrowser commandline commands"""
error = pyqtSignal(str) # Emitted if there's an error
def parse(self, text): def parse(self, text):
"""Parses a command and runs its handler"""
parts = text.strip().split(maxsplit=1) parts = text.strip().split(maxsplit=1)
# FIXME maybe we should handle unambigious shorthands for commands here? # FIXME maybe we should handle unambigious shorthands for commands
# Or at least we should add :q for :quit. # here? Or at least we should add :q for :quit.
cmd = parts[0] cmd = parts[0]
try: try:
obj = cmd_dict[cmd] obj = cmd_dict[cmd]
@ -42,17 +48,19 @@ class CommandParser(QObject):
try: try:
obj.check(args) obj.check(args)
except TypeError: except ArgumentCountError:
self.error.emit("{}: invalid argument count".format(cmd)) self.error.emit("{}: invalid argument count".format(cmd))
return return
obj.run(args) obj.run(args)
class Command(QObject): class Command(QObject):
"""Base skeleton for a command. See the module help for
qutebrowser.commands.commands for details.
"""
nargs = 0 nargs = 0
name = None name = None
signal = None signal = None
count = False count = False
bind = True
split_args = True split_args = True
signal = pyqtSignal(tuple) signal = pyqtSignal(tuple)
@ -62,16 +70,28 @@ class Command(QObject):
self.name = self.__class__.__name__.lower() self.name = self.__class__.__name__.lower()
def check(self, args): 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 if ((isinstance(self.nargs, int) and len(args) != self.nargs) or
(self.nargs == '?' and len(args) > 1) or (self.nargs == '?' and len(args) > 1) or
(self.nargs == '+' and len(args) < 1)): (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): def run(self, args=None, count=None):
countstr = ' * {}'.format(count) if count is not None else '' """Runs the command.
argstr = ", ".join(args) if args else ''
logging.debug("Cmd called: {}({}){}".format(self.name, argstr, args -- Arguments to the command.
countstr)) 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] argv = [self.name]
if args is not None: if args is not None:
argv += args argv += args

View File

@ -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 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): class TabbedBrowser(TabWidget):
cur_progress = pyqtSignal(int) """A TabWidget with QWebViews inside"""
cur_load_finished = pyqtSignal(bool)
cur_progress = pyqtSignal(int) # Progress of the current tab changed
cur_load_finished = pyqtSignal(bool) # Current tab finished loading
keypress = pyqtSignal('QKeyEvent') keypress = pyqtSignal('QKeyEvent')
url_stack = [] _url_stack = [] # Stack of URLs of closed tabs
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
self.currentChanged.connect(self.index_changed) self.currentChanged.connect(self._currentChanged_handler)
self.tabopen("http://ddg.gg/") self.tabopen("http://ddg.gg/")
def tabopen(self, url): def tabopen(self, url):
"""Opens a new tab with a given url"""
tab = BrowserTab(self) tab = BrowserTab(self)
tab.openurl(url) tab.openurl(url)
self.addTab(tab, url) self.addTab(tab, url)
self.setCurrentWidget(tab) self.setCurrentWidget(tab)
self.progress_changed(tab.progress) self.cur_progress.emit(tab.progress)
tab.loadProgress.connect(self.progress_changed) tab.loadProgress.connect(
tab.loadFinished.connect(self.load_finished) 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 # FIXME should we really bind this to loadStarted? Sometimes the URL
# isn't set correctly at this point, e.g. when doing # isn't set correctly at this point, e.g. when doing
# setContent(..., baseUrl=QUrl('foo')) # setContent(..., baseUrl=QUrl('foo'))
tab.loadStarted.connect(self.init_title) tab.loadStarted.connect(self._loadStarted_handler)
tab.titleChanged.connect(self.update_title) tab.titleChanged.connect(self._titleChanged_handler)
def openurl(self, url): def openurl(self, url):
"""Opens an url in the current tab"""
tab = self.currentWidget() tab = self.currentWidget()
tab.openurl(url) tab.openurl(url)
def undo_close(self): def undo_close(self):
if self.url_stack: """Undos closing a tab"""
self.tabopen(self.url_stack.pop()) 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: if self.count() > 1:
idx = self.currentIndex() idx = self.currentIndex()
tab = self.currentWidget() tab = self.currentWidget()
# FIXME maybe we should add the QUrl object here and deal with QUrls everywhere # FIXME maybe we should add the QUrl object here and deal with QUrls everywhere
# FIXME maybe we actually should store the webview objects here # 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) self.removeTab(idx)
else: else:
# FIXME # FIXME
pass pass
def reload_act(self): def cur_reload(self):
"""Reloads the current tab"""
self.currentWidget().reload() self.currentWidget().reload()
def stop_act(self): def cur_stop(self):
"""Stops loading in the current tab"""
self.currentWidget().stop() self.currentWidget().stop()
def print_act(self): def cur_print(self):
"""Prints the current tab"""
# FIXME that does not what I expect # FIXME that does not what I expect
preview = QPrintPreviewDialog() preview = QPrintPreviewDialog()
preview.paintRequested.connect(self.currentWidget().print) preview.paintRequested.connect(self.currentWidget().print)
preview.exec_() 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 # FIXME display warning if beginning of history
self.currentWidget().back() 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 # FIXME display warning if end of history
self.currentWidget().forward() 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: if count is None:
count = 50 count = 50
self.currentWidget().page().mainFrame().scroll(0, count) 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: if count is None:
count = 50 count = 50
self.currentWidget().page().mainFrame().scroll(0, -count) 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: if count is None:
count = 50 count = 50
self.currentWidget().page().mainFrame().scroll(-count, 0) 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: if count is None:
count = 50 count = 50
self.currentWidget().page().mainFrame().scroll(count, 0) 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() frame = self.currentWidget().page().mainFrame()
cur_pos = frame.scrollPosition() cur_pos = frame.scrollPosition()
frame.setScrollPosition(QPoint(cur_pos.x(), 0)) 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() frame = self.currentWidget().page().mainFrame()
cur_pos = frame.scrollPosition() cur_pos = frame.scrollPosition()
size = frame.contentsSize() size = frame.contentsSize()
frame.setScrollPosition(QPoint(cur_pos.x(), size.height())) frame.setScrollPosition(QPoint(cur_pos.x(), size.height()))
def switch_prev(self): def switch_prev(self):
"""Switches to the previous tab"""
idx = self.currentIndex() idx = self.currentIndex()
if idx > 0: if idx > 0:
self.setCurrentIndex(idx - 1) self.setCurrentIndex(idx - 1)
@ -109,6 +131,7 @@ class TabbedBrowser(TabWidget):
pass pass
def switch_next(self): def switch_next(self):
"""Switches to the next tab"""
idx = self.currentIndex() idx = self.currentIndex()
if idx < self.count() - 1: if idx < self.count() - 1:
self.setCurrentIndex(idx + 1) self.setCurrentIndex(idx + 1)
@ -116,21 +139,22 @@ class TabbedBrowser(TabWidget):
# FIXME # FIXME
pass pass
def progress_changed(self, prog): def keyPressEvent(self, e):
self.filter_signals(self.cur_progress, prog) self.keypress.emit(e)
super().keyPressEvent(e)
def load_finished(self, ok): def _titleChanged_handler(self, text):
self.filter_signals(self.cur_load_finished, ok)
def update_title(self, text):
if text: if text:
self.setTabText(self.indexOf(self.sender()), text) self.setTabText(self.indexOf(self.sender()), text)
def init_title(self): def _loadStarted_handler(self):
s = self.sender() s = self.sender()
self.setTabText(self.indexOf(s), s.url().toString()) 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( dbgstr = "{} ({})".format(
signal.signal, ','.join([str(e) for e in args])) signal.signal, ','.join([str(e) for e in args]))
if self.currentWidget() == self.sender(): if self.currentWidget() == self.sender():
@ -139,16 +163,12 @@ class TabbedBrowser(TabWidget):
else: else:
logging.debug('{} - ignoring'.format(dbgstr)) logging.debug('{} - ignoring'.format(dbgstr))
def index_changed(self, idx): def _currentChanged_handler(self, idx):
tab = self.widget(idx) tab = self.widget(idx)
self.cur_progress.emit(tab.progress) self.cur_progress.emit(tab.progress)
def keyPressEvent(self, e):
self.keypress.emit(e)
super().keyPressEvent(e)
class BrowserTab(QWebView): class BrowserTab(QWebView):
parent = None """One browser tab in TabbedBrowser"""
progress = 0 progress = 0
def __init__(self, parent): def __init__(self, parent):
@ -160,9 +180,10 @@ class BrowserTab(QWebView):
self.show() self.show()
def openurl(self, url): def openurl(self, url):
"""Opens an URL in the browser"""
if not url.startswith('http://'): if not url.startswith('http://'):
url = 'http://' + url url = 'http://' + url
super().load(QUrl(url)) self.load(QUrl(url))
def set_progress(self, prog): def set_progress(self, prog):
self.progress = prog self.progress = prog

View File

@ -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.statusbar import StatusBar
from qutebrowser.widgets.browser import TabbedBrowser from qutebrowser.widgets.browser import TabbedBrowser
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
"""The main window of QuteBrowser"""
cwidget = None
vbox = None
tabs = None
status = None
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setObjectName(self.__class__.__name__)
self.cwidget = QWidget(self) self.cwidget = QWidget(self)
self.cwidget.setObjectName("cwidget")
self.setCentralWidget(self.cwidget) self.setCentralWidget(self.cwidget)
self.vbox = QVBoxLayout(self.cwidget) self.vbox = QVBoxLayout(self.cwidget)
self.vbox.setObjectName("vbox")
self.vbox.setContentsMargins(0, 0, 0, 0) self.vbox.setContentsMargins(0, 0, 0, 0)
self.vbox.setSpacing(0) self.vbox.setSpacing(0)
self.tabs = TabbedBrowser(self) self.tabs = TabbedBrowser(self)
self.tabs.setObjectName("tabs")
self.vbox.addWidget(self.tabs) self.vbox.addWidget(self.tabs)
self.status = StatusBar(self) self.status = StatusBar(self)

View File

@ -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 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): class StatusBar(QWidget):
has_error = False """The statusbar at the bottom of the mainwindow"""
parent = None 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 # TODO: the statusbar should be a bit smaller
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
self.parent = parent
self.setObjectName(self.__class__.__name__)
self.set_color("white", "black") self.set_color("white", "black")
self.hbox = QHBoxLayout(self) self.hbox = QHBoxLayout(self)
self.hbox.setObjectName("status_hbox")
self.hbox.setContentsMargins(0, 0, 0, 0) self.hbox.setContentsMargins(0, 0, 0, 0)
self.hbox.setSpacing(0) self.hbox.setSpacing(0)
@ -28,6 +31,9 @@ class StatusBar(QWidget):
self.hbox.addWidget(self.prog) self.hbox.addWidget(self.prog)
def set_color(self, fg, bg): def set_color(self, fg, bg):
"""Sets background and foreground color of the statusbar"""
# FIXME maybe this would be easier with setColor()?
self.setStyleSheet(""" self.setStyleSheet("""
* { * {
background: """ + bg + """; background: """ + bg + """;
@ -36,23 +42,25 @@ class StatusBar(QWidget):
}""") }""")
def disp_error(self, text): def disp_error(self, text):
"""Displays an error in the statusbar"""
self.has_error = True self.has_error = True
self.set_color('white', 'red') self.set_color('white', 'red')
self.txt.error = text self.txt.error = text
def clear_error(self): def clear_error(self):
"""Clears a displayed error from the status bar"""
if self.has_error: if self.has_error:
self.has_error = False self.has_error = False
self.set_color('white', 'black') self.set_color('white', 'black')
self.txt.error = '' self.txt.error = ''
class StatusProgress(QProgressBar): class StatusProgress(QProgressBar):
parent = None """ The progress bar part of the status bar"""
bar = None
def __init__(self, parent): def __init__(self, bar):
self.parent = parent self.bar = bar
super().__init__(parent) super().__init__(bar)
self.setObjectName(self.__class__.__name__)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setTextVisible(False) self.setTextVisible(False)
@ -70,13 +78,15 @@ class StatusProgress(QProgressBar):
self.hide() self.hide()
def minimumSizeHint(self): def minimumSizeHint(self):
status_size = self.parent.size() status_size = self.bar.size()
return QSize(100, status_size.height()) return QSize(100, status_size.height())
def sizeHint(self): def sizeHint(self):
return self.minimumSizeHint() return self.minimumSizeHint()
def set_progress(self, prog): 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: if prog == 100:
self.setValue(prog) self.setValue(prog)
self.hide() self.hide()
@ -88,14 +98,14 @@ class StatusProgress(QProgressBar):
self.hide() self.hide()
class StatusText(QLabel): class StatusText(QLabel):
"""The text part of the status bar, composedof several 'widgets'"""
keystring = '' keystring = ''
error = '' error = ''
text = '' text = ''
scrollperc = '' scrollperc = ''
def __init__(self, parent): def __init__(self, bar):
super().__init__(parent) super().__init__(bar)
self.setObjectName(self.__class__.__name__)
self.setStyleSheet("padding-right: 1px") self.setStyleSheet("padding-right: 1px")
def __setattr__(self, name, value): def __setattr__(self, name, value):
@ -103,23 +113,35 @@ class StatusText(QLabel):
self.update() self.update()
def set_keystring(self, s): def set_keystring(self, s):
"""Setter to be used as a Qt slot"""
self.keystring = s self.keystring = s
def update(self): def update(self):
super().setText(' '.join([self.keystring, self.error, self.text, """Update the text displayed"""
self.setText(' '.join([self.keystring, self.error, self.text,
self.scrollperc])) self.scrollperc]))
class StatusCommand(QLineEdit):
got_cmd = pyqtSignal(str)
parent = None
esc_pressed = pyqtSignal()
def __init__(self, parent): class StatusCommand(QLineEdit):
super().__init__(parent) """The commandline part of the statusbar"""
self.parent = parent class CmdValidator(QValidator):
self.setObjectName(self.__class__.__name__) """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)
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.setStyleSheet("border: 0px; padding-left: 1px")
self.setValidator(CmdValidator()) self.setValidator(self.CmdValidator())
self.returnPressed.connect(self.process_cmd) self.returnPressed.connect(self.process_cmd)
self.esc = QShortcut(self) self.esc = QShortcut(self)
@ -128,27 +150,25 @@ class StatusCommand(QLineEdit):
self.esc.activated.connect(self.esc_pressed) self.esc.activated.connect(self.esc_pressed)
def process_cmd(self): def process_cmd(self):
"""Handle the command in the status bar"""
text = self.text().lstrip(':') text = self.text().lstrip(':')
self.setText('') self.setText('')
self.got_cmd.emit(text) self.got_cmd.emit(text)
def set_cmd(self, text): def set_cmd(self, text):
"""Preset the statusbar to some text"""
self.setText(text) self.setText(text)
self.setFocus() self.setFocus()
def focusOutEvent(self, e): def focusOutEvent(self, e):
"""Clear the statusbar text if it's explicitely unfocused"""
if e.reason() in [Qt.MouseFocusReason, Qt.TabFocusReason, if e.reason() in [Qt.MouseFocusReason, Qt.TabFocusReason,
Qt.BacktabFocusReason, Qt.OtherFocusReason]: Qt.BacktabFocusReason, Qt.OtherFocusReason]:
self.setText('') self.setText('')
super().focusOutEvent(e) super().focusOutEvent(e)
def focusInEvent(self, event): def focusInEvent(self, e):
self.parent.clear_error() """Clear error message when the statusbar is focused"""
super().focusInEvent(event) 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)

View File

@ -2,9 +2,9 @@ from PyQt5.QtWidgets import QTabWidget
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
class TabWidget(QTabWidget): class TabWidget(QTabWidget):
"""The tabwidget used for TabbedBrowser"""
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
self.setObjectName(self.__class__.__name__)
self.setStyleSheet(""" self.setStyleSheet("""
QTabWidget::pane { QTabWidget::pane {
position: absolute; position: absolute;