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:
parent
dfe7d6c7ef
commit
1095e24f98
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user