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
|
||||
import sys
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
""" Initialization of qutebrowser and application-wide things """
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import faulthandler
|
||||
@ -15,7 +17,13 @@ from qutebrowser.utils.appdirs import AppDirs
|
||||
|
||||
|
||||
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
|
||||
config = None # Config(Parser) object
|
||||
mainwindow = None
|
||||
@ -27,13 +35,13 @@ class QuteBrowser(QApplication):
|
||||
def __init__(self):
|
||||
super().__init__(sys.argv)
|
||||
# Exit on exceptions
|
||||
sys.excepthook = self.tmp_exception_hook
|
||||
sys.excepthook = self._tmp_exception_hook
|
||||
|
||||
# Handle segfaults
|
||||
faulthandler.enable()
|
||||
|
||||
self.parseopts()
|
||||
self.initlog()
|
||||
self._parseopts()
|
||||
self._initlog()
|
||||
|
||||
self.dirs = AppDirs('qutebrowser')
|
||||
if self.args.confdir is None:
|
||||
@ -46,7 +54,7 @@ class QuteBrowser(QApplication):
|
||||
|
||||
self.commandparser = cmdutils.CommandParser()
|
||||
self.keyparser = KeyParser(self.mainwindow)
|
||||
self.init_cmds()
|
||||
self._init_cmds()
|
||||
self.mainwindow = MainWindow()
|
||||
|
||||
self.aboutToQuit.connect(config.config.save)
|
||||
@ -62,15 +70,23 @@ class QuteBrowser(QApplication):
|
||||
self.mainwindow.status.txt.set_keystring)
|
||||
|
||||
self.mainwindow.show()
|
||||
self.python_hacks()
|
||||
self._python_hacks()
|
||||
|
||||
def tmp_exception_hook(self, exctype, value, traceback):
|
||||
"""Exception hook while initializing, simply exit"""
|
||||
def _tmp_exception_hook(self, exctype, value, traceback):
|
||||
"""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)
|
||||
self.exit(1)
|
||||
|
||||
def exception_hook(self, exctype, value, traceback):
|
||||
"""Try very hard to write open tabs to a file and exit gracefully"""
|
||||
def _exception_hook(self, exctype, value, traceback):
|
||||
"""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
|
||||
sys.__excepthook__(exctype, value, traceback)
|
||||
try:
|
||||
@ -84,22 +100,21 @@ class QuteBrowser(QApplication):
|
||||
pass
|
||||
self.exit(1)
|
||||
|
||||
def python_hacks(self):
|
||||
"""Gets around some PyQt-oddities by evil hacks"""
|
||||
## Make python exceptions work
|
||||
sys.excepthook = self.exception_hook
|
||||
def _python_hacks(self):
|
||||
"""Get around some PyQt-oddities by evil hacks.
|
||||
|
||||
## 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))
|
||||
|
||||
## 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):
|
||||
"""Parse command line options"""
|
||||
def _parseopts(self):
|
||||
"""Parse command line options."""
|
||||
parser = ArgumentParser("usage: %(prog)s [options]")
|
||||
parser.add_argument('-l', '--log', dest='loglevel',
|
||||
help='Set loglevel', default='info')
|
||||
@ -107,8 +122,8 @@ class QuteBrowser(QApplication):
|
||||
'(empty for no config storage)')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def initlog(self):
|
||||
"""Initialisation of the log"""
|
||||
def _initlog(self):
|
||||
"""Initialisation of the logging output."""
|
||||
loglevel = self.args.loglevel
|
||||
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||
if not isinstance(numeric_level, int):
|
||||
@ -119,8 +134,11 @@ class QuteBrowser(QApplication):
|
||||
'[%(module)s:%(funcName)s:%(lineno)s] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
def init_cmds(self):
|
||||
"""Initialisation of the qutebrowser commands"""
|
||||
def _init_cmds(self):
|
||||
"""Initialisation of the qutebrowser commands.
|
||||
|
||||
Registers all commands, connects its signals, and sets up keyparser.
|
||||
"""
|
||||
cmdutils.register_all()
|
||||
for cmd in cmdutils.cmd_dict.values():
|
||||
cmd.signal.connect(self.cmd_handler)
|
||||
@ -130,8 +148,10 @@ class QuteBrowser(QApplication):
|
||||
pass
|
||||
|
||||
def cmd_handler(self, tpl):
|
||||
"""Handler which gets called from all commands and delegates the
|
||||
specific actions.
|
||||
"""Handle commands and delegate the 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, ...]
|
||||
|
||||
@ -163,12 +183,17 @@ class QuteBrowser(QApplication):
|
||||
handler = handlers[cmd]
|
||||
|
||||
if self.sender().count:
|
||||
handler(*args, count=count)
|
||||
return handler(*args, count=count)
|
||||
else:
|
||||
handler(*args)
|
||||
return handler(*args)
|
||||
|
||||
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:
|
||||
r = eval(s)
|
||||
out = repr(r)
|
||||
|
@ -5,8 +5,10 @@ Defined here to avoid circular dependency hell.
|
||||
|
||||
|
||||
class NoSuchCommandError(ValueError):
|
||||
"""Raised when a command wasn't found."""
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentCountError(TypeError):
|
||||
"""Raised when a command was called with an invalid count of arguments."""
|
||||
pass
|
||||
|
@ -1,3 +1,6 @@
|
||||
"""Parse keypresses/keychains in the main window."""
|
||||
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
@ -8,7 +11,7 @@ from qutebrowser.commands.utils import (CommandParser, ArgumentCountError,
|
||||
|
||||
|
||||
class KeyParser(QObject):
|
||||
"""Parser for vim-like key sequences"""
|
||||
"""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)
|
||||
@ -27,8 +30,9 @@ class KeyParser(QObject):
|
||||
self.commandparser = CommandParser()
|
||||
|
||||
def from_config_sect(self, sect):
|
||||
"""Loads keybindings from a ConfigParser section, in the config format
|
||||
key = command, e.g.
|
||||
"""Load keybindings from a ConfigParser section.
|
||||
|
||||
Config format: key = command, e.g.:
|
||||
gg = scrollstart
|
||||
"""
|
||||
for (key, cmd) in sect.items():
|
||||
@ -36,13 +40,17 @@ class KeyParser(QObject):
|
||||
self.bindings[key] = cmd
|
||||
|
||||
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.keystring_updated.emit(self.keystring)
|
||||
|
||||
def _handle(self, e):
|
||||
"""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
|
||||
"""
|
||||
logging.debug('Got key: {} / text: "{}"'.format(e.key(), e.text()))
|
||||
@ -98,7 +106,10 @@ class KeyParser(QObject):
|
||||
return
|
||||
|
||||
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:
|
||||
cmdstr_hay = self.bindings[cmdstr_needle]
|
||||
return (self.MATCH_DEFINITIVE, cmdstr_hay)
|
||||
|
@ -1,3 +1,6 @@
|
||||
"""Contains the Command class, a skeleton for a command."""
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
@ -6,8 +9,9 @@ from qutebrowser.commands.exceptions import ArgumentCountError
|
||||
|
||||
|
||||
class Command(QObject):
|
||||
"""Base skeleton for a command. See the module help for
|
||||
qutebrowser.commands.commands for details.
|
||||
"""Base skeleton for a command.
|
||||
|
||||
See the module documentation for qutebrowser.commands.commands for details.
|
||||
"""
|
||||
|
||||
# FIXME:
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Various command utils and the Command base class"""
|
||||
"""Contains various command utils, and the CommandParser."""
|
||||
|
||||
|
||||
import inspect
|
||||
import shlex
|
||||
@ -10,6 +11,7 @@ from qutebrowser.commands.exceptions import (ArgumentCountError,
|
||||
NoSuchCommandError)
|
||||
from qutebrowser.utils.completion import CompletionModel
|
||||
|
||||
# A mapping from command-strings to command objects.
|
||||
cmd_dict = {}
|
||||
|
||||
|
||||
@ -28,13 +30,17 @@ def register_all():
|
||||
|
||||
|
||||
class CommandParser(QObject):
|
||||
"""Parser for qutebrowser commandline commands"""
|
||||
"""Parse qutebrowser commandline commands."""
|
||||
text = ''
|
||||
cmd = ''
|
||||
args = []
|
||||
error = pyqtSignal(str) # Emitted if there's an error
|
||||
|
||||
def _parse(self, text):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
Raise NoSuchCommandError if a command wasn't found.
|
||||
"""
|
||||
self.text = text
|
||||
parts = self.text.strip().split(maxsplit=1)
|
||||
cmdstr = parts[0]
|
||||
@ -53,19 +59,22 @@ class CommandParser(QObject):
|
||||
self.args = args
|
||||
|
||||
def _check(self):
|
||||
"""Check if the argument count for the command is correct."""
|
||||
self.cmd.check(self.args)
|
||||
|
||||
def _run(self, count=None):
|
||||
"""Run a command with an optional count."""
|
||||
if count is not None:
|
||||
self.cmd.run(self.args, count=count)
|
||||
else:
|
||||
self.cmd.run(self.args)
|
||||
|
||||
def run(self, text, count=None, ignore_exc=True):
|
||||
"""Parses a command from a line of text.
|
||||
If ignore_exc is True, ignores exceptions and returns True/False
|
||||
instead.
|
||||
Raises NoSuchCommandError if a command wasn't found, and
|
||||
"""Parse a command from a line of text.
|
||||
|
||||
If ignore_exc is True, ignore exceptions and return True/False.
|
||||
|
||||
Raise NoSuchCommandError if a command wasn't found, and
|
||||
ArgumentCountError if a command was called with the wrong count of
|
||||
arguments.
|
||||
"""
|
||||
@ -89,7 +98,11 @@ class CommandParser(QObject):
|
||||
|
||||
|
||||
class CommandCompletionModel(CompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all commands and descriptions."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
assert cmd_dict
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Simple browser for testing purposes"""
|
||||
"""Very simple browser for testing purposes."""
|
||||
|
||||
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 PyQt5.QtCore import (QAbstractItemModel, Qt, QModelIndex, QVariant,
|
||||
@ -5,6 +14,12 @@ from PyQt5.QtCore import (QAbstractItemModel, Qt, QModelIndex, QVariant,
|
||||
|
||||
|
||||
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):
|
||||
super().__init__(parent)
|
||||
self._data = OrderedDict()
|
||||
@ -12,22 +27,40 @@ class CompletionModel(QAbstractItemModel):
|
||||
self.root = CompletionItem([""] * 2)
|
||||
|
||||
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)
|
||||
node.children.pop(position)
|
||||
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():
|
||||
return index.internalPointer()
|
||||
else:
|
||||
return self.root
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
"""Return the column count in the model.
|
||||
|
||||
Overrides QAbstractItemModel::columnCount.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
return self.root.column_count()
|
||||
|
||||
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():
|
||||
return QVariant()
|
||||
item = index.internalPointer()
|
||||
@ -37,6 +70,11 @@ class CompletionModel(QAbstractItemModel):
|
||||
return QVariant()
|
||||
|
||||
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
|
||||
# tries to select them
|
||||
if not index.isValid():
|
||||
@ -48,11 +86,21 @@ class CompletionModel(QAbstractItemModel):
|
||||
return flags | Qt.ItemIsSelectable
|
||||
|
||||
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:
|
||||
return QVariant(self.root.data(section))
|
||||
return None
|
||||
return QVariant()
|
||||
|
||||
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():
|
||||
return False
|
||||
item = index.internalPointer()
|
||||
@ -64,6 +112,11 @@ class CompletionModel(QAbstractItemModel):
|
||||
return True
|
||||
|
||||
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
|
||||
0 <= column < self.columnCount(parent)):
|
||||
pass
|
||||
@ -82,6 +135,11 @@ class CompletionModel(QAbstractItemModel):
|
||||
return QModelIndex()
|
||||
|
||||
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():
|
||||
return QModelIndex()
|
||||
item = index.internalPointer().parent
|
||||
@ -90,6 +148,11 @@ class CompletionModel(QAbstractItemModel):
|
||||
return self.createIndex(item.row(), 0, item)
|
||||
|
||||
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:
|
||||
return 0
|
||||
|
||||
@ -101,9 +164,15 @@ class CompletionModel(QAbstractItemModel):
|
||||
return len(pitem.children)
|
||||
|
||||
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
|
||||
|
||||
def init_data(self):
|
||||
"""Initialize the Qt model based on the data in self._data."""
|
||||
for (cat, items) in self._data.items():
|
||||
newcat = CompletionItem([cat], self.root)
|
||||
self.root.children.append(newcat)
|
||||
@ -112,6 +181,10 @@ class CompletionModel(QAbstractItemModel):
|
||||
newcat.children.append(newitem)
|
||||
|
||||
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()):
|
||||
cat = self.index(i, 0)
|
||||
for k in range(self.rowCount(cat)):
|
||||
@ -121,6 +194,7 @@ class CompletionModel(QAbstractItemModel):
|
||||
self.setData(idx, marks, Qt.UserRole)
|
||||
|
||||
def _get_marks(self, needle, haystack):
|
||||
"""Return the marks for needle in haystack."""
|
||||
pos1 = pos2 = 0
|
||||
marks = []
|
||||
if not needle:
|
||||
@ -135,18 +209,29 @@ class CompletionModel(QAbstractItemModel):
|
||||
|
||||
|
||||
class CompletionItem():
|
||||
"""An item (row) in a CompletionModel."""
|
||||
|
||||
parent = None
|
||||
_data = None
|
||||
children = None
|
||||
_data = None
|
||||
_marks = 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._data = data
|
||||
self.children = []
|
||||
self._marks = []
|
||||
|
||||
def data(self, column, role=Qt.DisplayRole):
|
||||
"""Get the data for role/column.
|
||||
|
||||
Raise ValueError if the role is invalid.
|
||||
"""
|
||||
if role == Qt.DisplayRole:
|
||||
return self._data[column]
|
||||
elif role == Qt.UserRole:
|
||||
@ -155,6 +240,10 @@ class CompletionItem():
|
||||
raise ValueError
|
||||
|
||||
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:
|
||||
self._data[column] = value
|
||||
elif role == Qt.UserRole:
|
||||
@ -163,33 +252,53 @@ class CompletionItem():
|
||||
raise ValueError
|
||||
|
||||
def column_count(self):
|
||||
"""Return the column count in the item."""
|
||||
return len(self._data)
|
||||
|
||||
def row(self):
|
||||
"""Return the row index (int) of the item, or 0 if it's a root item."""
|
||||
if self.parent:
|
||||
return self.parent.children.index(self)
|
||||
return 0
|
||||
|
||||
|
||||
class CompletionFilterModel(QSortFilterProxyModel):
|
||||
_pattern = None
|
||||
|
||||
"""Subclass of QSortFilterProxyModel with custom sorting/filtering."""
|
||||
|
||||
pattern_changed = pyqtSignal(str)
|
||||
|
||||
@property
|
||||
def pattern(self):
|
||||
return self._pattern
|
||||
|
||||
@pattern.setter
|
||||
def pattern(self, val):
|
||||
self._pattern = val
|
||||
self.invalidate()
|
||||
self.pattern_changed.emit(val)
|
||||
_pattern = None
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
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):
|
||||
"""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():
|
||||
return True
|
||||
idx = self.sourceModel().index(row, 0, parent)
|
||||
@ -200,6 +309,14 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
||||
return self.pattern in data
|
||||
|
||||
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()
|
||||
right = self.sourceModel().data(rindex).value()
|
||||
|
||||
@ -216,9 +333,11 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
||||
return left < right
|
||||
|
||||
def first_item(self):
|
||||
"""Returns the first item in the model."""
|
||||
cat = self.index(0, 0)
|
||||
return self.index(0, 0, cat)
|
||||
|
||||
def last_item(self):
|
||||
"""Returns the last item in the model."""
|
||||
cat = self.index(self.rowCount() - 1, 0)
|
||||
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
|
||||
import logging
|
||||
@ -61,6 +69,7 @@ MONOSPACE = ', '.join(_MONOSPACE)
|
||||
|
||||
|
||||
def init(confdir):
|
||||
"""Initialize the global objects based on the config in configdir."""
|
||||
global config, colordict
|
||||
config = Config(confdir)
|
||||
try:
|
||||
@ -70,11 +79,26 @@ def init(confdir):
|
||||
|
||||
|
||||
def get_stylesheet(template):
|
||||
"""Return a formatted stylesheet based on a template."""
|
||||
return template.strip().format(color=colordict, monospace=MONOSPACE)
|
||||
|
||||
|
||||
class ColorDict(dict):
|
||||
"""A dict aimed at Qt stylesheet colors."""
|
||||
|
||||
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:
|
||||
val = super().__getitem__(key)
|
||||
except KeyError:
|
||||
@ -87,6 +111,10 @@ class ColorDict(dict):
|
||||
return val
|
||||
|
||||
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:
|
||||
return super().__getitem__(key)
|
||||
except KeyError:
|
||||
@ -94,12 +122,16 @@ class ColorDict(dict):
|
||||
|
||||
|
||||
class Config(ConfigParser):
|
||||
"""Our own ConfigParser"""
|
||||
"""Our own ConfigParser subclass."""
|
||||
|
||||
configdir = None
|
||||
FNAME = 'config'
|
||||
|
||||
def __init__(self, configdir):
|
||||
"""configdir: directory to store the config in"""
|
||||
"""Config constructor.
|
||||
|
||||
configdir -- directory to store the config in.
|
||||
"""
|
||||
super().__init__()
|
||||
self.optionxform = lambda opt: opt # be case-insensitive
|
||||
self.configdir = configdir
|
||||
@ -113,6 +145,7 @@ class Config(ConfigParser):
|
||||
self.read(self.configfile)
|
||||
|
||||
def init_config(self):
|
||||
"""Initialize Config from default_config and save it."""
|
||||
logging.info("Initializing default config.")
|
||||
if self.configdir is None:
|
||||
self.read_dict(default_config)
|
||||
@ -126,6 +159,7 @@ class Config(ConfigParser):
|
||||
cp.write(f)
|
||||
|
||||
def save(self):
|
||||
"""Save the config file."""
|
||||
if self.configdir is None:
|
||||
return
|
||||
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
|
||||
|
||||
from PyQt5.QtWidgets import QShortcut
|
||||
@ -11,7 +18,12 @@ from qutebrowser.widgets.tabbar import 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_load_started = pyqtSignal() # Current tab started loading
|
||||
@ -33,7 +45,10 @@ class TabbedBrowser(TabWidget):
|
||||
space.activated.connect(self.space_scroll)
|
||||
|
||||
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)
|
||||
tab = BrowserTab(self)
|
||||
tab.openurl(url)
|
||||
@ -60,16 +75,26 @@ class TabbedBrowser(TabWidget):
|
||||
tab.open_tab.connect(self.tabopen)
|
||||
|
||||
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)
|
||||
|
||||
def undo_close(self):
|
||||
"""Undos closing a tab"""
|
||||
"""Undo closing a tab.
|
||||
|
||||
Command handler for :undo.
|
||||
"""
|
||||
if self._url_stack:
|
||||
self.tabopen(self._url_stack.pop())
|
||||
|
||||
def cur_close(self):
|
||||
"""Closes the current tab"""
|
||||
"""Close the current tab.
|
||||
|
||||
Command handler for :close.
|
||||
"""
|
||||
if self.count() > 1:
|
||||
idx = self.currentIndex()
|
||||
tab = self.currentWidget()
|
||||
@ -81,32 +106,50 @@ class TabbedBrowser(TabWidget):
|
||||
pass
|
||||
|
||||
def cur_reload(self):
|
||||
"""Reloads the current tab"""
|
||||
"""Reload the current tab.
|
||||
|
||||
Command handler for :reload.
|
||||
"""
|
||||
self.currentWidget().reload()
|
||||
|
||||
def cur_stop(self):
|
||||
"""Stops loading in the current tab"""
|
||||
"""Stop loading in the current tab.
|
||||
|
||||
Command handler for :stop.
|
||||
"""
|
||||
self.currentWidget().stop()
|
||||
|
||||
def cur_print(self):
|
||||
"""Prints the current tab"""
|
||||
"""Print the current tab.
|
||||
|
||||
Command handler for :print.
|
||||
"""
|
||||
# FIXME that does not what I expect
|
||||
preview = QPrintPreviewDialog()
|
||||
preview.paintRequested.connect(self.currentWidget().print)
|
||||
preview.exec_()
|
||||
|
||||
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
|
||||
self.currentWidget().back()
|
||||
|
||||
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
|
||||
self.currentWidget().forward()
|
||||
|
||||
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:
|
||||
count = 1
|
||||
dx = int(count) * int(dx)
|
||||
@ -114,18 +157,23 @@ class TabbedBrowser(TabWidget):
|
||||
self.currentWidget().page().mainFrame().scroll(dx, dy)
|
||||
|
||||
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.
|
||||
|
||||
Command handler for :scroll_perc_x.
|
||||
"""
|
||||
self._cur_scroll_percent(perc, count, Qt.Horizontal)
|
||||
|
||||
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.
|
||||
|
||||
Command handler for :scroll_perc_y
|
||||
"""
|
||||
self._cur_scroll_percent(perc, count, Qt.Vertical)
|
||||
|
||||
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:
|
||||
perc = 100
|
||||
elif perc is None:
|
||||
@ -138,15 +186,11 @@ class TabbedBrowser(TabWidget):
|
||||
return
|
||||
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):
|
||||
"""Switches to the previous tab"""
|
||||
"""Switch to the previous tab.
|
||||
|
||||
Command handler for :tabprev.
|
||||
"""
|
||||
idx = self.currentIndex()
|
||||
if idx > 0:
|
||||
self.setCurrentIndex(idx - 1)
|
||||
@ -155,7 +199,10 @@ class TabbedBrowser(TabWidget):
|
||||
pass
|
||||
|
||||
def switch_next(self):
|
||||
"""Switches to the next tab"""
|
||||
"""Switch to the next tab.
|
||||
|
||||
Command handler for :tabnext.
|
||||
"""
|
||||
idx = self.currentIndex()
|
||||
if idx < self.count() - 1:
|
||||
self.setCurrentIndex(idx + 1)
|
||||
@ -163,37 +210,69 @@ class TabbedBrowser(TabWidget):
|
||||
# FIXME
|
||||
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):
|
||||
"""Extend TabWidget (QWidget)'s keyPressEvent to emit a signal."""
|
||||
self.keypress.emit(e)
|
||||
super().keyPressEvent(e)
|
||||
|
||||
def _titleChanged_handler(self, text):
|
||||
"""Set the title of a tab.
|
||||
|
||||
Slot for the titleChanged signal of any tab.
|
||||
"""
|
||||
if text:
|
||||
self.setTabText(self.indexOf(self.sender()), text)
|
||||
|
||||
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()
|
||||
self.setTabText(self.indexOf(s), s.url().toString())
|
||||
|
||||
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.
|
||||
|
||||
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(
|
||||
signal.signal, ','.join([str(e) for e in args]))
|
||||
if self.currentWidget() == self.sender():
|
||||
logging.debug('{} - emitting'.format(dbgstr))
|
||||
signal.emit(*args)
|
||||
return signal.emit(*args)
|
||||
else:
|
||||
logging.debug('{} - ignoring'.format(dbgstr))
|
||||
|
||||
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)
|
||||
self.cur_progress.emit(tab.progress)
|
||||
|
||||
def _scroll_pos_changed_handler(self, x, y):
|
||||
"""Gets the new position from a BrowserTab. If it's the current tab, it
|
||||
calculates the percentage and emits cur_scroll_perc_changed.
|
||||
"""Get the new position from a BrowserTab. If it's the current tab,
|
||||
calculate the percentage and emits cur_scroll_perc_changed.
|
||||
|
||||
Slot for the scroll_pos_changed signal of any tab.
|
||||
"""
|
||||
sender = self.sender()
|
||||
if sender != self.currentWidget():
|
||||
@ -207,12 +286,15 @@ class TabbedBrowser(TabWidget):
|
||||
|
||||
|
||||
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
|
||||
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')
|
||||
_scroll_pos = (-1, -1)
|
||||
_open_new_tab = False # open new tab for the next action
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
@ -224,16 +306,32 @@ class BrowserTab(QWebView):
|
||||
self.show()
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
else:
|
||||
self.openurl(url)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
be requested when scrolling, and if the scroll position actually
|
||||
changed, we emit a signal.
|
||||
|
||||
watched -- The watched Qt object.
|
||||
e -- The new event.
|
||||
"""
|
||||
if watched == self and e.type() == QEvent.Paint:
|
||||
frame = self.page().mainFrame()
|
||||
@ -254,10 +355,16 @@ class BrowserTab(QWebView):
|
||||
return super().eventFilter(watched, e)
|
||||
|
||||
def event(self, e):
|
||||
"""Another hack to see when a link was pressed with the middle
|
||||
button
|
||||
"""Check if a link was clicked with the middle button or Ctrl.
|
||||
|
||||
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]:
|
||||
self.open_new_tab = (e.button() == Qt.MidButton or
|
||||
e.modifiers() & Qt.ControlModifier)
|
||||
self._open_new_tab = (e.button() == Qt.MidButton or
|
||||
e.modifiers() & Qt.ControlModifier)
|
||||
return super().event(e)
|
||||
|
@ -157,27 +157,41 @@ class CompletionView(QTreeView):
|
||||
self.show()
|
||||
|
||||
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:
|
||||
# No completion running at the moment, ignore keypress
|
||||
return
|
||||
idx = self._next_idx(shift)
|
||||
self.ignore_next = True
|
||||
self.selectionModel().setCurrentIndex(
|
||||
idx, QItemSelectionModel.ClearAndSelect)
|
||||
data = self.model.data(idx)
|
||||
if data is not None:
|
||||
self.ignore_next = True
|
||||
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()
|
||||
if not idx.isValid():
|
||||
# No item selected yet
|
||||
return self.model.first_item()
|
||||
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
|
||||
if not idx.isValid() and shift:
|
||||
if not idx.isValid() and upwards:
|
||||
return self.model.last_item()
|
||||
elif not idx.isValid() and not shift:
|
||||
elif not idx.isValid() and not upwards:
|
||||
return self.model.first_item()
|
||||
elif idx.parent().isValid():
|
||||
# Item is a real item, not a category header -> success
|
||||
@ -185,11 +199,21 @@ class CompletionView(QTreeView):
|
||||
|
||||
|
||||
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
|
||||
style = None
|
||||
painter = None
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
"""Overrides the QStyledItemDelegate paint function."""
|
||||
painter.save()
|
||||
|
||||
self.painter = painter
|
||||
@ -205,10 +229,12 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
painter.restore()
|
||||
|
||||
def _draw_background(self):
|
||||
"""Draw the background of an ItemViewItem"""
|
||||
self.style.drawPrimitive(self.style.PE_PanelItemViewItem, self.opt,
|
||||
self.painter, self.opt.widget)
|
||||
|
||||
def _draw_icon(self):
|
||||
"""Draw the icon of an ItemViewItem"""
|
||||
icon_rect = self.style.subElementRect(
|
||||
self.style.SE_ItemViewItemDecoration, self.opt, self.opt.widget)
|
||||
|
||||
@ -222,6 +248,13 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
self.opt.decorationAlignment, mode, state)
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
@ -258,6 +291,11 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
self.painter.restore()
|
||||
|
||||
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
|
||||
# qcommonstyle.cpp:viewItemDrawText
|
||||
clip = QRectF(0, 0, text_rect.width(), text_rect.height())
|
||||
@ -298,6 +336,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
doc.drawContents(self.painter, clip)
|
||||
|
||||
def _draw_focus_rect(self):
|
||||
"""Draws the focus rectangle of an ItemViewItem"""
|
||||
state = self.opt.state
|
||||
if not state & QStyle.State_HasFocus:
|
||||
return
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""The main window of QuteBrowser."""
|
||||
|
||||
from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QWidget
|
||||
|
||||
from qutebrowser.widgets.statusbar import StatusBar
|
||||
@ -6,7 +8,11 @@ from qutebrowser.widgets.completion import CompletionView
|
||||
|
||||
|
||||
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
|
||||
vbox = None
|
||||
tabs = None
|
||||
|
@ -69,5 +69,9 @@ class StatusBar(QWidget):
|
||||
self.txt.error = ''
|
||||
|
||||
def resizeEvent(self, e):
|
||||
"""Override resizeEvent of QWidget to emit a resized signal afterwards.
|
||||
|
||||
e -- The QResizeEvent.
|
||||
"""
|
||||
super().resizeEvent(e)
|
||||
self.resized.emit(self.geometry())
|
||||
|
@ -1,3 +1,4 @@
|
||||
"""The commandline part of the statusbar."""
|
||||
import logging
|
||||
|
||||
from PyQt5.QtWidgets import QLineEdit, QShortcut
|
||||
@ -6,7 +7,7 @@ from PyQt5.QtGui import QValidator, QKeySequence
|
||||
|
||||
|
||||
class Command(QLineEdit):
|
||||
"""The commandline part of the statusbar"""
|
||||
"""The commandline part of the statusbar."""
|
||||
# Emitted when a command is triggered by the user
|
||||
got_cmd = pyqtSignal(str)
|
||||
statusbar = None # The status bar object
|
||||
@ -30,18 +31,20 @@ class Command(QLineEdit):
|
||||
self.returnPressed.connect(self.process_cmd)
|
||||
self.textEdited.connect(self._histbrowse_stop)
|
||||
|
||||
for (key, handler) in [(Qt.Key_Escape, self.esc_pressed),
|
||||
(Qt.Key_Up, self.key_up_handler),
|
||||
(Qt.Key_Down, self.key_down_handler),
|
||||
(Qt.Key_Tab | Qt.SHIFT, self.key_stab_handler),
|
||||
(Qt.Key_Tab, self.key_tab_handler)]:
|
||||
for (key, handler) in [
|
||||
(Qt.Key_Escape, self.esc_pressed),
|
||||
(Qt.Key_Up, self.key_up_handler),
|
||||
(Qt.Key_Down, self.key_down_handler),
|
||||
(Qt.Key_Tab | Qt.SHIFT, lambda: self.tab_pressed.emit(True)),
|
||||
(Qt.Key_Tab, lambda: self.tab_pressed.emit(False))
|
||||
]:
|
||||
sc = QShortcut(self)
|
||||
sc.setKey(QKeySequence(key))
|
||||
sc.setContext(Qt.WidgetWithChildrenShortcut)
|
||||
sc.activated.connect(handler)
|
||||
|
||||
def process_cmd(self):
|
||||
"""Handle the command in the status bar"""
|
||||
"""Handle the command in the status bar."""
|
||||
self._histbrowse_stop()
|
||||
text = self.text().lstrip(':')
|
||||
if not self.history or text != self.history[-1]:
|
||||
@ -50,18 +53,18 @@ class Command(QLineEdit):
|
||||
self.got_cmd.emit(text)
|
||||
|
||||
def set_cmd(self, text):
|
||||
"""Preset the statusbar to some text"""
|
||||
"""Preset the statusbar to some text."""
|
||||
self.setText(':' + text)
|
||||
self.setFocus()
|
||||
|
||||
def append_cmd(self, text):
|
||||
"""Append text to the commandline"""
|
||||
"""Append text to the commandline."""
|
||||
# FIXME do the right thing here
|
||||
self.setText(':' + text)
|
||||
self.setFocus()
|
||||
|
||||
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,
|
||||
Qt.BacktabFocusReason, Qt.OtherFocusReason]:
|
||||
self.setText('')
|
||||
@ -70,11 +73,18 @@ class Command(QLineEdit):
|
||||
super().focusOutEvent(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()
|
||||
super().focusInEvent(e)
|
||||
|
||||
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(':')
|
||||
logging.debug('Preset text: "{}"'.format(pre))
|
||||
if pre:
|
||||
@ -84,9 +94,11 @@ class Command(QLineEdit):
|
||||
self._histpos = len(self._tmphist) - 1
|
||||
|
||||
def _histbrowse_stop(self):
|
||||
"""Stop browsing the history."""
|
||||
self._histpos = None
|
||||
|
||||
def key_up_handler(self):
|
||||
"""Handle Up presses (go back in history)."""
|
||||
logging.debug("history up [pre]: pos {}".format(self._histpos))
|
||||
if self._histpos is None:
|
||||
self._histbrowse_start()
|
||||
@ -101,6 +113,7 @@ class Command(QLineEdit):
|
||||
self.set_cmd(self._tmphist[self._histpos])
|
||||
|
||||
def key_down_handler(self):
|
||||
"""Handle Down presses (go forward in history)."""
|
||||
logging.debug("history up [pre]: pos {}".format(self._histpos,
|
||||
self._tmphist, len(self._tmphist), self._histpos))
|
||||
if (self._histpos is None or
|
||||
@ -112,16 +125,20 @@ class Command(QLineEdit):
|
||||
self._tmphist, len(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):
|
||||
"""Validator to prevent the : from getting deleted"""
|
||||
|
||||
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(':'):
|
||||
return (QValidator.Acceptable, string, pos)
|
||||
else:
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""Widget to show the percentage of the page load in the statusbar."""
|
||||
|
||||
import qutebrowser.utils.config as config
|
||||
|
||||
from PyQt5.QtWidgets import QProgressBar, QSizePolicy
|
||||
@ -5,7 +7,7 @@ from PyQt5.QtCore import QSize
|
||||
|
||||
|
||||
class Progress(QProgressBar):
|
||||
""" The progress bar part of the status bar"""
|
||||
"""The progress bar part of the status bar."""
|
||||
statusbar = None
|
||||
color = None
|
||||
_stylesheet = """
|
||||
@ -30,21 +32,28 @@ class Progress(QProgressBar):
|
||||
self.hide()
|
||||
|
||||
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)
|
||||
if name == 'color' and value is not None:
|
||||
config.colordict['statusbar.progress.bg.__cur__'] = value
|
||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
||||
|
||||
def minimumSizeHint(self):
|
||||
"""Return the size of the progress widget."""
|
||||
status_size = self.statusbar.size()
|
||||
return QSize(100, status_size.height())
|
||||
|
||||
def sizeHint(self):
|
||||
|
||||
"""Return the size of the progress widget.
|
||||
|
||||
Simply copied from minimumSizeHint because the SizePolicy is fixed.
|
||||
"""
|
||||
|
||||
return self.minimumSizeHint()
|
||||
|
||||
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?
|
||||
if prog == 100:
|
||||
self.setValue(prog)
|
||||
@ -56,6 +65,12 @@ class Progress(QProgressBar):
|
||||
self.show()
|
||||
|
||||
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:
|
||||
self.hide()
|
||||
else:
|
||||
|
@ -1,9 +1,16 @@
|
||||
"""The text part of the statusbar."""
|
||||
|
||||
import logging
|
||||
from PyQt5.QtWidgets import 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 = ''
|
||||
error = ''
|
||||
text = ''
|
||||
@ -18,11 +25,11 @@ class Text(QLabel):
|
||||
self.update()
|
||||
|
||||
def set_keystring(self, s):
|
||||
"""Setter to be used as a Qt slot"""
|
||||
"""Setter to be used as a Qt slot."""
|
||||
self.keystring = s
|
||||
|
||||
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
|
||||
if y == 0:
|
||||
self.scrollperc = '[top]'
|
||||
@ -32,10 +39,11 @@ class Text(QLabel):
|
||||
self.scrollperc = '[{}%]'.format(y)
|
||||
|
||||
def set_text(self, text):
|
||||
"""Setter to be used as a Qt slot."""
|
||||
logging.debug('Setting text to "{}"'.format(text))
|
||||
self.text = text
|
||||
|
||||
def update(self):
|
||||
"""Update the text displayed"""
|
||||
"""Update the text displayed."""
|
||||
self.setText(' '.join([self.keystring, self.error, self.text,
|
||||
self.scrollperc]))
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""The tab widget used for TabbedBrowser from browser.py."""
|
||||
|
||||
from PyQt5.QtWidgets import QTabWidget
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
@ -5,7 +7,7 @@ import qutebrowser.utils.config as config
|
||||
|
||||
|
||||
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
|
||||
# background-color: grey for QTabBar...
|
||||
|
Loading…
Reference in New Issue
Block a user