Docstringify ALL the things
This commit is contained in:
parent
46660b11ef
commit
e56099e0ec
@ -1 +1,14 @@
|
|||||||
"""A vim like browser based on Qt"""
|
"""A vim like browser based on Qt.
|
||||||
|
|
||||||
|
Files:
|
||||||
|
__init__.py - This file.
|
||||||
|
__main__.py - Entry point for qutebrowser, to use\
|
||||||
|
'python -m qutebrowser'.
|
||||||
|
app.py - Main qutebrowser application>
|
||||||
|
simplebrowser.py - Simple browser for testing purposes.
|
||||||
|
|
||||||
|
Subpackages:
|
||||||
|
commands - Handling of commands and key parsing.
|
||||||
|
utils - Misc utility code.
|
||||||
|
widgets - Qt widgets displayed on the screen.
|
||||||
|
"""
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""Entry point for qutebrowser. Simply execute qutebrowser."""
|
||||||
|
|
||||||
from qutebrowser.app import QuteBrowser
|
from qutebrowser.app import QuteBrowser
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
""" Initialization of qutebrowser and application-wide things """
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import faulthandler
|
import faulthandler
|
||||||
@ -15,7 +17,13 @@ from qutebrowser.utils.appdirs import AppDirs
|
|||||||
|
|
||||||
|
|
||||||
class QuteBrowser(QApplication):
|
class QuteBrowser(QApplication):
|
||||||
"""Main object for QuteBrowser"""
|
"""Main object for qutebrowser.
|
||||||
|
|
||||||
|
Can be used like this:
|
||||||
|
|
||||||
|
>>> app = QuteBrowser()
|
||||||
|
>>> sys.exit(app.exec_())
|
||||||
|
"""
|
||||||
dirs = None # AppDirs - config/cache directories
|
dirs = None # AppDirs - config/cache directories
|
||||||
config = None # Config(Parser) object
|
config = None # Config(Parser) object
|
||||||
mainwindow = None
|
mainwindow = None
|
||||||
@ -27,13 +35,13 @@ class QuteBrowser(QApplication):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(sys.argv)
|
super().__init__(sys.argv)
|
||||||
# Exit on exceptions
|
# Exit on exceptions
|
||||||
sys.excepthook = self.tmp_exception_hook
|
sys.excepthook = self._tmp_exception_hook
|
||||||
|
|
||||||
# Handle segfaults
|
# Handle segfaults
|
||||||
faulthandler.enable()
|
faulthandler.enable()
|
||||||
|
|
||||||
self.parseopts()
|
self._parseopts()
|
||||||
self.initlog()
|
self._initlog()
|
||||||
|
|
||||||
self.dirs = AppDirs('qutebrowser')
|
self.dirs = AppDirs('qutebrowser')
|
||||||
if self.args.confdir is None:
|
if self.args.confdir is None:
|
||||||
@ -46,7 +54,7 @@ class QuteBrowser(QApplication):
|
|||||||
|
|
||||||
self.commandparser = cmdutils.CommandParser()
|
self.commandparser = cmdutils.CommandParser()
|
||||||
self.keyparser = KeyParser(self.mainwindow)
|
self.keyparser = KeyParser(self.mainwindow)
|
||||||
self.init_cmds()
|
self._init_cmds()
|
||||||
self.mainwindow = MainWindow()
|
self.mainwindow = MainWindow()
|
||||||
|
|
||||||
self.aboutToQuit.connect(config.config.save)
|
self.aboutToQuit.connect(config.config.save)
|
||||||
@ -62,15 +70,23 @@ class QuteBrowser(QApplication):
|
|||||||
self.mainwindow.status.txt.set_keystring)
|
self.mainwindow.status.txt.set_keystring)
|
||||||
|
|
||||||
self.mainwindow.show()
|
self.mainwindow.show()
|
||||||
self.python_hacks()
|
self._python_hacks()
|
||||||
|
|
||||||
def tmp_exception_hook(self, exctype, value, traceback):
|
def _tmp_exception_hook(self, exctype, value, traceback):
|
||||||
"""Exception hook while initializing, simply exit"""
|
"""Handle exceptions while initializing by simply exiting.
|
||||||
|
|
||||||
|
This is only temporary and will get replaced by exception_hook later.
|
||||||
|
It's necessary because PyQt seems to ignore exceptions by default.
|
||||||
|
"""
|
||||||
sys.__excepthook__(exctype, value, traceback)
|
sys.__excepthook__(exctype, value, traceback)
|
||||||
self.exit(1)
|
self.exit(1)
|
||||||
|
|
||||||
def exception_hook(self, exctype, value, traceback):
|
def _exception_hook(self, exctype, value, traceback):
|
||||||
"""Try very hard to write open tabs to a file and exit gracefully"""
|
"""Handle uncaught python exceptions.
|
||||||
|
|
||||||
|
It'll try very hard to write all open tabs to a file, and then exit
|
||||||
|
gracefully.
|
||||||
|
"""
|
||||||
# pylint: disable=broad-except
|
# pylint: disable=broad-except
|
||||||
sys.__excepthook__(exctype, value, traceback)
|
sys.__excepthook__(exctype, value, traceback)
|
||||||
try:
|
try:
|
||||||
@ -84,22 +100,21 @@ class QuteBrowser(QApplication):
|
|||||||
pass
|
pass
|
||||||
self.exit(1)
|
self.exit(1)
|
||||||
|
|
||||||
def python_hacks(self):
|
def _python_hacks(self):
|
||||||
"""Gets around some PyQt-oddities by evil hacks"""
|
"""Get around some PyQt-oddities by evil hacks.
|
||||||
## Make python exceptions work
|
|
||||||
sys.excepthook = self.exception_hook
|
|
||||||
|
|
||||||
## Quit on SIGINT
|
This sets up the uncaught exception hook, quits with an appropriate
|
||||||
|
exit status, and handles Ctrl+C properly by passing control to the
|
||||||
|
Python interpreter once all 500ms.
|
||||||
|
"""
|
||||||
|
sys.excepthook = self._exception_hook
|
||||||
signal(SIGINT, lambda *args: self.exit(128 + 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)
|
|
||||||
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):
|
||||||
"""Parse command line options"""
|
"""Parse command line options."""
|
||||||
parser = ArgumentParser("usage: %(prog)s [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='info')
|
help='Set loglevel', default='info')
|
||||||
@ -107,8 +122,8 @@ class QuteBrowser(QApplication):
|
|||||||
'(empty for no config storage)')
|
'(empty for no config storage)')
|
||||||
self.args = parser.parse_args()
|
self.args = parser.parse_args()
|
||||||
|
|
||||||
def initlog(self):
|
def _initlog(self):
|
||||||
"""Initialisation of the log"""
|
"""Initialisation of the logging output."""
|
||||||
loglevel = self.args.loglevel
|
loglevel = self.args.loglevel
|
||||||
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):
|
||||||
@ -119,8 +134,11 @@ class QuteBrowser(QApplication):
|
|||||||
'[%(module)s:%(funcName)s:%(lineno)s] %(message)s',
|
'[%(module)s:%(funcName)s:%(lineno)s] %(message)s',
|
||||||
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"""
|
"""Initialisation of the qutebrowser commands.
|
||||||
|
|
||||||
|
Registers all commands, connects its signals, and sets up keyparser.
|
||||||
|
"""
|
||||||
cmdutils.register_all()
|
cmdutils.register_all()
|
||||||
for cmd in cmdutils.cmd_dict.values():
|
for cmd in cmdutils.cmd_dict.values():
|
||||||
cmd.signal.connect(self.cmd_handler)
|
cmd.signal.connect(self.cmd_handler)
|
||||||
@ -130,8 +148,10 @@ class QuteBrowser(QApplication):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def cmd_handler(self, tpl):
|
def cmd_handler(self, tpl):
|
||||||
"""Handler which gets called from all commands and delegates the
|
"""Handle commands and delegate the specific actions.
|
||||||
specific actions.
|
|
||||||
|
This gets called as a slot from all commands, and then calls the
|
||||||
|
appropriate command handler.
|
||||||
|
|
||||||
tpl -- A tuple in the form (count, argv) where argv is [cmd, arg, ...]
|
tpl -- A tuple in the form (count, argv) where argv is [cmd, arg, ...]
|
||||||
|
|
||||||
@ -163,12 +183,17 @@ class QuteBrowser(QApplication):
|
|||||||
handler = handlers[cmd]
|
handler = handlers[cmd]
|
||||||
|
|
||||||
if self.sender().count:
|
if self.sender().count:
|
||||||
handler(*args, count=count)
|
return handler(*args, count=count)
|
||||||
else:
|
else:
|
||||||
handler(*args)
|
return handler(*args)
|
||||||
|
|
||||||
def pyeval(self, s):
|
def pyeval(self, s):
|
||||||
"""Evaluates a python string, handler for the pyeval command"""
|
"""Evaluate a python string and display the results as a webpage.
|
||||||
|
|
||||||
|
s -- The string to evaluate.
|
||||||
|
|
||||||
|
:pyeval command handler.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
r = eval(s)
|
r = eval(s)
|
||||||
out = repr(r)
|
out = repr(r)
|
||||||
|
@ -5,8 +5,10 @@ Defined here to avoid circular dependency hell.
|
|||||||
|
|
||||||
|
|
||||||
class NoSuchCommandError(ValueError):
|
class NoSuchCommandError(ValueError):
|
||||||
|
"""Raised when a command wasn't found."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ArgumentCountError(TypeError):
|
class ArgumentCountError(TypeError):
|
||||||
|
"""Raised when a command was called with an invalid count of arguments."""
|
||||||
pass
|
pass
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
"""Parse keypresses/keychains in the main window."""
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -8,7 +11,7 @@ from qutebrowser.commands.utils import (CommandParser, ArgumentCountError,
|
|||||||
|
|
||||||
|
|
||||||
class KeyParser(QObject):
|
class KeyParser(QObject):
|
||||||
"""Parser for vim-like key sequences"""
|
"""Parser for vim-like key sequences."""
|
||||||
keystring = '' # The currently entered key sequence
|
keystring = '' # The currently entered key sequence
|
||||||
# Signal emitted when the statusbar should set a partial command
|
# Signal emitted when the statusbar should set a partial command
|
||||||
set_cmd_text = pyqtSignal(str)
|
set_cmd_text = pyqtSignal(str)
|
||||||
@ -27,8 +30,9 @@ class KeyParser(QObject):
|
|||||||
self.commandparser = CommandParser()
|
self.commandparser = CommandParser()
|
||||||
|
|
||||||
def from_config_sect(self, sect):
|
def from_config_sect(self, sect):
|
||||||
"""Loads keybindings from a ConfigParser section, in the config format
|
"""Load keybindings from a ConfigParser section.
|
||||||
key = command, e.g.
|
|
||||||
|
Config format: key = command, e.g.:
|
||||||
gg = scrollstart
|
gg = scrollstart
|
||||||
"""
|
"""
|
||||||
for (key, cmd) in sect.items():
|
for (key, cmd) in sect.items():
|
||||||
@ -36,13 +40,17 @@ class KeyParser(QObject):
|
|||||||
self.bindings[key] = cmd
|
self.bindings[key] = cmd
|
||||||
|
|
||||||
def handle(self, e):
|
def handle(self, e):
|
||||||
"""Wrapper for _handle to emit keystring_updated after _handle"""
|
"""Wrap _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.
|
"""Handle a new keypress.
|
||||||
|
|
||||||
|
Separates the keypress into count/command, then checks if it matches
|
||||||
|
any possible command, and either runs the command, ignores it, or
|
||||||
|
displays an error.
|
||||||
|
|
||||||
e -- the KeyPressEvent from Qt
|
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()))
|
||||||
@ -98,7 +106,10 @@ class KeyParser(QObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _match_key(self, cmdstr_needle):
|
def _match_key(self, cmdstr_needle):
|
||||||
"""Tries to match a given cmdstr with any defined command"""
|
"""Try to match a given keystring with any bound keychain.
|
||||||
|
|
||||||
|
cmdstr_needle: The command string to find.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
cmdstr_hay = self.bindings[cmdstr_needle]
|
cmdstr_hay = self.bindings[cmdstr_needle]
|
||||||
return (self.MATCH_DEFINITIVE, cmdstr_hay)
|
return (self.MATCH_DEFINITIVE, cmdstr_hay)
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
"""Contains the Command class, a skeleton for a command."""
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtSignal
|
||||||
@ -6,8 +9,9 @@ from qutebrowser.commands.exceptions import ArgumentCountError
|
|||||||
|
|
||||||
|
|
||||||
class Command(QObject):
|
class Command(QObject):
|
||||||
"""Base skeleton for a command. See the module help for
|
"""Base skeleton for a command.
|
||||||
qutebrowser.commands.commands for details.
|
|
||||||
|
See the module documentation for qutebrowser.commands.commands for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# FIXME:
|
# FIXME:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Various command utils and the Command base class"""
|
"""Contains various command utils, and the CommandParser."""
|
||||||
|
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import shlex
|
import shlex
|
||||||
@ -10,6 +11,7 @@ from qutebrowser.commands.exceptions import (ArgumentCountError,
|
|||||||
NoSuchCommandError)
|
NoSuchCommandError)
|
||||||
from qutebrowser.utils.completion import CompletionModel
|
from qutebrowser.utils.completion import CompletionModel
|
||||||
|
|
||||||
|
# A mapping from command-strings to command objects.
|
||||||
cmd_dict = {}
|
cmd_dict = {}
|
||||||
|
|
||||||
|
|
||||||
@ -28,13 +30,17 @@ def register_all():
|
|||||||
|
|
||||||
|
|
||||||
class CommandParser(QObject):
|
class CommandParser(QObject):
|
||||||
"""Parser for qutebrowser commandline commands"""
|
"""Parse qutebrowser commandline commands."""
|
||||||
text = ''
|
text = ''
|
||||||
cmd = ''
|
cmd = ''
|
||||||
args = []
|
args = []
|
||||||
error = pyqtSignal(str) # Emitted if there's an error
|
error = pyqtSignal(str) # Emitted if there's an error
|
||||||
|
|
||||||
def _parse(self, text):
|
def _parse(self, text):
|
||||||
|
"""Split the commandline text into command and arguments.
|
||||||
|
|
||||||
|
Raise NoSuchCommandError if a command wasn't found.
|
||||||
|
"""
|
||||||
self.text = text
|
self.text = text
|
||||||
parts = self.text.strip().split(maxsplit=1)
|
parts = self.text.strip().split(maxsplit=1)
|
||||||
cmdstr = parts[0]
|
cmdstr = parts[0]
|
||||||
@ -53,19 +59,22 @@ class CommandParser(QObject):
|
|||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
def _check(self):
|
def _check(self):
|
||||||
|
"""Check if the argument count for the command is correct."""
|
||||||
self.cmd.check(self.args)
|
self.cmd.check(self.args)
|
||||||
|
|
||||||
def _run(self, count=None):
|
def _run(self, count=None):
|
||||||
|
"""Run a command with an optional count."""
|
||||||
if count is not None:
|
if count is not None:
|
||||||
self.cmd.run(self.args, count=count)
|
self.cmd.run(self.args, count=count)
|
||||||
else:
|
else:
|
||||||
self.cmd.run(self.args)
|
self.cmd.run(self.args)
|
||||||
|
|
||||||
def run(self, text, count=None, ignore_exc=True):
|
def run(self, text, count=None, ignore_exc=True):
|
||||||
"""Parses a command from a line of text.
|
"""Parse a command from a line of text.
|
||||||
If ignore_exc is True, ignores exceptions and returns True/False
|
|
||||||
instead.
|
If ignore_exc is True, ignore exceptions and return True/False.
|
||||||
Raises NoSuchCommandError if a command wasn't found, and
|
|
||||||
|
Raise NoSuchCommandError if a command wasn't found, and
|
||||||
ArgumentCountError if a command was called with the wrong count of
|
ArgumentCountError if a command was called with the wrong count of
|
||||||
arguments.
|
arguments.
|
||||||
"""
|
"""
|
||||||
@ -89,7 +98,11 @@ class CommandParser(QObject):
|
|||||||
|
|
||||||
|
|
||||||
class CommandCompletionModel(CompletionModel):
|
class CommandCompletionModel(CompletionModel):
|
||||||
|
|
||||||
|
"""A CompletionModel filled with all commands and descriptions."""
|
||||||
|
|
||||||
# pylint: disable=abstract-method
|
# pylint: disable=abstract-method
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
assert cmd_dict
|
assert cmd_dict
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Simple browser for testing purposes"""
|
"""Very simple browser for testing purposes."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
"""The base data models for completion in the commandline.
|
||||||
|
|
||||||
|
Contains:
|
||||||
|
CompletionModel -- A simple tree model based on Python data.
|
||||||
|
CompletionItem -- One item in the CompletionModel.
|
||||||
|
CompletionFilterModel -- A QSortFilterProxyModel subclass for completions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from PyQt5.QtCore import (QAbstractItemModel, Qt, QModelIndex, QVariant,
|
from PyQt5.QtCore import (QAbstractItemModel, Qt, QModelIndex, QVariant,
|
||||||
@ -5,6 +14,12 @@ from PyQt5.QtCore import (QAbstractItemModel, Qt, QModelIndex, QVariant,
|
|||||||
|
|
||||||
|
|
||||||
class CompletionModel(QAbstractItemModel):
|
class CompletionModel(QAbstractItemModel):
|
||||||
|
|
||||||
|
"""A simple tree model based on Python OrderdDict containing tuples.
|
||||||
|
|
||||||
|
Used for showing completions later in the CompletionView.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._data = OrderedDict()
|
self._data = OrderedDict()
|
||||||
@ -12,22 +27,40 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
self.root = CompletionItem([""] * 2)
|
self.root = CompletionItem([""] * 2)
|
||||||
|
|
||||||
def removeRows(self, position=0, count=1, parent=QModelIndex()):
|
def removeRows(self, position=0, count=1, parent=QModelIndex()):
|
||||||
node = self.node(parent)
|
"""Remove rows from the model.
|
||||||
|
|
||||||
|
Overrides QAbstractItemModel::removeRows.
|
||||||
|
"""
|
||||||
|
node = self._node(parent)
|
||||||
self.beginRemoveRows(parent, position, position + count - 1)
|
self.beginRemoveRows(parent, position, position + count - 1)
|
||||||
node.children.pop(position)
|
node.children.pop(position)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
|
|
||||||
def node(self, index):
|
def _node(self, index):
|
||||||
|
"""Return the interal data representation for index.
|
||||||
|
|
||||||
|
Returns the CompletionItem for index, or the root CompletionItem if the
|
||||||
|
index was invalid.
|
||||||
|
"""
|
||||||
if index.isValid():
|
if index.isValid():
|
||||||
return index.internalPointer()
|
return index.internalPointer()
|
||||||
else:
|
else:
|
||||||
return self.root
|
return self.root
|
||||||
|
|
||||||
def columnCount(self, parent=QModelIndex()):
|
def columnCount(self, parent=QModelIndex()):
|
||||||
|
"""Return the column count in the model.
|
||||||
|
|
||||||
|
Overrides QAbstractItemModel::columnCount.
|
||||||
|
"""
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
return self.root.column_count()
|
return self.root.column_count()
|
||||||
|
|
||||||
def data(self, index, role=Qt.DisplayRole):
|
def data(self, index, role=Qt.DisplayRole):
|
||||||
|
"""Return the data for role/index as QVariant.
|
||||||
|
|
||||||
|
Returns an invalid QVariant on error.
|
||||||
|
Overrides QAbstractItemModel::data.
|
||||||
|
"""
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return QVariant()
|
return QVariant()
|
||||||
item = index.internalPointer()
|
item = index.internalPointer()
|
||||||
@ -37,6 +70,11 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
|
"""Return the item flags for index.
|
||||||
|
|
||||||
|
Returns Qt.NoItemFlags on error.
|
||||||
|
Overrides QAbstractItemModel::flags.
|
||||||
|
"""
|
||||||
# FIXME categories are not selectable, but moving via arrow keys still
|
# FIXME categories are not selectable, but moving via arrow keys still
|
||||||
# tries to select them
|
# tries to select them
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
@ -48,11 +86,21 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
return flags | Qt.ItemIsSelectable
|
return flags | Qt.ItemIsSelectable
|
||||||
|
|
||||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||||
|
"""Return the header data for role/index as QVariant.
|
||||||
|
|
||||||
|
Returns an invalid QVariant on error.
|
||||||
|
Overrides QAbstractItemModel::headerData.
|
||||||
|
"""
|
||||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||||
return QVariant(self.root.data(section))
|
return QVariant(self.root.data(section))
|
||||||
return None
|
return QVariant()
|
||||||
|
|
||||||
def setData(self, index, value, role=Qt.EditRole):
|
def setData(self, index, value, role=Qt.EditRole):
|
||||||
|
"""Set the data for role/index to value.
|
||||||
|
|
||||||
|
Returns True on success, False on failure.
|
||||||
|
Overrides QAbstractItemModel::setData.
|
||||||
|
"""
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return False
|
return False
|
||||||
item = index.internalPointer()
|
item = index.internalPointer()
|
||||||
@ -64,6 +112,11 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def index(self, row, column, parent=QModelIndex()):
|
def index(self, row, column, parent=QModelIndex()):
|
||||||
|
"""Return the QModelIndex for row/column/parent.
|
||||||
|
|
||||||
|
Returns an invalid QModelIndex on failure.
|
||||||
|
Overrides QAbstractItemModel::index.
|
||||||
|
"""
|
||||||
if (0 <= row < self.rowCount(parent) and
|
if (0 <= row < self.rowCount(parent) and
|
||||||
0 <= column < self.columnCount(parent)):
|
0 <= column < self.columnCount(parent)):
|
||||||
pass
|
pass
|
||||||
@ -82,6 +135,11 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
return QModelIndex()
|
return QModelIndex()
|
||||||
|
|
||||||
def parent(self, index):
|
def parent(self, index):
|
||||||
|
"""Return the QModelIndex of the parent of the object behind index.
|
||||||
|
|
||||||
|
Returns an invalid QModelIndex on failure.
|
||||||
|
Overrides QAbstractItemModel::parent.
|
||||||
|
"""
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return QModelIndex()
|
return QModelIndex()
|
||||||
item = index.internalPointer().parent
|
item = index.internalPointer().parent
|
||||||
@ -90,6 +148,11 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
return self.createIndex(item.row(), 0, item)
|
return self.createIndex(item.row(), 0, item)
|
||||||
|
|
||||||
def rowCount(self, parent=QModelIndex()):
|
def rowCount(self, parent=QModelIndex()):
|
||||||
|
"""Return the rowCount (children count) for a parent.
|
||||||
|
|
||||||
|
Uses the root frame if parent is invalid.
|
||||||
|
Overrides QAbstractItemModel::data.
|
||||||
|
"""
|
||||||
if parent.column() > 0:
|
if parent.column() > 0:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -101,9 +164,15 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
return len(pitem.children)
|
return len(pitem.children)
|
||||||
|
|
||||||
def sort(self, column, order=Qt.AscendingOrder):
|
def sort(self, column, order=Qt.AscendingOrder):
|
||||||
|
"""Sort the data in column according to order.
|
||||||
|
|
||||||
|
Raises NotImplementedError, should be overwritten in a superclass.
|
||||||
|
Overrides QAbstractItemModel::sort.
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def init_data(self):
|
def init_data(self):
|
||||||
|
"""Initialize the Qt model based on the data in self._data."""
|
||||||
for (cat, items) in self._data.items():
|
for (cat, items) in self._data.items():
|
||||||
newcat = CompletionItem([cat], self.root)
|
newcat = CompletionItem([cat], self.root)
|
||||||
self.root.children.append(newcat)
|
self.root.children.append(newcat)
|
||||||
@ -112,6 +181,10 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
newcat.children.append(newitem)
|
newcat.children.append(newitem)
|
||||||
|
|
||||||
def mark_all_items(self, needle):
|
def mark_all_items(self, needle):
|
||||||
|
"""Mark a string in all items (children of root-children).
|
||||||
|
|
||||||
|
needle -- The string to mark.
|
||||||
|
"""
|
||||||
for i in range(self.rowCount()):
|
for i in range(self.rowCount()):
|
||||||
cat = self.index(i, 0)
|
cat = self.index(i, 0)
|
||||||
for k in range(self.rowCount(cat)):
|
for k in range(self.rowCount(cat)):
|
||||||
@ -121,6 +194,7 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
self.setData(idx, marks, Qt.UserRole)
|
self.setData(idx, marks, Qt.UserRole)
|
||||||
|
|
||||||
def _get_marks(self, needle, haystack):
|
def _get_marks(self, needle, haystack):
|
||||||
|
"""Return the marks for needle in haystack."""
|
||||||
pos1 = pos2 = 0
|
pos1 = pos2 = 0
|
||||||
marks = []
|
marks = []
|
||||||
if not needle:
|
if not needle:
|
||||||
@ -135,18 +209,29 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
|
|
||||||
|
|
||||||
class CompletionItem():
|
class CompletionItem():
|
||||||
|
"""An item (row) in a CompletionModel."""
|
||||||
|
|
||||||
parent = None
|
parent = None
|
||||||
_data = None
|
|
||||||
children = None
|
children = None
|
||||||
|
_data = None
|
||||||
_marks = None
|
_marks = None
|
||||||
|
|
||||||
def __init__(self, data, parent=None):
|
def __init__(self, data, parent=None):
|
||||||
|
"""Constructor for CompletionItem.
|
||||||
|
|
||||||
|
data -- The data for the model, as tuple (columns).
|
||||||
|
parent -- An optional parent item.
|
||||||
|
"""
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self._data = data
|
self._data = data
|
||||||
self.children = []
|
self.children = []
|
||||||
self._marks = []
|
self._marks = []
|
||||||
|
|
||||||
def data(self, column, role=Qt.DisplayRole):
|
def data(self, column, role=Qt.DisplayRole):
|
||||||
|
"""Get the data for role/column.
|
||||||
|
|
||||||
|
Raise ValueError if the role is invalid.
|
||||||
|
"""
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
return self._data[column]
|
return self._data[column]
|
||||||
elif role == Qt.UserRole:
|
elif role == Qt.UserRole:
|
||||||
@ -155,6 +240,10 @@ class CompletionItem():
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
def setdata(self, column, value, role=Qt.DisplayRole):
|
def setdata(self, column, value, role=Qt.DisplayRole):
|
||||||
|
"""Set the data for column/role to value.
|
||||||
|
|
||||||
|
Raise ValueError if the role is invalid.
|
||||||
|
"""
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
self._data[column] = value
|
self._data[column] = value
|
||||||
elif role == Qt.UserRole:
|
elif role == Qt.UserRole:
|
||||||
@ -163,33 +252,53 @@ class CompletionItem():
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
def column_count(self):
|
def column_count(self):
|
||||||
|
"""Return the column count in the item."""
|
||||||
return len(self._data)
|
return len(self._data)
|
||||||
|
|
||||||
def row(self):
|
def row(self):
|
||||||
|
"""Return the row index (int) of the item, or 0 if it's a root item."""
|
||||||
if self.parent:
|
if self.parent:
|
||||||
return self.parent.children.index(self)
|
return self.parent.children.index(self)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class CompletionFilterModel(QSortFilterProxyModel):
|
class CompletionFilterModel(QSortFilterProxyModel):
|
||||||
_pattern = None
|
|
||||||
|
"""Subclass of QSortFilterProxyModel with custom sorting/filtering."""
|
||||||
|
|
||||||
pattern_changed = pyqtSignal(str)
|
pattern_changed = pyqtSignal(str)
|
||||||
|
_pattern = None
|
||||||
@property
|
|
||||||
def pattern(self):
|
|
||||||
return self._pattern
|
|
||||||
|
|
||||||
@pattern.setter
|
|
||||||
def pattern(self, val):
|
|
||||||
self._pattern = val
|
|
||||||
self.invalidate()
|
|
||||||
self.pattern_changed.emit(val)
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.pattern = ''
|
self.pattern = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pattern(self):
|
||||||
|
"""Getter for pattern."""
|
||||||
|
return self._pattern
|
||||||
|
|
||||||
|
@pattern.setter
|
||||||
|
def pattern(self, val):
|
||||||
|
"""Setter for pattern.
|
||||||
|
|
||||||
|
Invalidates the filter and emits pattern_changed.
|
||||||
|
"""
|
||||||
|
self._pattern = val
|
||||||
|
self.invalidate()
|
||||||
|
self.pattern_changed.emit(val)
|
||||||
|
|
||||||
def filterAcceptsRow(self, row, parent):
|
def filterAcceptsRow(self, row, parent):
|
||||||
|
"""Custom filter implementation.
|
||||||
|
|
||||||
|
Overrides QSortFilterProxyModel::filterAcceptsRow.
|
||||||
|
|
||||||
|
row -- The row of the item.
|
||||||
|
parent -- The parent item QModelIndex.
|
||||||
|
|
||||||
|
Returns True if self.pattern is contained in item, or if it's a root
|
||||||
|
item (category). Else returns False.
|
||||||
|
"""
|
||||||
if parent == QModelIndex():
|
if parent == QModelIndex():
|
||||||
return True
|
return True
|
||||||
idx = self.sourceModel().index(row, 0, parent)
|
idx = self.sourceModel().index(row, 0, parent)
|
||||||
@ -200,6 +309,14 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
|||||||
return self.pattern in data
|
return self.pattern in data
|
||||||
|
|
||||||
def lessThan(self, lindex, rindex):
|
def lessThan(self, lindex, rindex):
|
||||||
|
"""Custom sorting implementation.
|
||||||
|
|
||||||
|
lindex -- The QModelIndex of the left item (*left* < right)
|
||||||
|
rindex -- The QModelIndex of the right item (left < *right*)
|
||||||
|
|
||||||
|
Prefers all items which start with self.pattern. Other than that, uses
|
||||||
|
normal Python string sorting.
|
||||||
|
"""
|
||||||
left = self.sourceModel().data(lindex).value()
|
left = self.sourceModel().data(lindex).value()
|
||||||
right = self.sourceModel().data(rindex).value()
|
right = self.sourceModel().data(rindex).value()
|
||||||
|
|
||||||
@ -216,9 +333,11 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
|||||||
return left < right
|
return left < right
|
||||||
|
|
||||||
def first_item(self):
|
def first_item(self):
|
||||||
|
"""Returns the first item in the model."""
|
||||||
cat = self.index(0, 0)
|
cat = self.index(0, 0)
|
||||||
return self.index(0, 0, cat)
|
return self.index(0, 0, cat)
|
||||||
|
|
||||||
def last_item(self):
|
def last_item(self):
|
||||||
|
"""Returns the last item in the model."""
|
||||||
cat = self.index(self.rowCount() - 1, 0)
|
cat = self.index(self.rowCount() - 1, 0)
|
||||||
return self.index(self.rowCount(cat) - 1, 0, cat)
|
return self.index(self.rowCount(cat) - 1, 0, cat)
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
"""Configuration storage and config-related utilities.
|
||||||
|
|
||||||
|
config -- The main Config object.
|
||||||
|
colordict -- All configured colors.
|
||||||
|
default_config -- The default config as dict.
|
||||||
|
MONOSPACE -- A list of suitable monospace fonts.
|
||||||
|
"""
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@ -61,6 +69,7 @@ MONOSPACE = ', '.join(_MONOSPACE)
|
|||||||
|
|
||||||
|
|
||||||
def init(confdir):
|
def init(confdir):
|
||||||
|
"""Initialize the global objects based on the config in configdir."""
|
||||||
global config, colordict
|
global config, colordict
|
||||||
config = Config(confdir)
|
config = Config(confdir)
|
||||||
try:
|
try:
|
||||||
@ -70,11 +79,26 @@ def init(confdir):
|
|||||||
|
|
||||||
|
|
||||||
def get_stylesheet(template):
|
def get_stylesheet(template):
|
||||||
|
"""Return a formatted stylesheet based on a template."""
|
||||||
return template.strip().format(color=colordict, monospace=MONOSPACE)
|
return template.strip().format(color=colordict, monospace=MONOSPACE)
|
||||||
|
|
||||||
|
|
||||||
class ColorDict(dict):
|
class ColorDict(dict):
|
||||||
|
"""A dict aimed at Qt stylesheet colors."""
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
|
||||||
|
"""Override dict __getitem__.
|
||||||
|
|
||||||
|
If a value wasn't found, return an empty string.
|
||||||
|
(Color not defined, so no output in the stylesheet)
|
||||||
|
|
||||||
|
If the key has a .fg. element in it, return color: X;.
|
||||||
|
If the key has a .bg. element in it, return background-color: X;.
|
||||||
|
|
||||||
|
In all other cases, return the plain value.
|
||||||
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
val = super().__getitem__(key)
|
val = super().__getitem__(key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -87,6 +111,10 @@ class ColorDict(dict):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
def getraw(self, key):
|
def getraw(self, key):
|
||||||
|
"""Get a value without the transformations done in __getitem__.
|
||||||
|
|
||||||
|
Returns a value, or None if the value wasn't found.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -94,12 +122,16 @@ class ColorDict(dict):
|
|||||||
|
|
||||||
|
|
||||||
class Config(ConfigParser):
|
class Config(ConfigParser):
|
||||||
"""Our own ConfigParser"""
|
"""Our own ConfigParser subclass."""
|
||||||
|
|
||||||
configdir = None
|
configdir = None
|
||||||
FNAME = 'config'
|
FNAME = 'config'
|
||||||
|
|
||||||
def __init__(self, configdir):
|
def __init__(self, configdir):
|
||||||
"""configdir: directory to store the config in"""
|
"""Config constructor.
|
||||||
|
|
||||||
|
configdir -- directory to store the config in.
|
||||||
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.optionxform = lambda opt: opt # be case-insensitive
|
self.optionxform = lambda opt: opt # be case-insensitive
|
||||||
self.configdir = configdir
|
self.configdir = configdir
|
||||||
@ -113,6 +145,7 @@ class Config(ConfigParser):
|
|||||||
self.read(self.configfile)
|
self.read(self.configfile)
|
||||||
|
|
||||||
def init_config(self):
|
def init_config(self):
|
||||||
|
"""Initialize Config from default_config and save it."""
|
||||||
logging.info("Initializing default config.")
|
logging.info("Initializing default config.")
|
||||||
if self.configdir is None:
|
if self.configdir is None:
|
||||||
self.read_dict(default_config)
|
self.read_dict(default_config)
|
||||||
@ -126,6 +159,7 @@ class Config(ConfigParser):
|
|||||||
cp.write(f)
|
cp.write(f)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
"""Save the config file."""
|
||||||
if self.configdir is None:
|
if self.configdir is None:
|
||||||
return
|
return
|
||||||
if not os.path.exists(self.configdir):
|
if not os.path.exists(self.configdir):
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
"""The main browser widget.
|
||||||
|
|
||||||
|
Defines BrowserTab (our own QWebView subclass) and TabbedBrowser (a TabWidget
|
||||||
|
containing BrowserTabs).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QShortcut
|
from PyQt5.QtWidgets import QShortcut
|
||||||
@ -11,7 +18,12 @@ from qutebrowser.widgets.tabbar import TabWidget
|
|||||||
|
|
||||||
|
|
||||||
class TabbedBrowser(TabWidget):
|
class TabbedBrowser(TabWidget):
|
||||||
"""A TabWidget with QWebViews inside"""
|
"""A TabWidget with QWebViews inside.
|
||||||
|
|
||||||
|
Provides methods to manage tabs, convenience methods to interact with the
|
||||||
|
current tab (cur_*) and filters signals to re-emit them when they occured
|
||||||
|
in the currently visible tab.
|
||||||
|
"""
|
||||||
|
|
||||||
cur_progress = pyqtSignal(int) # Progress of the current tab changed
|
cur_progress = pyqtSignal(int) # Progress of the current tab changed
|
||||||
cur_load_started = pyqtSignal() # Current tab started loading
|
cur_load_started = pyqtSignal() # Current tab started loading
|
||||||
@ -33,7 +45,10 @@ class TabbedBrowser(TabWidget):
|
|||||||
space.activated.connect(self.space_scroll)
|
space.activated.connect(self.space_scroll)
|
||||||
|
|
||||||
def tabopen(self, url):
|
def tabopen(self, url):
|
||||||
"""Opens a new tab with a given url"""
|
"""Open a new tab with a given url.
|
||||||
|
|
||||||
|
Also connect all the signals we need to _filter_signals.
|
||||||
|
"""
|
||||||
url = utils.qurl(url)
|
url = utils.qurl(url)
|
||||||
tab = BrowserTab(self)
|
tab = BrowserTab(self)
|
||||||
tab.openurl(url)
|
tab.openurl(url)
|
||||||
@ -60,16 +75,26 @@ class TabbedBrowser(TabWidget):
|
|||||||
tab.open_tab.connect(self.tabopen)
|
tab.open_tab.connect(self.tabopen)
|
||||||
|
|
||||||
def openurl(self, url):
|
def openurl(self, url):
|
||||||
"""Opens an url in the current tab"""
|
"""Open an url in the current tab.
|
||||||
|
|
||||||
|
Command handler for :open.
|
||||||
|
url -- The URL to open.
|
||||||
|
"""
|
||||||
self.currentWidget().openurl(url)
|
self.currentWidget().openurl(url)
|
||||||
|
|
||||||
def undo_close(self):
|
def undo_close(self):
|
||||||
"""Undos closing a tab"""
|
"""Undo closing a tab.
|
||||||
|
|
||||||
|
Command handler for :undo.
|
||||||
|
"""
|
||||||
if self._url_stack:
|
if self._url_stack:
|
||||||
self.tabopen(self._url_stack.pop())
|
self.tabopen(self._url_stack.pop())
|
||||||
|
|
||||||
def cur_close(self):
|
def cur_close(self):
|
||||||
"""Closes the current tab"""
|
"""Close the current tab.
|
||||||
|
|
||||||
|
Command handler for :close.
|
||||||
|
"""
|
||||||
if self.count() > 1:
|
if self.count() > 1:
|
||||||
idx = self.currentIndex()
|
idx = self.currentIndex()
|
||||||
tab = self.currentWidget()
|
tab = self.currentWidget()
|
||||||
@ -81,32 +106,50 @@ class TabbedBrowser(TabWidget):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def cur_reload(self):
|
def cur_reload(self):
|
||||||
"""Reloads the current tab"""
|
"""Reload the current tab.
|
||||||
|
|
||||||
|
Command handler for :reload.
|
||||||
|
"""
|
||||||
self.currentWidget().reload()
|
self.currentWidget().reload()
|
||||||
|
|
||||||
def cur_stop(self):
|
def cur_stop(self):
|
||||||
"""Stops loading in the current tab"""
|
"""Stop loading in the current tab.
|
||||||
|
|
||||||
|
Command handler for :stop.
|
||||||
|
"""
|
||||||
self.currentWidget().stop()
|
self.currentWidget().stop()
|
||||||
|
|
||||||
def cur_print(self):
|
def cur_print(self):
|
||||||
"""Prints the current tab"""
|
"""Print the current tab.
|
||||||
|
|
||||||
|
Command handler for :print.
|
||||||
|
"""
|
||||||
# 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 cur_back(self):
|
def cur_back(self):
|
||||||
"""Goes back in the history of the current tab"""
|
"""Go back in the history of the current tab.
|
||||||
|
|
||||||
|
Command handler for :back.
|
||||||
|
"""
|
||||||
# FIXME display warning if beginning of history
|
# FIXME display warning if beginning of history
|
||||||
self.currentWidget().back()
|
self.currentWidget().back()
|
||||||
|
|
||||||
def cur_forward(self):
|
def cur_forward(self):
|
||||||
"""Goes forward in the history of the current tab"""
|
"""Go forward in the history of the current tab.
|
||||||
|
|
||||||
|
Command handler for :forward.
|
||||||
|
"""
|
||||||
# FIXME display warning if end of history
|
# FIXME display warning if end of history
|
||||||
self.currentWidget().forward()
|
self.currentWidget().forward()
|
||||||
|
|
||||||
def cur_scroll(self, dx, dy, count=None):
|
def cur_scroll(self, dx, dy, count=None):
|
||||||
"""Scrolls the current tab by count * dx/dy"""
|
"""Scroll the current tab by count * dx/dy
|
||||||
|
|
||||||
|
Command handler for :scroll.
|
||||||
|
"""
|
||||||
if count is None:
|
if count is None:
|
||||||
count = 1
|
count = 1
|
||||||
dx = int(count) * int(dx)
|
dx = int(count) * int(dx)
|
||||||
@ -114,18 +157,23 @@ class TabbedBrowser(TabWidget):
|
|||||||
self.currentWidget().page().mainFrame().scroll(dx, dy)
|
self.currentWidget().page().mainFrame().scroll(dx, dy)
|
||||||
|
|
||||||
def cur_scroll_percent_x(self, perc=None, count=None):
|
def cur_scroll_percent_x(self, perc=None, count=None):
|
||||||
"""Scrolls the current tab to a specific percent of the page.
|
"""Scroll the current tab to a specific percent of the page.
|
||||||
Accepts percentage either as argument, or as count.
|
Accepts percentage either as argument, or as count.
|
||||||
|
|
||||||
|
Command handler for :scroll_perc_x.
|
||||||
"""
|
"""
|
||||||
self._cur_scroll_percent(perc, count, Qt.Horizontal)
|
self._cur_scroll_percent(perc, count, Qt.Horizontal)
|
||||||
|
|
||||||
def cur_scroll_percent_y(self, perc=None, count=None):
|
def cur_scroll_percent_y(self, perc=None, count=None):
|
||||||
"""Scrolls the current tab to a specific percent of the page
|
"""Scroll the current tab to a specific percent of the page
|
||||||
Accepts percentage either as argument, or as count.
|
Accepts percentage either as argument, or as count.
|
||||||
|
|
||||||
|
Command handler for :scroll_perc_y
|
||||||
"""
|
"""
|
||||||
self._cur_scroll_percent(perc, count, Qt.Vertical)
|
self._cur_scroll_percent(perc, count, Qt.Vertical)
|
||||||
|
|
||||||
def _cur_scroll_percent(self, perc=None, count=None, orientation=None):
|
def _cur_scroll_percent(self, perc=None, count=None, orientation=None):
|
||||||
|
"""Inner logic for cur_scroll_percent_(x|y)."""
|
||||||
if perc is None and count is None:
|
if perc is None and count is None:
|
||||||
perc = 100
|
perc = 100
|
||||||
elif perc is None:
|
elif perc is None:
|
||||||
@ -138,15 +186,11 @@ class TabbedBrowser(TabWidget):
|
|||||||
return
|
return
|
||||||
frame.setScrollBarValue(orientation, int(m * perc / 100))
|
frame.setScrollBarValue(orientation, int(m * perc / 100))
|
||||||
|
|
||||||
def space_scroll(self):
|
|
||||||
try:
|
|
||||||
amount = config.config['general']['space_scroll']
|
|
||||||
except KeyError:
|
|
||||||
amount = 200
|
|
||||||
self.cur_scroll(0, amount)
|
|
||||||
|
|
||||||
def switch_prev(self):
|
def switch_prev(self):
|
||||||
"""Switches to the previous tab"""
|
"""Switch to the previous tab.
|
||||||
|
|
||||||
|
Command handler for :tabprev.
|
||||||
|
"""
|
||||||
idx = self.currentIndex()
|
idx = self.currentIndex()
|
||||||
if idx > 0:
|
if idx > 0:
|
||||||
self.setCurrentIndex(idx - 1)
|
self.setCurrentIndex(idx - 1)
|
||||||
@ -155,7 +199,10 @@ class TabbedBrowser(TabWidget):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def switch_next(self):
|
def switch_next(self):
|
||||||
"""Switches to the next tab"""
|
"""Switch to the next tab.
|
||||||
|
|
||||||
|
Command handler for :tabnext.
|
||||||
|
"""
|
||||||
idx = self.currentIndex()
|
idx = self.currentIndex()
|
||||||
if idx < self.count() - 1:
|
if idx < self.count() - 1:
|
||||||
self.setCurrentIndex(idx + 1)
|
self.setCurrentIndex(idx + 1)
|
||||||
@ -163,37 +210,69 @@ class TabbedBrowser(TabWidget):
|
|||||||
# FIXME
|
# FIXME
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def space_scroll(self):
|
||||||
|
"""Scroll when space is pressed.
|
||||||
|
|
||||||
|
This gets called from the space QShortcut in __init__.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
amount = config.config['general']['space_scroll']
|
||||||
|
except KeyError:
|
||||||
|
amount = 200
|
||||||
|
self.cur_scroll(0, amount)
|
||||||
|
|
||||||
def keyPressEvent(self, e):
|
def keyPressEvent(self, e):
|
||||||
|
"""Extend TabWidget (QWidget)'s keyPressEvent to emit a signal."""
|
||||||
self.keypress.emit(e)
|
self.keypress.emit(e)
|
||||||
super().keyPressEvent(e)
|
super().keyPressEvent(e)
|
||||||
|
|
||||||
def _titleChanged_handler(self, text):
|
def _titleChanged_handler(self, text):
|
||||||
|
"""Set the title of a tab.
|
||||||
|
|
||||||
|
Slot for the titleChanged signal of any tab.
|
||||||
|
"""
|
||||||
if text:
|
if text:
|
||||||
self.setTabText(self.indexOf(self.sender()), text)
|
self.setTabText(self.indexOf(self.sender()), text)
|
||||||
|
|
||||||
def _loadStarted_handler(self):
|
def _loadStarted_handler(self):
|
||||||
|
"""Set url as the title of a tab after it loaded.
|
||||||
|
|
||||||
|
Slot for the loadStarted signal of any tab.
|
||||||
|
"""
|
||||||
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
|
"""Filter signals and trigger TabbedBrowser signals if the signal
|
||||||
was sent from the _current_ tab and not from any other one.
|
was sent from the _current_ tab and not from any other one.
|
||||||
|
|
||||||
|
The original signal does not matter, since we get the new signal and
|
||||||
|
all args.
|
||||||
|
|
||||||
|
signal -- The signal to emit if the sender was the current widget.
|
||||||
|
*args -- The args to pass to the signal.
|
||||||
"""
|
"""
|
||||||
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():
|
||||||
logging.debug('{} - emitting'.format(dbgstr))
|
logging.debug('{} - emitting'.format(dbgstr))
|
||||||
signal.emit(*args)
|
return signal.emit(*args)
|
||||||
else:
|
else:
|
||||||
logging.debug('{} - ignoring'.format(dbgstr))
|
logging.debug('{} - ignoring'.format(dbgstr))
|
||||||
|
|
||||||
def _currentChanged_handler(self, idx):
|
def _currentChanged_handler(self, idx):
|
||||||
|
"""Update status bar values when a tab was changed.
|
||||||
|
|
||||||
|
Slot for the currentChanged signal of any tab.
|
||||||
|
"""
|
||||||
tab = self.widget(idx)
|
tab = self.widget(idx)
|
||||||
self.cur_progress.emit(tab.progress)
|
self.cur_progress.emit(tab.progress)
|
||||||
|
|
||||||
def _scroll_pos_changed_handler(self, x, y):
|
def _scroll_pos_changed_handler(self, x, y):
|
||||||
"""Gets the new position from a BrowserTab. If it's the current tab, it
|
"""Get the new position from a BrowserTab. If it's the current tab,
|
||||||
calculates the percentage and emits cur_scroll_perc_changed.
|
calculate the percentage and emits cur_scroll_perc_changed.
|
||||||
|
|
||||||
|
Slot for the scroll_pos_changed signal of any tab.
|
||||||
"""
|
"""
|
||||||
sender = self.sender()
|
sender = self.sender()
|
||||||
if sender != self.currentWidget():
|
if sender != self.currentWidget():
|
||||||
@ -207,12 +286,15 @@ class TabbedBrowser(TabWidget):
|
|||||||
|
|
||||||
|
|
||||||
class BrowserTab(QWebView):
|
class BrowserTab(QWebView):
|
||||||
"""One browser tab in TabbedBrowser"""
|
"""One browser tab in TabbedBrowser.
|
||||||
|
|
||||||
|
Our own subclass of a QWebView with some added bells and whistles.
|
||||||
|
"""
|
||||||
progress = 0
|
progress = 0
|
||||||
scroll_pos_changed = pyqtSignal(int, int)
|
scroll_pos_changed = pyqtSignal(int, int)
|
||||||
_scroll_pos = (-1, -1)
|
|
||||||
open_new_tab = False # open new tab for the next action
|
|
||||||
open_tab = pyqtSignal('QUrl')
|
open_tab = pyqtSignal('QUrl')
|
||||||
|
_scroll_pos = (-1, -1)
|
||||||
|
_open_new_tab = False # open new tab for the next action
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -224,16 +306,32 @@ class BrowserTab(QWebView):
|
|||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def openurl(self, url):
|
def openurl(self, url):
|
||||||
"""Opens an URL in the browser"""
|
"""Open an URL in the browser.
|
||||||
|
|
||||||
|
url -- The URL to load, as string or QUrl.
|
||||||
|
"""
|
||||||
return self.load(utils.qurl(url))
|
return self.load(utils.qurl(url))
|
||||||
|
|
||||||
def link_handler(self, url):
|
def link_handler(self, url):
|
||||||
if self.open_new_tab:
|
"""Handle a link.
|
||||||
|
|
||||||
|
Called from the linkClicked signal. Checks if it should open it in a
|
||||||
|
tab (middle-click or control) or not, and does so.
|
||||||
|
|
||||||
|
url -- The url to handle, as string or QUrl.
|
||||||
|
"""
|
||||||
|
if self._open_new_tab:
|
||||||
self.open_tab.emit(url)
|
self.open_tab.emit(url)
|
||||||
else:
|
else:
|
||||||
self.openurl(url)
|
self.openurl(url)
|
||||||
|
|
||||||
def set_progress(self, prog):
|
def set_progress(self, prog):
|
||||||
|
"""Update the progress property if the loading progress changed.
|
||||||
|
|
||||||
|
Slot for the loadProgress signal.
|
||||||
|
|
||||||
|
prog -- New progress.
|
||||||
|
"""
|
||||||
self.progress = prog
|
self.progress = prog
|
||||||
|
|
||||||
def eventFilter(self, watched, e):
|
def eventFilter(self, watched, e):
|
||||||
@ -242,6 +340,9 @@ class BrowserTab(QWebView):
|
|||||||
We listen to repaint requests here, in the hope a repaint will always
|
We listen to repaint requests here, in the hope a repaint will always
|
||||||
be requested when scrolling, and if the scroll position actually
|
be requested when scrolling, and if the scroll position actually
|
||||||
changed, we emit a signal.
|
changed, we emit a signal.
|
||||||
|
|
||||||
|
watched -- The watched Qt object.
|
||||||
|
e -- The new event.
|
||||||
"""
|
"""
|
||||||
if watched == self and e.type() == QEvent.Paint:
|
if watched == self and e.type() == QEvent.Paint:
|
||||||
frame = self.page().mainFrame()
|
frame = self.page().mainFrame()
|
||||||
@ -254,10 +355,16 @@ class BrowserTab(QWebView):
|
|||||||
return super().eventFilter(watched, e)
|
return super().eventFilter(watched, e)
|
||||||
|
|
||||||
def event(self, e):
|
def event(self, e):
|
||||||
"""Another hack to see when a link was pressed with the middle
|
"""Check if a link was clicked with the middle button or Ctrl.
|
||||||
button
|
|
||||||
|
Extends the superclass event().
|
||||||
|
|
||||||
|
This also is a bit of a hack, but it seems it's the only possible way.
|
||||||
|
Set the _open_new_tab attribute accordingly.
|
||||||
|
|
||||||
|
e -- The arrived event
|
||||||
"""
|
"""
|
||||||
if e.type() in [QEvent.MouseButtonPress, QEvent.MouseButtonDblClick]:
|
if e.type() in [QEvent.MouseButtonPress, QEvent.MouseButtonDblClick]:
|
||||||
self.open_new_tab = (e.button() == Qt.MidButton or
|
self._open_new_tab = (e.button() == Qt.MidButton or
|
||||||
e.modifiers() & Qt.ControlModifier)
|
e.modifiers() & Qt.ControlModifier)
|
||||||
return super().event(e)
|
return super().event(e)
|
||||||
|
@ -157,27 +157,41 @@ class CompletionView(QTreeView):
|
|||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def tab_handler(self, shift):
|
def tab_handler(self, shift):
|
||||||
|
"""Handle a tab press for the CompletionView.
|
||||||
|
|
||||||
|
Selects the previous/next item and writes the new text to the
|
||||||
|
statusbar. Called by key_(s)tab_handler in statusbar.command.
|
||||||
|
|
||||||
|
shift -- Whether shift is pressed or not.
|
||||||
|
"""
|
||||||
if not self.completing:
|
if not self.completing:
|
||||||
|
# No completion running at the moment, ignore keypress
|
||||||
return
|
return
|
||||||
idx = self._next_idx(shift)
|
idx = self._next_idx(shift)
|
||||||
self.ignore_next = True
|
|
||||||
self.selectionModel().setCurrentIndex(
|
self.selectionModel().setCurrentIndex(
|
||||||
idx, QItemSelectionModel.ClearAndSelect)
|
idx, QItemSelectionModel.ClearAndSelect)
|
||||||
data = self.model.data(idx)
|
data = self.model.data(idx)
|
||||||
if data is not None:
|
if data is not None:
|
||||||
|
self.ignore_next = True
|
||||||
self.append_cmd_text.emit(self.model.data(idx) + ' ')
|
self.append_cmd_text.emit(self.model.data(idx) + ' ')
|
||||||
|
|
||||||
def _next_idx(self, shift):
|
def _next_idx(self, upwards):
|
||||||
|
"""Get the previous/next QModelIndex displayed in the view.
|
||||||
|
|
||||||
|
Used by tab_handler.
|
||||||
|
|
||||||
|
upwards -- Get previous item, not next.
|
||||||
|
"""
|
||||||
idx = self.selectionModel().currentIndex()
|
idx = self.selectionModel().currentIndex()
|
||||||
if not idx.isValid():
|
if not idx.isValid():
|
||||||
# No item selected yet
|
# No item selected yet
|
||||||
return self.model.first_item()
|
return self.model.first_item()
|
||||||
while True:
|
while True:
|
||||||
idx = self.indexAbove(idx) if shift else self.indexBelow(idx)
|
idx = self.indexAbove(idx) if upwards else self.indexBelow(idx)
|
||||||
# wrap around if we arrived at beginning/end
|
# wrap around if we arrived at beginning/end
|
||||||
if not idx.isValid() and shift:
|
if not idx.isValid() and upwards:
|
||||||
return self.model.last_item()
|
return self.model.last_item()
|
||||||
elif not idx.isValid() and not shift:
|
elif not idx.isValid() and not upwards:
|
||||||
return self.model.first_item()
|
return self.model.first_item()
|
||||||
elif idx.parent().isValid():
|
elif idx.parent().isValid():
|
||||||
# Item is a real item, not a category header -> success
|
# Item is a real item, not a category header -> success
|
||||||
@ -185,11 +199,21 @@ class CompletionView(QTreeView):
|
|||||||
|
|
||||||
|
|
||||||
class CompletionItemDelegate(QStyledItemDelegate):
|
class CompletionItemDelegate(QStyledItemDelegate):
|
||||||
|
"""Delegate used by CompletionView to draw individual items.
|
||||||
|
|
||||||
|
Mainly a cleaned up port of Qt's way to draw a TreeView item, except it
|
||||||
|
uses a QTextDocument to draw the text and add marking.
|
||||||
|
|
||||||
|
Original implementation:
|
||||||
|
qt/src/gui/styles/qcommonstyle.cpp:drawControl:2153
|
||||||
|
"""
|
||||||
|
|
||||||
opt = None
|
opt = None
|
||||||
style = None
|
style = None
|
||||||
painter = None
|
painter = None
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
|
"""Overrides the QStyledItemDelegate paint function."""
|
||||||
painter.save()
|
painter.save()
|
||||||
|
|
||||||
self.painter = painter
|
self.painter = painter
|
||||||
@ -205,10 +229,12 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
painter.restore()
|
painter.restore()
|
||||||
|
|
||||||
def _draw_background(self):
|
def _draw_background(self):
|
||||||
|
"""Draw the background of an ItemViewItem"""
|
||||||
self.style.drawPrimitive(self.style.PE_PanelItemViewItem, self.opt,
|
self.style.drawPrimitive(self.style.PE_PanelItemViewItem, self.opt,
|
||||||
self.painter, self.opt.widget)
|
self.painter, self.opt.widget)
|
||||||
|
|
||||||
def _draw_icon(self):
|
def _draw_icon(self):
|
||||||
|
"""Draw the icon of an ItemViewItem"""
|
||||||
icon_rect = self.style.subElementRect(
|
icon_rect = self.style.subElementRect(
|
||||||
self.style.SE_ItemViewItemDecoration, self.opt, self.opt.widget)
|
self.style.SE_ItemViewItemDecoration, self.opt, self.opt.widget)
|
||||||
|
|
||||||
@ -222,6 +248,13 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
self.opt.decorationAlignment, mode, state)
|
self.opt.decorationAlignment, mode, state)
|
||||||
|
|
||||||
def _draw_text(self, index):
|
def _draw_text(self, index):
|
||||||
|
"""Draw the text of an ItemViewItem.
|
||||||
|
|
||||||
|
This is the main part where we differ from the original implementation
|
||||||
|
in Qt: We use a QTextDocument to draw text.
|
||||||
|
|
||||||
|
index -- The QModelIndex of the item to draw.
|
||||||
|
"""
|
||||||
if not self.opt.text:
|
if not self.opt.text:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -258,6 +291,11 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
self.painter.restore()
|
self.painter.restore()
|
||||||
|
|
||||||
def _draw_textdoc(self, index, text_rect):
|
def _draw_textdoc(self, index, text_rect):
|
||||||
|
"""Draw the QTextDocument of an item.
|
||||||
|
|
||||||
|
index -- The QModelIndex of the item to draw.
|
||||||
|
text_rect -- The QRect to clip the drawing to.
|
||||||
|
"""
|
||||||
# FIXME we probably should do eliding here. See
|
# FIXME we probably should do eliding here. See
|
||||||
# qcommonstyle.cpp:viewItemDrawText
|
# qcommonstyle.cpp:viewItemDrawText
|
||||||
clip = QRectF(0, 0, text_rect.width(), text_rect.height())
|
clip = QRectF(0, 0, text_rect.width(), text_rect.height())
|
||||||
@ -298,6 +336,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
doc.drawContents(self.painter, clip)
|
doc.drawContents(self.painter, clip)
|
||||||
|
|
||||||
def _draw_focus_rect(self):
|
def _draw_focus_rect(self):
|
||||||
|
"""Draws the focus rectangle of an ItemViewItem"""
|
||||||
state = self.opt.state
|
state = self.opt.state
|
||||||
if not state & QStyle.State_HasFocus:
|
if not state & QStyle.State_HasFocus:
|
||||||
return
|
return
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""The main window of QuteBrowser."""
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QWidget
|
from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QWidget
|
||||||
|
|
||||||
from qutebrowser.widgets.statusbar import StatusBar
|
from qutebrowser.widgets.statusbar import StatusBar
|
||||||
@ -6,7 +8,11 @@ from qutebrowser.widgets.completion import CompletionView
|
|||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
"""The main window of QuteBrowser"""
|
"""The main window of QuteBrowser.
|
||||||
|
|
||||||
|
Adds all needed components to a vbox, initializes subwidgets and connects
|
||||||
|
signals.
|
||||||
|
"""
|
||||||
cwidget = None
|
cwidget = None
|
||||||
vbox = None
|
vbox = None
|
||||||
tabs = None
|
tabs = None
|
||||||
|
@ -69,5 +69,9 @@ class StatusBar(QWidget):
|
|||||||
self.txt.error = ''
|
self.txt.error = ''
|
||||||
|
|
||||||
def resizeEvent(self, e):
|
def resizeEvent(self, e):
|
||||||
|
"""Override resizeEvent of QWidget to emit a resized signal afterwards.
|
||||||
|
|
||||||
|
e -- The QResizeEvent.
|
||||||
|
"""
|
||||||
super().resizeEvent(e)
|
super().resizeEvent(e)
|
||||||
self.resized.emit(self.geometry())
|
self.resized.emit(self.geometry())
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
"""The commandline part of the statusbar."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QLineEdit, QShortcut
|
from PyQt5.QtWidgets import QLineEdit, QShortcut
|
||||||
@ -6,7 +7,7 @@ from PyQt5.QtGui import QValidator, QKeySequence
|
|||||||
|
|
||||||
|
|
||||||
class Command(QLineEdit):
|
class Command(QLineEdit):
|
||||||
"""The commandline part of the statusbar"""
|
"""The commandline part of the statusbar."""
|
||||||
# Emitted when a command is triggered by the user
|
# Emitted when a command is triggered by the user
|
||||||
got_cmd = pyqtSignal(str)
|
got_cmd = pyqtSignal(str)
|
||||||
statusbar = None # The status bar object
|
statusbar = None # The status bar object
|
||||||
@ -30,18 +31,20 @@ class Command(QLineEdit):
|
|||||||
self.returnPressed.connect(self.process_cmd)
|
self.returnPressed.connect(self.process_cmd)
|
||||||
self.textEdited.connect(self._histbrowse_stop)
|
self.textEdited.connect(self._histbrowse_stop)
|
||||||
|
|
||||||
for (key, handler) in [(Qt.Key_Escape, self.esc_pressed),
|
for (key, handler) in [
|
||||||
|
(Qt.Key_Escape, self.esc_pressed),
|
||||||
(Qt.Key_Up, self.key_up_handler),
|
(Qt.Key_Up, self.key_up_handler),
|
||||||
(Qt.Key_Down, self.key_down_handler),
|
(Qt.Key_Down, self.key_down_handler),
|
||||||
(Qt.Key_Tab | Qt.SHIFT, self.key_stab_handler),
|
(Qt.Key_Tab | Qt.SHIFT, lambda: self.tab_pressed.emit(True)),
|
||||||
(Qt.Key_Tab, self.key_tab_handler)]:
|
(Qt.Key_Tab, lambda: self.tab_pressed.emit(False))
|
||||||
|
]:
|
||||||
sc = QShortcut(self)
|
sc = QShortcut(self)
|
||||||
sc.setKey(QKeySequence(key))
|
sc.setKey(QKeySequence(key))
|
||||||
sc.setContext(Qt.WidgetWithChildrenShortcut)
|
sc.setContext(Qt.WidgetWithChildrenShortcut)
|
||||||
sc.activated.connect(handler)
|
sc.activated.connect(handler)
|
||||||
|
|
||||||
def process_cmd(self):
|
def process_cmd(self):
|
||||||
"""Handle the command in the status bar"""
|
"""Handle the command in the status bar."""
|
||||||
self._histbrowse_stop()
|
self._histbrowse_stop()
|
||||||
text = self.text().lstrip(':')
|
text = self.text().lstrip(':')
|
||||||
if not self.history or text != self.history[-1]:
|
if not self.history or text != self.history[-1]:
|
||||||
@ -50,18 +53,18 @@ class Command(QLineEdit):
|
|||||||
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"""
|
"""Preset the statusbar to some text."""
|
||||||
self.setText(':' + text)
|
self.setText(':' + text)
|
||||||
self.setFocus()
|
self.setFocus()
|
||||||
|
|
||||||
def append_cmd(self, text):
|
def append_cmd(self, text):
|
||||||
"""Append text to the commandline"""
|
"""Append text to the commandline."""
|
||||||
# FIXME do the right thing here
|
# FIXME do the right thing here
|
||||||
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"""
|
"""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('')
|
||||||
@ -70,11 +73,18 @@ class Command(QLineEdit):
|
|||||||
super().focusOutEvent(e)
|
super().focusOutEvent(e)
|
||||||
|
|
||||||
def focusInEvent(self, e):
|
def focusInEvent(self, e):
|
||||||
"""Clear error message when the statusbar is focused"""
|
"""Clear error message when the statusbar is focused."""
|
||||||
self.statusbar.clear_error()
|
self.statusbar.clear_error()
|
||||||
super().focusInEvent(e)
|
super().focusInEvent(e)
|
||||||
|
|
||||||
def _histbrowse_start(self):
|
def _histbrowse_start(self):
|
||||||
|
|
||||||
|
"""Start browsing to the history.
|
||||||
|
|
||||||
|
Called when the user presses the up/down key and wasn't browsing the
|
||||||
|
history already.
|
||||||
|
"""
|
||||||
|
|
||||||
pre = self.text().strip().lstrip(':')
|
pre = self.text().strip().lstrip(':')
|
||||||
logging.debug('Preset text: "{}"'.format(pre))
|
logging.debug('Preset text: "{}"'.format(pre))
|
||||||
if pre:
|
if pre:
|
||||||
@ -84,9 +94,11 @@ class Command(QLineEdit):
|
|||||||
self._histpos = len(self._tmphist) - 1
|
self._histpos = len(self._tmphist) - 1
|
||||||
|
|
||||||
def _histbrowse_stop(self):
|
def _histbrowse_stop(self):
|
||||||
|
"""Stop browsing the history."""
|
||||||
self._histpos = None
|
self._histpos = None
|
||||||
|
|
||||||
def key_up_handler(self):
|
def key_up_handler(self):
|
||||||
|
"""Handle Up presses (go back in history)."""
|
||||||
logging.debug("history up [pre]: pos {}".format(self._histpos))
|
logging.debug("history up [pre]: pos {}".format(self._histpos))
|
||||||
if self._histpos is None:
|
if self._histpos is None:
|
||||||
self._histbrowse_start()
|
self._histbrowse_start()
|
||||||
@ -101,6 +113,7 @@ class Command(QLineEdit):
|
|||||||
self.set_cmd(self._tmphist[self._histpos])
|
self.set_cmd(self._tmphist[self._histpos])
|
||||||
|
|
||||||
def key_down_handler(self):
|
def key_down_handler(self):
|
||||||
|
"""Handle Down presses (go forward in history)."""
|
||||||
logging.debug("history up [pre]: pos {}".format(self._histpos,
|
logging.debug("history up [pre]: pos {}".format(self._histpos,
|
||||||
self._tmphist, len(self._tmphist), self._histpos))
|
self._tmphist, len(self._tmphist), self._histpos))
|
||||||
if (self._histpos is None or
|
if (self._histpos is None or
|
||||||
@ -112,16 +125,20 @@ class Command(QLineEdit):
|
|||||||
self._tmphist, len(self._tmphist), self._histpos))
|
self._tmphist, len(self._tmphist), self._histpos))
|
||||||
self.set_cmd(self._tmphist[self._histpos])
|
self.set_cmd(self._tmphist[self._histpos])
|
||||||
|
|
||||||
def key_tab_handler(self):
|
|
||||||
self.tab_pressed.emit(False)
|
|
||||||
|
|
||||||
def key_stab_handler(self):
|
|
||||||
self.tab_pressed.emit(True)
|
|
||||||
|
|
||||||
|
|
||||||
class Validator(QValidator):
|
class Validator(QValidator):
|
||||||
"""Validator to prevent the : from getting deleted"""
|
"""Validator to prevent the : from getting deleted"""
|
||||||
|
|
||||||
def validate(self, string, pos):
|
def validate(self, string, pos):
|
||||||
|
|
||||||
|
"""Overrides QValidator::validate.
|
||||||
|
|
||||||
|
string -- The string to validate.
|
||||||
|
pos -- The current curser position.
|
||||||
|
|
||||||
|
Returns a tuple (status, string, pos) as a QValidator should.\
|
||||||
|
"""
|
||||||
|
|
||||||
if string.startswith(':'):
|
if string.startswith(':'):
|
||||||
return (QValidator.Acceptable, string, pos)
|
return (QValidator.Acceptable, string, pos)
|
||||||
else:
|
else:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""Widget to show the percentage of the page load in the statusbar."""
|
||||||
|
|
||||||
import qutebrowser.utils.config as config
|
import qutebrowser.utils.config as config
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QProgressBar, QSizePolicy
|
from PyQt5.QtWidgets import QProgressBar, QSizePolicy
|
||||||
@ -5,7 +7,7 @@ from PyQt5.QtCore import QSize
|
|||||||
|
|
||||||
|
|
||||||
class Progress(QProgressBar):
|
class Progress(QProgressBar):
|
||||||
""" The progress bar part of the status bar"""
|
"""The progress bar part of the status bar."""
|
||||||
statusbar = None
|
statusbar = None
|
||||||
color = None
|
color = None
|
||||||
_stylesheet = """
|
_stylesheet = """
|
||||||
@ -30,21 +32,28 @@ class Progress(QProgressBar):
|
|||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
"""Update the stylesheet if relevant attributes have been changed"""
|
"""Update the stylesheet if relevant attributes have been changed."""
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
if name == 'color' and value is not None:
|
if name == 'color' and value is not None:
|
||||||
config.colordict['statusbar.progress.bg.__cur__'] = value
|
config.colordict['statusbar.progress.bg.__cur__'] = value
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
||||||
|
|
||||||
def minimumSizeHint(self):
|
def minimumSizeHint(self):
|
||||||
|
"""Return the size of the progress widget."""
|
||||||
status_size = self.statusbar.size()
|
status_size = self.statusbar.size()
|
||||||
return QSize(100, status_size.height())
|
return QSize(100, status_size.height())
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
|
|
||||||
|
"""Return the size of the progress widget.
|
||||||
|
|
||||||
|
Simply copied from minimumSizeHint because the SizePolicy is fixed.
|
||||||
|
"""
|
||||||
|
|
||||||
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"""
|
"""Set the progress of the bar and show/hide it if necessary."""
|
||||||
# TODO display failed loading in some meaningful way?
|
# TODO display failed loading in some meaningful way?
|
||||||
if prog == 100:
|
if prog == 100:
|
||||||
self.setValue(prog)
|
self.setValue(prog)
|
||||||
@ -56,6 +65,12 @@ class Progress(QProgressBar):
|
|||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def load_finished(self, ok):
|
def load_finished(self, ok):
|
||||||
|
|
||||||
|
"""Hide the progress bar or color it red, depending on ok.
|
||||||
|
|
||||||
|
Slot for the loadFinished signal of a QWebView.
|
||||||
|
"""
|
||||||
|
|
||||||
if ok:
|
if ok:
|
||||||
self.hide()
|
self.hide()
|
||||||
else:
|
else:
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
|
"""The text part of the statusbar."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from PyQt5.QtWidgets import QLabel
|
from PyQt5.QtWidgets import QLabel
|
||||||
|
|
||||||
|
|
||||||
class Text(QLabel):
|
class Text(QLabel):
|
||||||
"""The text part of the status bar, composed of several 'widgets'"""
|
|
||||||
|
"""The text part of the status bar.
|
||||||
|
|
||||||
|
Contains several parts (keystring, error, text, scrollperc) which are later
|
||||||
|
joined and displayed."""
|
||||||
|
|
||||||
keystring = ''
|
keystring = ''
|
||||||
error = ''
|
error = ''
|
||||||
text = ''
|
text = ''
|
||||||
@ -18,11 +25,11 @@ class Text(QLabel):
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def set_keystring(self, s):
|
def set_keystring(self, s):
|
||||||
"""Setter to be used as a Qt slot"""
|
"""Setter to be used as a Qt slot."""
|
||||||
self.keystring = s
|
self.keystring = s
|
||||||
|
|
||||||
def set_perc(self, x, y):
|
def set_perc(self, x, y):
|
||||||
"""Setter to be used as a Qt slot"""
|
"""Setter to be used as a Qt slot."""
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
if y == 0:
|
if y == 0:
|
||||||
self.scrollperc = '[top]'
|
self.scrollperc = '[top]'
|
||||||
@ -32,10 +39,11 @@ class Text(QLabel):
|
|||||||
self.scrollperc = '[{}%]'.format(y)
|
self.scrollperc = '[{}%]'.format(y)
|
||||||
|
|
||||||
def set_text(self, text):
|
def set_text(self, text):
|
||||||
|
"""Setter to be used as a Qt slot."""
|
||||||
logging.debug('Setting text to "{}"'.format(text))
|
logging.debug('Setting text to "{}"'.format(text))
|
||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the text displayed"""
|
"""Update the text displayed."""
|
||||||
self.setText(' '.join([self.keystring, self.error, self.text,
|
self.setText(' '.join([self.keystring, self.error, self.text,
|
||||||
self.scrollperc]))
|
self.scrollperc]))
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""The tab widget used for TabbedBrowser from browser.py."""
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QTabWidget
|
from PyQt5.QtWidgets import QTabWidget
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
@ -5,7 +7,7 @@ import qutebrowser.utils.config as config
|
|||||||
|
|
||||||
|
|
||||||
class TabWidget(QTabWidget):
|
class TabWidget(QTabWidget):
|
||||||
"""The tabwidget used for TabbedBrowser"""
|
"""The tabwidget used for TabbedBrowser."""
|
||||||
|
|
||||||
# FIXME there is still some ugly 1px white stripe from somewhere if we do
|
# FIXME there is still some ugly 1px white stripe from somewhere if we do
|
||||||
# background-color: grey for QTabBar...
|
# background-color: grey for QTabBar...
|
||||||
|
Loading…
Reference in New Issue
Block a user