Merge branch 'master' of ssh://lupin/qutebrowser
This commit is contained in:
commit
133d720de5
1
TODO
1
TODO
@ -38,7 +38,6 @@ Internationalization
|
|||||||
Marks
|
Marks
|
||||||
show infos in statusline, temporary/longer
|
show infos in statusline, temporary/longer
|
||||||
set settings/colors/bindings via commandline
|
set settings/colors/bindings via commandline
|
||||||
write default config with comments
|
|
||||||
more completions (URLs, ...)
|
more completions (URLs, ...)
|
||||||
SSL handling
|
SSL handling
|
||||||
|
|
||||||
|
@ -22,8 +22,10 @@ import sys
|
|||||||
import logging
|
import logging
|
||||||
import functools
|
import functools
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import configparser
|
||||||
from signal import signal, SIGINT
|
from signal import signal, SIGINT
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
# Print a nice traceback on segfault -- only available on Python 3.3+, but if
|
# Print a nice traceback on segfault -- only available on Python 3.3+, but if
|
||||||
# it's unavailable, it doesn't matter much.
|
# it's unavailable, it doesn't matter much.
|
||||||
@ -62,39 +64,43 @@ class QuteBrowser(QApplication):
|
|||||||
>>> app = QuteBrowser()
|
>>> app = QuteBrowser()
|
||||||
>>> sys.exit(app.exec_())
|
>>> sys.exit(app.exec_())
|
||||||
|
|
||||||
"""
|
Attributes:
|
||||||
|
mainwindow: The MainWindow QWidget.
|
||||||
|
commandparser: The main CommandParser instance.
|
||||||
|
keyparser: The main KeyParser instance.
|
||||||
|
searchparser: The main SearchParser instance.
|
||||||
|
_dirs: AppDirs instance for config/cache directories.
|
||||||
|
_args: ArgumentParser instance.
|
||||||
|
_timers: List of used QTimers so they don't get GCed.
|
||||||
|
_shutting_down: True if we're currently shutting down.
|
||||||
|
_quit_status: The current quitting status.
|
||||||
|
|
||||||
dirs = None # AppDirs - config/cache directories
|
"""
|
||||||
config = None # Config(Parser) object
|
|
||||||
mainwindow = None
|
|
||||||
commandparser = None
|
|
||||||
keyparser = None
|
|
||||||
args = None # ArgumentParser
|
|
||||||
timers = None
|
|
||||||
shutting_down = False
|
|
||||||
_quit_status = None
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(sys.argv)
|
super().__init__(sys.argv)
|
||||||
self._quit_status = {}
|
self._quit_status = {}
|
||||||
|
self._timers = []
|
||||||
|
self._shutting_down = False
|
||||||
|
|
||||||
sys.excepthook = self._exception_hook
|
sys.excepthook = self._exception_hook
|
||||||
|
|
||||||
self._parseopts()
|
self._args = self._parseopts()
|
||||||
self._initlog()
|
self._initlog()
|
||||||
self._initmisc()
|
self._initmisc()
|
||||||
|
|
||||||
self.dirs = AppDirs('qutebrowser')
|
self._dirs = AppDirs('qutebrowser')
|
||||||
if self.args.confdir is None:
|
if self._args.confdir is None:
|
||||||
confdir = self.dirs.user_config_dir
|
confdir = self._dirs.user_config_dir
|
||||||
elif self.args.confdir == '':
|
elif self._args.confdir == '':
|
||||||
confdir = None
|
confdir = None
|
||||||
else:
|
else:
|
||||||
confdir = self.args.confdir
|
confdir = self._args.confdir
|
||||||
config.init(confdir)
|
config.init(confdir)
|
||||||
|
|
||||||
self.commandparser = cmdutils.CommandParser()
|
self.commandparser = cmdutils.CommandParser()
|
||||||
self.searchparser = cmdutils.SearchParser()
|
self.searchparser = cmdutils.SearchParser()
|
||||||
self.keyparser = KeyParser(self.mainwindow)
|
self.keyparser = KeyParser(self)
|
||||||
self._init_cmds()
|
self._init_cmds()
|
||||||
self.mainwindow = MainWindow()
|
self.mainwindow = MainWindow()
|
||||||
|
|
||||||
@ -102,9 +108,9 @@ class QuteBrowser(QApplication):
|
|||||||
self.lastWindowClosed.connect(self.shutdown)
|
self.lastWindowClosed.connect(self.shutdown)
|
||||||
self.mainwindow.tabs.keypress.connect(self.keyparser.handle)
|
self.mainwindow.tabs.keypress.connect(self.keyparser.handle)
|
||||||
self.keyparser.set_cmd_text.connect(
|
self.keyparser.set_cmd_text.connect(
|
||||||
self.mainwindow.status.cmd.on_set_cmd_text)
|
self.mainwindow.status.cmd.set_cmd_text)
|
||||||
self.mainwindow.tabs.set_cmd_text.connect(
|
self.mainwindow.tabs.set_cmd_text.connect(
|
||||||
self.mainwindow.status.cmd.on_set_cmd_text)
|
self.mainwindow.status.cmd.set_cmd_text)
|
||||||
self.mainwindow.tabs.quit.connect(self.shutdown)
|
self.mainwindow.tabs.quit.connect(self.shutdown)
|
||||||
self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.run)
|
self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.run)
|
||||||
self.mainwindow.status.cmd.got_search.connect(self.searchparser.search)
|
self.mainwindow.status.cmd.got_search.connect(self.searchparser.search)
|
||||||
@ -123,7 +129,55 @@ class QuteBrowser(QApplication):
|
|||||||
self.mainwindow.show()
|
self.mainwindow.show()
|
||||||
self._python_hacks()
|
self._python_hacks()
|
||||||
timer = QTimer.singleShot(0, self._process_init_args)
|
timer = QTimer.singleShot(0, self._process_init_args)
|
||||||
self.timers.append(timer)
|
self._timers.append(timer)
|
||||||
|
|
||||||
|
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')
|
||||||
|
parser.add_argument('-c', '--confdir', help='Set config directory '
|
||||||
|
'(empty for no config storage)')
|
||||||
|
parser.add_argument('-d', '--debug', help='Turn on debugging options.',
|
||||||
|
action='store_true')
|
||||||
|
parser.add_argument('command', nargs='*', help='Commands to execute '
|
||||||
|
'on startup.', metavar=':command')
|
||||||
|
# URLs will actually be in command
|
||||||
|
parser.add_argument('url', nargs='*', help='URLs to open on startup.')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def _initlog(self):
|
||||||
|
"""Initialisation of the logging output."""
|
||||||
|
loglevel = 'debug' if self._args.debug else self._args.loglevel
|
||||||
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||||
|
if not isinstance(numeric_level, int):
|
||||||
|
raise ValueError('Invalid log level: {}'.format(loglevel))
|
||||||
|
logging.basicConfig(
|
||||||
|
level=numeric_level,
|
||||||
|
format='%(asctime)s [%(levelname)s] '
|
||||||
|
'[%(module)s:%(funcName)s:%(lineno)s] %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
def _initmisc(self):
|
||||||
|
"""Initialize misc things."""
|
||||||
|
if self._args.debug:
|
||||||
|
os.environ['QT_FATAL_WARNINGS'] = '1'
|
||||||
|
self.setApplicationName("qutebrowser")
|
||||||
|
self.setApplicationVersion(qutebrowser.__version__)
|
||||||
|
|
||||||
|
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)
|
||||||
|
try:
|
||||||
|
self.keyparser.from_config_sect(config.config['keybind'])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def _process_init_args(self):
|
def _process_init_args(self):
|
||||||
"""Process initial positional args.
|
"""Process initial positional args.
|
||||||
@ -137,7 +191,7 @@ class QuteBrowser(QApplication):
|
|||||||
QEventLoop.ExcludeSocketNotifiers)
|
QEventLoop.ExcludeSocketNotifiers)
|
||||||
opened_urls = False
|
opened_urls = False
|
||||||
|
|
||||||
for e in self.args.command:
|
for e in self._args.command:
|
||||||
if e.startswith(':'):
|
if e.startswith(':'):
|
||||||
logging.debug('Startup cmd {}'.format(e))
|
logging.debug('Startup cmd {}'.format(e))
|
||||||
self.commandparser.run(e.lstrip(':'))
|
self.commandparser.run(e.lstrip(':'))
|
||||||
@ -152,6 +206,20 @@ class QuteBrowser(QApplication):
|
|||||||
for url in config.config.get('general', 'startpage').split(','):
|
for url in config.config.get('general', 'startpage').split(','):
|
||||||
self.mainwindow.tabs.tabopen(url)
|
self.mainwindow.tabs.tabopen(url)
|
||||||
|
|
||||||
|
def _python_hacks(self):
|
||||||
|
"""Get around some PyQt-oddities by evil hacks.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
signal(SIGINT, lambda *args: self.exit(128 + SIGINT))
|
||||||
|
timer = QTimer()
|
||||||
|
timer.start(500)
|
||||||
|
timer.timeout.connect(lambda: None)
|
||||||
|
self._timers.append(timer)
|
||||||
|
|
||||||
def _recover_pages(self):
|
def _recover_pages(self):
|
||||||
"""Try to recover all open pages.
|
"""Try to recover all open pages.
|
||||||
|
|
||||||
@ -173,6 +241,15 @@ class QuteBrowser(QApplication):
|
|||||||
pass
|
pass
|
||||||
return pages
|
return pages
|
||||||
|
|
||||||
|
def _save_geometry(self):
|
||||||
|
"""Save the window geometry to the state config."""
|
||||||
|
geom = b64encode(bytes(self.mainwindow.saveGeometry())).decode('ASCII')
|
||||||
|
try:
|
||||||
|
config.state.add_section('geometry')
|
||||||
|
except configparser.DuplicateSectionError:
|
||||||
|
pass
|
||||||
|
config.state['geometry']['mainwindow'] = geom
|
||||||
|
|
||||||
def _exception_hook(self, exctype, excvalue, tb):
|
def _exception_hook(self, exctype, excvalue, tb):
|
||||||
"""Handle uncaught python exceptions.
|
"""Handle uncaught python exceptions.
|
||||||
|
|
||||||
@ -237,97 +314,7 @@ class QuteBrowser(QApplication):
|
|||||||
logging.debug("maybe_quit called from {}, quit status {}".format(
|
logging.debug("maybe_quit called from {}, quit status {}".format(
|
||||||
sender, self._quit_status))
|
sender, self._quit_status))
|
||||||
if all(self._quit_status.values()):
|
if all(self._quit_status.values()):
|
||||||
self.quit()
|
logging.debug("maybe_quit quitting.")
|
||||||
|
|
||||||
def _python_hacks(self):
|
|
||||||
"""Get around some PyQt-oddities by evil hacks.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
signal(SIGINT, lambda *args: self.exit(128 + SIGINT))
|
|
||||||
timer = QTimer()
|
|
||||||
timer.start(500)
|
|
||||||
timer.timeout.connect(lambda: None)
|
|
||||||
self.timers.append(timer)
|
|
||||||
|
|
||||||
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')
|
|
||||||
parser.add_argument('-c', '--confdir', help='Set config directory '
|
|
||||||
'(empty for no config storage)')
|
|
||||||
parser.add_argument('-d', '--debug', help='Turn on debugging options.',
|
|
||||||
action='store_true')
|
|
||||||
parser.add_argument('command', nargs='*', help='Commands to execute '
|
|
||||||
'on startup.', metavar=':command')
|
|
||||||
# URLs will actually be in command
|
|
||||||
parser.add_argument('url', nargs='*', help='URLs to open on startup.')
|
|
||||||
self.args = parser.parse_args()
|
|
||||||
|
|
||||||
def _initlog(self):
|
|
||||||
"""Initialisation of the logging output."""
|
|
||||||
loglevel = 'debug' if self.args.debug else self.args.loglevel
|
|
||||||
numeric_level = getattr(logging, loglevel.upper(), None)
|
|
||||||
if not isinstance(numeric_level, int):
|
|
||||||
raise ValueError('Invalid log level: {}'.format(loglevel))
|
|
||||||
logging.basicConfig(
|
|
||||||
level=numeric_level,
|
|
||||||
format='%(asctime)s [%(levelname)s] '
|
|
||||||
'[%(module)s:%(funcName)s:%(lineno)s] %(message)s',
|
|
||||||
datefmt='%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
def _initmisc(self):
|
|
||||||
"""Initialize misc things."""
|
|
||||||
if self.args.debug:
|
|
||||||
os.environ['QT_FATAL_WARNINGS'] = '1'
|
|
||||||
self.setApplicationName("qutebrowser")
|
|
||||||
self.setApplicationVersion(qutebrowser.__version__)
|
|
||||||
self.timers = []
|
|
||||||
|
|
||||||
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)
|
|
||||||
try:
|
|
||||||
self.keyparser.from_config_sect(config.config['keybind'])
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pyqtSlot()
|
|
||||||
def shutdown(self, do_quit=True):
|
|
||||||
"""Try to shutdown everything cleanly.
|
|
||||||
|
|
||||||
For some reason lastWindowClosing sometimes seem to get emitted twice,
|
|
||||||
so we make sure we only run once here.
|
|
||||||
|
|
||||||
quit -- Whether to quit after shutting down.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.shutting_down:
|
|
||||||
return
|
|
||||||
self.shutting_down = True
|
|
||||||
logging.debug("Shutting down... (do_quit={})".format(do_quit))
|
|
||||||
if config.config is not None:
|
|
||||||
config.config.save()
|
|
||||||
try:
|
|
||||||
if do_quit:
|
|
||||||
self.mainwindow.tabs.shutdown_complete.connect(self.quit)
|
|
||||||
else:
|
|
||||||
self.mainwindow.tabs.shutdown_complete.connect(
|
|
||||||
functools.partial(self._maybe_quit, 'shutdown'))
|
|
||||||
self.mainwindow.tabs.shutdown()
|
|
||||||
except AttributeError: # mainwindow or tabs could still be None
|
|
||||||
logging.debug("No mainwindow/tabs to shut down.")
|
|
||||||
if do_quit:
|
|
||||||
self.quit()
|
self.quit()
|
||||||
|
|
||||||
@pyqtSlot(tuple)
|
@pyqtSlot(tuple)
|
||||||
@ -345,32 +332,33 @@ class QuteBrowser(QApplication):
|
|||||||
(count, argv) = tpl
|
(count, argv) = tpl
|
||||||
cmd = argv[0]
|
cmd = argv[0]
|
||||||
args = argv[1:]
|
args = argv[1:]
|
||||||
|
browser = self.mainwindow.tabs
|
||||||
|
|
||||||
handlers = {
|
handlers = {
|
||||||
'open': self.mainwindow.tabs.openurl,
|
'open': browser.openurl,
|
||||||
'opencur': self.mainwindow.tabs.opencur,
|
'opencur': browser.opencur,
|
||||||
'tabopen': self.mainwindow.tabs.tabopen,
|
'tabopen': browser.tabopen,
|
||||||
'tabopencur': self.mainwindow.tabs.tabopencur,
|
'tabopencur': browser.tabopencur,
|
||||||
'quit': self.shutdown,
|
'quit': self.shutdown,
|
||||||
'tabclose': self.mainwindow.tabs.cur_close,
|
'tabclose': browser.cur_close,
|
||||||
'tabprev': self.mainwindow.tabs.switch_prev,
|
'tabprev': browser.switch_prev,
|
||||||
'tabnext': self.mainwindow.tabs.switch_next,
|
'tabnext': browser.switch_next,
|
||||||
'reload': self.mainwindow.tabs.cur_reload,
|
'reload': browser.cur_reload,
|
||||||
'stop': self.mainwindow.tabs.cur_stop,
|
'stop': browser.cur_stop,
|
||||||
'back': self.mainwindow.tabs.cur_back,
|
'back': browser.cur_back,
|
||||||
'forward': self.mainwindow.tabs.cur_forward,
|
'forward': browser.cur_forward,
|
||||||
'print': self.mainwindow.tabs.cur_print,
|
'print': browser.cur_print,
|
||||||
'scroll': self.mainwindow.tabs.cur_scroll,
|
'scroll': browser.cur_scroll,
|
||||||
'scroll_page': self.mainwindow.tabs.cur_scroll_page,
|
'scroll_page': browser.cur_scroll_page,
|
||||||
'scroll_perc_x': self.mainwindow.tabs.cur_scroll_percent_x,
|
'scroll_perc_x': browser.cur_scroll_percent_x,
|
||||||
'scroll_perc_y': self.mainwindow.tabs.cur_scroll_percent_y,
|
'scroll_perc_y': browser.cur_scroll_percent_y,
|
||||||
'undo': self.mainwindow.tabs.undo_close,
|
'undo': browser.undo_close,
|
||||||
'pyeval': self.pyeval,
|
'pyeval': self.pyeval,
|
||||||
'nextsearch': self.searchparser.nextsearch,
|
'nextsearch': self.searchparser.nextsearch,
|
||||||
'yank': self.mainwindow.tabs.cur_yank,
|
'yank': browser.cur_yank,
|
||||||
'yanktitle': self.mainwindow.tabs.cur_yank_title,
|
'yanktitle': browser.cur_yank_title,
|
||||||
'paste': self.mainwindow.tabs.paste,
|
'paste': browser.paste,
|
||||||
'tabpaste': self.mainwindow.tabs.tabpaste,
|
'tabpaste': browser.tabpaste,
|
||||||
'crash': self.crash,
|
'crash': self.crash,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,3 +392,49 @@ class QuteBrowser(QApplication):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def shutdown(self, do_quit=True):
|
||||||
|
"""Try to shutdown everything cleanly.
|
||||||
|
|
||||||
|
For some reason lastWindowClosing sometimes seem to get emitted twice,
|
||||||
|
so we make sure we only run once here.
|
||||||
|
|
||||||
|
quit -- Whether to quit after shutting down.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self._shutting_down:
|
||||||
|
return
|
||||||
|
self._shutting_down = True
|
||||||
|
logging.debug("Shutting down... (do_quit={})".format(do_quit))
|
||||||
|
try:
|
||||||
|
config.config.save()
|
||||||
|
except AttributeError:
|
||||||
|
logging.exception("Could not save config.")
|
||||||
|
try:
|
||||||
|
self._save_geometry()
|
||||||
|
config.state.save()
|
||||||
|
except AttributeError:
|
||||||
|
logging.exception("Could not save window geometry.")
|
||||||
|
try:
|
||||||
|
if do_quit:
|
||||||
|
self.mainwindow.tabs.shutdown_complete.connect(
|
||||||
|
self.on_tab_shutdown_complete)
|
||||||
|
else:
|
||||||
|
self.mainwindow.tabs.shutdown_complete.connect(
|
||||||
|
functools.partial(self._maybe_quit, 'shutdown'))
|
||||||
|
self.mainwindow.tabs.shutdown()
|
||||||
|
except AttributeError: # mainwindow or tabs could still be None
|
||||||
|
logging.exception("No mainwindow/tabs to shut down.")
|
||||||
|
if do_quit:
|
||||||
|
self.quit()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def on_tab_shutdown_complete(self):
|
||||||
|
"""Quit application after a shutdown.
|
||||||
|
|
||||||
|
Gets called when all tabs finished shutting down after shutdown().
|
||||||
|
|
||||||
|
"""
|
||||||
|
logging.debug("Shutdown complete, quitting.")
|
||||||
|
self.quit()
|
||||||
|
@ -32,17 +32,24 @@ startchars = ":/?"
|
|||||||
|
|
||||||
class KeyParser(QObject):
|
class KeyParser(QObject):
|
||||||
|
|
||||||
"""Parser for vim-like key sequences."""
|
"""Parser for vim-like key sequences.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
commandparser: Commandparser instance.
|
||||||
|
_keystring: The currently entered key sequence
|
||||||
|
_bindings: Bound keybindings
|
||||||
|
_modifier_bindings: Bound modifier bindings.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
set_cmd_text: Emitted when the statusbar should set a partial command.
|
||||||
|
arg: Text to set.
|
||||||
|
keystring_updated: Emitted when the keystring is updated.
|
||||||
|
arg: New keystring.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
keystring = '' # The currently entered key sequence
|
|
||||||
# Signal emitted when the statusbar should set a partial command
|
|
||||||
set_cmd_text = pyqtSignal(str)
|
set_cmd_text = pyqtSignal(str)
|
||||||
# Signal emitted when the keystring is updated
|
|
||||||
keystring_updated = pyqtSignal(str)
|
keystring_updated = pyqtSignal(str)
|
||||||
# Keybindings
|
|
||||||
bindings = {}
|
|
||||||
modifier_bindings = {}
|
|
||||||
commandparser = None
|
|
||||||
|
|
||||||
MATCH_PARTIAL = 0
|
MATCH_PARTIAL = 0
|
||||||
MATCH_DEFINITIVE = 1
|
MATCH_DEFINITIVE = 1
|
||||||
@ -51,35 +58,9 @@ class KeyParser(QObject):
|
|||||||
def __init__(self, mainwindow):
|
def __init__(self, mainwindow):
|
||||||
super().__init__(mainwindow)
|
super().__init__(mainwindow)
|
||||||
self.commandparser = CommandParser()
|
self.commandparser = CommandParser()
|
||||||
|
self._keystring = ''
|
||||||
def from_config_sect(self, sect):
|
self._bindings = {}
|
||||||
"""Load keybindings from a ConfigParser section.
|
self._modifier_bindings = {}
|
||||||
|
|
||||||
Config format: key = command, e.g.:
|
|
||||||
gg = scrollstart
|
|
||||||
|
|
||||||
"""
|
|
||||||
for (key, cmd) in sect.items():
|
|
||||||
if key.startswith('@') and key.endswith('@'):
|
|
||||||
# normalize keystring
|
|
||||||
keystr = self._normalize_keystr(key.strip('@'))
|
|
||||||
logging.debug('registered mod key: {} -> {}'.format(keystr,
|
|
||||||
cmd))
|
|
||||||
self.modifier_bindings[keystr] = cmd
|
|
||||||
else:
|
|
||||||
logging.debug('registered key: {} -> {}'.format(key, cmd))
|
|
||||||
self.bindings[key] = cmd
|
|
||||||
|
|
||||||
def handle(self, e):
|
|
||||||
"""Handle a new keypress and call the respective handlers.
|
|
||||||
|
|
||||||
e -- the KeyPressEvent from Qt
|
|
||||||
|
|
||||||
"""
|
|
||||||
handled = self._handle_modifier_key(e)
|
|
||||||
if not handled:
|
|
||||||
self._handle_single_key(e)
|
|
||||||
self.keystring_updated.emit(self.keystring)
|
|
||||||
|
|
||||||
def _handle_modifier_key(self, e):
|
def _handle_modifier_key(self, e):
|
||||||
"""Handle a new keypress with modifiers.
|
"""Handle a new keypress with modifiers.
|
||||||
@ -108,7 +89,7 @@ class KeyParser(QObject):
|
|||||||
modstr += s + '+'
|
modstr += s + '+'
|
||||||
keystr = QKeySequence(e.key()).toString()
|
keystr = QKeySequence(e.key()).toString()
|
||||||
try:
|
try:
|
||||||
cmdstr = self.modifier_bindings[modstr + keystr]
|
cmdstr = self._modifier_bindings[modstr + keystr]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logging.debug('No binding found for {}.'.format(modstr + keystr))
|
logging.debug('No binding found for {}.'.format(modstr + keystr))
|
||||||
return True
|
return True
|
||||||
@ -133,15 +114,15 @@ class KeyParser(QObject):
|
|||||||
logging.debug('Ignoring, no text')
|
logging.debug('Ignoring, no text')
|
||||||
return
|
return
|
||||||
|
|
||||||
self.keystring += txt
|
self._keystring += txt
|
||||||
|
|
||||||
if any(self.keystring == c for c in startchars):
|
if any(self._keystring == c for c in startchars):
|
||||||
self.set_cmd_text.emit(self.keystring)
|
self.set_cmd_text.emit(self._keystring)
|
||||||
self.keystring = ''
|
self._keystring = ''
|
||||||
return
|
return
|
||||||
|
|
||||||
(countstr, cmdstr_needle) = re.match(r'^(\d*)(.*)',
|
(countstr, cmdstr_needle) = re.match(r'^(\d*)(.*)',
|
||||||
self.keystring).groups()
|
self._keystring).groups()
|
||||||
|
|
||||||
if not cmdstr_needle:
|
if not cmdstr_needle:
|
||||||
return
|
return
|
||||||
@ -157,16 +138,16 @@ class KeyParser(QObject):
|
|||||||
if match == self.MATCH_DEFINITIVE:
|
if match == self.MATCH_DEFINITIVE:
|
||||||
pass
|
pass
|
||||||
elif match == self.MATCH_PARTIAL:
|
elif match == self.MATCH_PARTIAL:
|
||||||
logging.debug('No match for "{}" (added {})'.format(self.keystring,
|
logging.debug('No match for "{}" (added {})'.format(
|
||||||
txt))
|
self._keystring, txt))
|
||||||
return
|
return
|
||||||
elif match == self.MATCH_NONE:
|
elif match == self.MATCH_NONE:
|
||||||
logging.debug('Giving up with "{}", no matches'.format(
|
logging.debug('Giving up with "{}", no matches'.format(
|
||||||
self.keystring))
|
self._keystring))
|
||||||
self.keystring = ''
|
self._keystring = ''
|
||||||
return
|
return
|
||||||
|
|
||||||
self.keystring = ''
|
self._keystring = ''
|
||||||
count = int(countstr) if countstr else None
|
count = int(countstr) if countstr else None
|
||||||
self._run_or_fill(cmdstr_hay, count=count, ignore_exc=False)
|
self._run_or_fill(cmdstr_hay, count=count, ignore_exc=False)
|
||||||
return
|
return
|
||||||
@ -178,11 +159,11 @@ class KeyParser(QObject):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No definitive match, check if there's a chance of a partial match
|
# No definitive match, check if there's a chance of a partial match
|
||||||
for hay in self.bindings:
|
for hay in self._bindings:
|
||||||
try:
|
try:
|
||||||
if cmdstr_needle[-1] == hay[len(cmdstr_needle) - 1]:
|
if cmdstr_needle[-1] == hay[len(cmdstr_needle) - 1]:
|
||||||
return (self.MATCH_PARTIAL, None)
|
return (self.MATCH_PARTIAL, None)
|
||||||
@ -229,3 +210,32 @@ class KeyParser(QObject):
|
|||||||
cmdstr))
|
cmdstr))
|
||||||
self.set_cmd_text.emit(':{} '.format(cmdstr))
|
self.set_cmd_text.emit(':{} '.format(cmdstr))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def from_config_sect(self, sect):
|
||||||
|
"""Load keybindings from a ConfigParser section.
|
||||||
|
|
||||||
|
Config format: key = command, e.g.:
|
||||||
|
gg = scrollstart
|
||||||
|
|
||||||
|
"""
|
||||||
|
for (key, cmd) in sect.items():
|
||||||
|
if key.startswith('@') and key.endswith('@'):
|
||||||
|
# normalize keystring
|
||||||
|
keystr = self._normalize_keystr(key.strip('@'))
|
||||||
|
logging.debug('registered mod key: {} -> {}'.format(keystr,
|
||||||
|
cmd))
|
||||||
|
self._modifier_bindings[keystr] = cmd
|
||||||
|
else:
|
||||||
|
logging.debug('registered key: {} -> {}'.format(key, cmd))
|
||||||
|
self._bindings[key] = cmd
|
||||||
|
|
||||||
|
def handle(self, e):
|
||||||
|
"""Handle a new keypress and call the respective handlers.
|
||||||
|
|
||||||
|
e -- the KeyPressEvent from Qt
|
||||||
|
|
||||||
|
"""
|
||||||
|
handled = self._handle_modifier_key(e)
|
||||||
|
if not handled:
|
||||||
|
self._handle_single_key(e)
|
||||||
|
self.keystring_updated.emit(self._keystring)
|
||||||
|
@ -30,6 +30,10 @@ class Command(QObject):
|
|||||||
|
|
||||||
See the module documentation for qutebrowser.commands.commands for details.
|
See the module documentation for qutebrowser.commands.commands for details.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
signal: Emitted when the command was executed.
|
||||||
|
arg: A tuple (command, [args])
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# FIXME:
|
# FIXME:
|
||||||
|
@ -49,12 +49,45 @@ def register_all():
|
|||||||
|
|
||||||
class SearchParser(QObject):
|
class SearchParser(QObject):
|
||||||
|
|
||||||
"""Parse qutebrowser searches."""
|
"""Parse qutebrowser searches.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_text: The text from the last search.
|
||||||
|
_flags: The flags from the last search.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
do_search: Emitted when a search should be started.
|
||||||
|
arg 1: Search string.
|
||||||
|
arg 2: Flags to use.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
text = None
|
|
||||||
flags = 0
|
|
||||||
do_search = pyqtSignal(str, 'QWebPage::FindFlags')
|
do_search = pyqtSignal(str, 'QWebPage::FindFlags')
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
self._text = None
|
||||||
|
self._flags = 0
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
def _search(self, text, rev=False):
|
||||||
|
"""Search for a text on the current page.
|
||||||
|
|
||||||
|
text -- The text to search for.
|
||||||
|
rev -- Search direction.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self._text is not None and self._text != text:
|
||||||
|
self.do_search.emit('', 0)
|
||||||
|
self._text = text
|
||||||
|
self._flags = 0
|
||||||
|
if config.config.getboolean('general', 'ignorecase', fallback=True):
|
||||||
|
self._flags |= QWebPage.FindCaseSensitively
|
||||||
|
if config.config.getboolean('general', 'wrapsearch', fallback=True):
|
||||||
|
self._flags |= QWebPage.FindWrapsAroundDocument
|
||||||
|
if rev:
|
||||||
|
self._flags |= QWebPage.FindBackward
|
||||||
|
self.do_search.emit(self._text, self._flags)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def search(self, text):
|
def search(self, text):
|
||||||
"""Search for a text on a website.
|
"""Search for a text on a website.
|
||||||
@ -73,40 +106,33 @@ class SearchParser(QObject):
|
|||||||
"""
|
"""
|
||||||
self._search(text, rev=True)
|
self._search(text, rev=True)
|
||||||
|
|
||||||
def _search(self, text, rev=False):
|
|
||||||
"""Search for a text on the current page.
|
|
||||||
|
|
||||||
text -- The text to search for.
|
|
||||||
rev -- Search direction.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.text != text:
|
|
||||||
self.do_search.emit('', 0)
|
|
||||||
self.text = text
|
|
||||||
self.flags = 0
|
|
||||||
if config.config.getboolean('general', 'ignorecase', fallback=True):
|
|
||||||
self.flags |= QWebPage.FindCaseSensitively
|
|
||||||
if config.config.getboolean('general', 'wrapsearch', fallback=True):
|
|
||||||
self.flags |= QWebPage.FindWrapsAroundDocument
|
|
||||||
if rev:
|
|
||||||
self.flags |= QWebPage.FindBackward
|
|
||||||
self.do_search.emit(self.text, self.flags)
|
|
||||||
|
|
||||||
def nextsearch(self, count=1):
|
def nextsearch(self, count=1):
|
||||||
"""Continue the search to the ([count]th) next term."""
|
"""Continue the search to the ([count]th) next term."""
|
||||||
if self.text is not None:
|
if self._text is not None:
|
||||||
for i in range(count): # pylint: disable=unused-variable
|
for i in range(count): # pylint: disable=unused-variable
|
||||||
self.do_search.emit(self.text, self.flags)
|
self.do_search.emit(self._text, self._flags)
|
||||||
|
|
||||||
|
|
||||||
class CommandParser(QObject):
|
class CommandParser(QObject):
|
||||||
|
|
||||||
"""Parse qutebrowser commandline commands."""
|
"""Parse qutebrowser commandline commands.
|
||||||
|
|
||||||
text = ''
|
Attributes:
|
||||||
cmd = ''
|
_cmd: The command which was parsed.
|
||||||
args = []
|
_args: The arguments which were parsed.
|
||||||
error = pyqtSignal(str) # Emitted if there's an error
|
|
||||||
|
Signals:
|
||||||
|
error: Emitted if there was an error.
|
||||||
|
arg: The error message.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
error = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._cmd = None
|
||||||
|
self._args = []
|
||||||
|
|
||||||
def _parse(self, text):
|
def _parse(self, text):
|
||||||
"""Split the commandline text into command and arguments.
|
"""Split the commandline text into command and arguments.
|
||||||
@ -114,8 +140,7 @@ class CommandParser(QObject):
|
|||||||
Raise NoSuchCommandError if a command wasn't found.
|
Raise NoSuchCommandError if a command wasn't found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.text = text
|
parts = text.strip().split(maxsplit=1)
|
||||||
parts = self.text.strip().split(maxsplit=1)
|
|
||||||
if not parts:
|
if not parts:
|
||||||
raise NoSuchCommandError
|
raise NoSuchCommandError
|
||||||
cmdstr = parts[0]
|
cmdstr = parts[0]
|
||||||
@ -130,19 +155,19 @@ class CommandParser(QObject):
|
|||||||
args = shlex.split(parts[1])
|
args = shlex.split(parts[1])
|
||||||
else:
|
else:
|
||||||
args = [parts[1]]
|
args = [parts[1]]
|
||||||
self.cmd = cmd
|
self._cmd = cmd
|
||||||
self.args = args
|
self._args = args
|
||||||
|
|
||||||
def _check(self):
|
def _check(self):
|
||||||
"""Check if the argument count for the command is correct."""
|
"""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."""
|
"""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)
|
||||||
|
|
||||||
@pyqtSlot(str, int, bool)
|
@pyqtSlot(str, int, bool)
|
||||||
def run(self, text, count=None, ignore_exc=True):
|
def run(self, text, count=None, ignore_exc=True):
|
||||||
@ -155,13 +180,17 @@ class CommandParser(QObject):
|
|||||||
arguments.
|
arguments.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if ';;' in text:
|
||||||
|
for sub in text.split(';;'):
|
||||||
|
self.run(sub, count, ignore_exc)
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
self._parse(text)
|
self._parse(text)
|
||||||
self._check()
|
self._check()
|
||||||
except ArgumentCountError:
|
except ArgumentCountError:
|
||||||
if ignore_exc:
|
if ignore_exc:
|
||||||
self.error.emit("{}: invalid argument count".format(
|
self.error.emit("{}: invalid argument count".format(
|
||||||
self.cmd.mainname))
|
self._cmd.mainname))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
"""A CompletionModel filled with all commands and descriptions."""
|
"""A CompletionModel filled with all commands and descriptions."""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from qutebrowser.commands.utils import cmd_dict
|
from qutebrowser.commands.utils import cmd_dict
|
||||||
from qutebrowser.models.completion import CompletionModel
|
from qutebrowser.models.completion import CompletionModel
|
||||||
|
|
||||||
@ -35,5 +37,6 @@ class CommandCompletionModel(CompletionModel):
|
|||||||
if not obj.hide:
|
if not obj.hide:
|
||||||
doc = obj.__doc__.splitlines()[0].strip().rstrip('.')
|
doc = obj.__doc__.splitlines()[0].strip().rstrip('.')
|
||||||
cmdlist.append([obj.mainname, doc])
|
cmdlist.append([obj.mainname, doc])
|
||||||
self._data['Commands'] = sorted(cmdlist)
|
data = OrderedDict()
|
||||||
self.init_data()
|
data['Commands'] = sorted(cmdlist)
|
||||||
|
self.init_data(data)
|
||||||
|
@ -15,15 +15,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""The base completion model for completion in the command line.
|
"""The base completion model for completion in the command line."""
|
||||||
|
|
||||||
Contains:
|
|
||||||
CompletionModel -- A simple tree model based on Python data.
|
|
||||||
CompletionItem -- One item in the CompletionModel.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QVariant, QAbstractItemModel, QModelIndex
|
from PyQt5.QtCore import Qt, QVariant, QAbstractItemModel, QModelIndex
|
||||||
|
|
||||||
@ -34,26 +26,18 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
|
|
||||||
Used for showing completions later in the CompletionView.
|
Used for showing completions later in the CompletionView.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_id_map: A mapping from Python object IDs (from id()) to objects, to be
|
||||||
|
used as internalIndex for the model.
|
||||||
|
_root: The root item.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._data = OrderedDict()
|
self._id_map = {}
|
||||||
self.parents = []
|
self._root = CompletionItem([""] * 2)
|
||||||
self.id_map = {}
|
self._id_map[id(self._root)] = self._root
|
||||||
self.root = CompletionItem([""] * 2)
|
|
||||||
self.id_map[id(self.root)] = self.root
|
|
||||||
|
|
||||||
def removeRows(self, position=0, count=1, parent=QModelIndex()):
|
|
||||||
"""Remove rows from the model.
|
|
||||||
|
|
||||||
Override 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.
|
"""Return the interal data representation for index.
|
||||||
@ -63,172 +47,9 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if index.isValid():
|
if index.isValid():
|
||||||
return self.id_map[index.internalId()]
|
return self._id_map[index.internalId()]
|
||||||
else:
|
else:
|
||||||
return self.root
|
return self._root
|
||||||
|
|
||||||
def columnCount(self, parent=QModelIndex()):
|
|
||||||
"""Return the column count in the model.
|
|
||||||
|
|
||||||
Override 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.
|
|
||||||
|
|
||||||
Return an invalid QVariant on error.
|
|
||||||
Override QAbstractItemModel::data.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not index.isValid():
|
|
||||||
return QVariant()
|
|
||||||
try:
|
|
||||||
item = self.id_map[index.internalId()]
|
|
||||||
except KeyError:
|
|
||||||
return QVariant()
|
|
||||||
try:
|
|
||||||
return QVariant(item.data(index.column(), role))
|
|
||||||
except (IndexError, ValueError):
|
|
||||||
return QVariant()
|
|
||||||
|
|
||||||
def flags(self, index):
|
|
||||||
"""Return the item flags for index.
|
|
||||||
|
|
||||||
Return Qt.NoItemFlags on error.
|
|
||||||
Override QAbstractItemModel::flags.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# FIXME categories are not selectable, but moving via arrow keys still
|
|
||||||
# tries to select them
|
|
||||||
if not index.isValid():
|
|
||||||
return Qt.NoItemFlags
|
|
||||||
flags = Qt.ItemIsEnabled
|
|
||||||
if len(self.id_map[index.internalId()].children) > 0:
|
|
||||||
return flags
|
|
||||||
else:
|
|
||||||
return flags | Qt.ItemIsSelectable
|
|
||||||
|
|
||||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
|
||||||
"""Return the header data for role/index as QVariant.
|
|
||||||
|
|
||||||
Return an invalid QVariant on error.
|
|
||||||
Override QAbstractItemModel::headerData.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
|
||||||
return QVariant(self.root.data(section))
|
|
||||||
return QVariant()
|
|
||||||
|
|
||||||
def setData(self, index, value, role=Qt.EditRole):
|
|
||||||
"""Set the data for role/index to value.
|
|
||||||
|
|
||||||
Return True on success, False on failure.
|
|
||||||
Override QAbstractItemModel::setData.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not index.isValid():
|
|
||||||
return False
|
|
||||||
item = self.id_map[index.internalId()]
|
|
||||||
try:
|
|
||||||
item.setdata(index.column(), value, role)
|
|
||||||
except (IndexError, ValueError):
|
|
||||||
return False
|
|
||||||
self.dataChanged.emit(index, index)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def index(self, row, column, parent=QModelIndex()):
|
|
||||||
"""Return the QModelIndex for row/column/parent.
|
|
||||||
|
|
||||||
Return an invalid QModelIndex on failure.
|
|
||||||
Override QAbstractItemModel::index.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if (0 <= row < self.rowCount(parent) and
|
|
||||||
0 <= column < self.columnCount(parent)):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
return QModelIndex()
|
|
||||||
|
|
||||||
if not parent.isValid():
|
|
||||||
parent_item = self.root
|
|
||||||
else:
|
|
||||||
parent_item = self.id_map[parent.internalId()]
|
|
||||||
|
|
||||||
child_item = parent_item.children[row]
|
|
||||||
if child_item:
|
|
||||||
index = self.createIndex(row, column, id(child_item))
|
|
||||||
self.id_map.setdefault(index.internalId(), child_item)
|
|
||||||
return index
|
|
||||||
else:
|
|
||||||
return QModelIndex()
|
|
||||||
|
|
||||||
def parent(self, index):
|
|
||||||
"""Return the QModelIndex of the parent of the object behind index.
|
|
||||||
|
|
||||||
Return an invalid QModelIndex on failure.
|
|
||||||
Override QAbstractItemModel::parent.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not index.isValid():
|
|
||||||
return QModelIndex()
|
|
||||||
item = self.id_map[index.internalId()].parent
|
|
||||||
if item == self.root or item is None:
|
|
||||||
return QModelIndex()
|
|
||||||
return self.createIndex(item.row(), 0, id(item))
|
|
||||||
|
|
||||||
def rowCount(self, parent=QModelIndex()):
|
|
||||||
"""Return the children count of an item.
|
|
||||||
|
|
||||||
Use the root frame if parent is invalid.
|
|
||||||
Override QAbstractItemModel::rowCount.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if parent.column() > 0:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if not parent.isValid():
|
|
||||||
pitem = self.root
|
|
||||||
else:
|
|
||||||
pitem = self.id_map[parent.internalId()]
|
|
||||||
|
|
||||||
return len(pitem.children)
|
|
||||||
|
|
||||||
def sort(self, column, order=Qt.AscendingOrder):
|
|
||||||
"""Sort the data in column according to order.
|
|
||||||
|
|
||||||
Raise NotImplementedError, should be overwritten in a superclass.
|
|
||||||
Override 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.id_map[id(newcat)] = newcat
|
|
||||||
self.root.children.append(newcat)
|
|
||||||
for item in items:
|
|
||||||
newitem = CompletionItem(item, newcat)
|
|
||||||
self.id_map[id(newitem)] = newitem
|
|
||||||
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)):
|
|
||||||
idx = self.index(k, 0, cat)
|
|
||||||
old = self.data(idx).value()
|
|
||||||
marks = self._get_marks(needle, old)
|
|
||||||
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."""
|
"""Return the marks for needle in haystack."""
|
||||||
@ -244,15 +65,196 @@ class CompletionModel(QAbstractItemModel):
|
|||||||
marks.append((pos1, pos2))
|
marks.append((pos1, pos2))
|
||||||
return marks
|
return marks
|
||||||
|
|
||||||
|
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)):
|
||||||
|
idx = self.index(k, 0, cat)
|
||||||
|
old = self.data(idx).value()
|
||||||
|
marks = self._get_marks(needle, old)
|
||||||
|
self.setData(idx, marks, Qt.UserRole)
|
||||||
|
|
||||||
|
def init_data(self, data):
|
||||||
|
"""Initialize the Qt model based on the data given.
|
||||||
|
|
||||||
|
data -- dict of data to process.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for (cat, items) in data.items():
|
||||||
|
newcat = CompletionItem([cat], self._root)
|
||||||
|
self._id_map[id(newcat)] = newcat
|
||||||
|
self._root.children.append(newcat)
|
||||||
|
for item in items:
|
||||||
|
newitem = CompletionItem(item, newcat)
|
||||||
|
self._id_map[id(newitem)] = newitem
|
||||||
|
newcat.children.append(newitem)
|
||||||
|
|
||||||
|
def removeRows(self, position=0, count=1, parent=QModelIndex()):
|
||||||
|
"""Remove rows from the model.
|
||||||
|
|
||||||
|
Override QAbstractItemModel::removeRows.
|
||||||
|
|
||||||
|
"""
|
||||||
|
node = self._node(parent)
|
||||||
|
self.beginRemoveRows(parent, position, position + count - 1)
|
||||||
|
node.children.pop(position)
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
def columnCount(self, parent=QModelIndex()):
|
||||||
|
"""Return the column count in the model.
|
||||||
|
|
||||||
|
Override QAbstractItemModel::columnCount.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
return self._root.column_count()
|
||||||
|
|
||||||
|
def rowCount(self, parent=QModelIndex()):
|
||||||
|
"""Return the children count of an item.
|
||||||
|
|
||||||
|
Use the root frame if parent is invalid.
|
||||||
|
Override QAbstractItemModel::rowCount.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if parent.column() > 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not parent.isValid():
|
||||||
|
pitem = self._root
|
||||||
|
else:
|
||||||
|
pitem = self._id_map[parent.internalId()]
|
||||||
|
|
||||||
|
return len(pitem.children)
|
||||||
|
|
||||||
|
def data(self, index, role=Qt.DisplayRole):
|
||||||
|
"""Return the data for role/index as QVariant.
|
||||||
|
|
||||||
|
Return an invalid QVariant on error.
|
||||||
|
Override QAbstractItemModel::data.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not index.isValid():
|
||||||
|
return QVariant()
|
||||||
|
try:
|
||||||
|
item = self._id_map[index.internalId()]
|
||||||
|
except KeyError:
|
||||||
|
return QVariant()
|
||||||
|
try:
|
||||||
|
return QVariant(item.data(index.column(), role))
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
return QVariant()
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||||
|
"""Return the header data for role/index as QVariant.
|
||||||
|
|
||||||
|
Return an invalid QVariant on error.
|
||||||
|
Override QAbstractItemModel::headerData.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||||
|
return QVariant(self._root.data(section))
|
||||||
|
return QVariant()
|
||||||
|
|
||||||
|
def setData(self, index, value, role=Qt.EditRole):
|
||||||
|
"""Set the data for role/index to value.
|
||||||
|
|
||||||
|
Return True on success, False on failure.
|
||||||
|
Override QAbstractItemModel::setData.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not index.isValid():
|
||||||
|
return False
|
||||||
|
item = self._id_map[index.internalId()]
|
||||||
|
try:
|
||||||
|
item.setdata(index.column(), value, role)
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
return False
|
||||||
|
self.dataChanged.emit(index, index)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
"""Return the item flags for index.
|
||||||
|
|
||||||
|
Return Qt.NoItemFlags on error.
|
||||||
|
Override QAbstractItemModel::flags.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# FIXME categories are not selectable, but moving via arrow keys still
|
||||||
|
# tries to select them
|
||||||
|
if not index.isValid():
|
||||||
|
return Qt.NoItemFlags
|
||||||
|
flags = Qt.ItemIsEnabled
|
||||||
|
if len(self._id_map[index.internalId()].children) > 0:
|
||||||
|
return flags
|
||||||
|
else:
|
||||||
|
return flags | Qt.ItemIsSelectable
|
||||||
|
|
||||||
|
def index(self, row, column, parent=QModelIndex()):
|
||||||
|
"""Return the QModelIndex for row/column/parent.
|
||||||
|
|
||||||
|
Return an invalid QModelIndex on failure.
|
||||||
|
Override QAbstractItemModel::index.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if (0 <= row < self.rowCount(parent) and
|
||||||
|
0 <= column < self.columnCount(parent)):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return QModelIndex()
|
||||||
|
|
||||||
|
if not parent.isValid():
|
||||||
|
parent_item = self._root
|
||||||
|
else:
|
||||||
|
parent_item = self._id_map[parent.internalId()]
|
||||||
|
|
||||||
|
child_item = parent_item.children[row]
|
||||||
|
if child_item:
|
||||||
|
index = self.createIndex(row, column, id(child_item))
|
||||||
|
self._id_map.setdefault(index.internalId(), child_item)
|
||||||
|
return index
|
||||||
|
else:
|
||||||
|
return QModelIndex()
|
||||||
|
|
||||||
|
def parent(self, index):
|
||||||
|
"""Return the QModelIndex of the parent of the object behind index.
|
||||||
|
|
||||||
|
Return an invalid QModelIndex on failure.
|
||||||
|
Override QAbstractItemModel::parent.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not index.isValid():
|
||||||
|
return QModelIndex()
|
||||||
|
item = self._id_map[index.internalId()].parent
|
||||||
|
if item == self._root or item is None:
|
||||||
|
return QModelIndex()
|
||||||
|
return self.createIndex(item.row(), 0, id(item))
|
||||||
|
|
||||||
|
def sort(self, column, order=Qt.AscendingOrder):
|
||||||
|
"""Sort the data in column according to order.
|
||||||
|
|
||||||
|
Raise NotImplementedError, should be overwritten in a superclass.
|
||||||
|
Override QAbstractItemModel::sort.
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class CompletionItem():
|
class CompletionItem():
|
||||||
|
|
||||||
"""An item (row) in a CompletionModel."""
|
"""An item (row) in a CompletionModel.
|
||||||
|
|
||||||
parent = None
|
Attributes:
|
||||||
children = None
|
parent: The parent of this item.
|
||||||
_data = None
|
children: The children of this item.
|
||||||
_marks = None
|
_data: The data of this item.
|
||||||
|
_marks: The marks of this item.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, data, parent=None):
|
def __init__(self, data, parent=None):
|
||||||
"""Constructor for CompletionItem.
|
"""Constructor for CompletionItem.
|
||||||
@ -262,8 +264,8 @@ class CompletionItem():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self._data = data
|
|
||||||
self.children = []
|
self.children = []
|
||||||
|
self._data = data
|
||||||
self._marks = []
|
self._marks = []
|
||||||
|
|
||||||
def data(self, column, role=Qt.DisplayRole):
|
def data(self, column, role=Qt.DisplayRole):
|
||||||
|
@ -27,13 +27,17 @@ from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex
|
|||||||
|
|
||||||
class CompletionFilterModel(QSortFilterProxyModel):
|
class CompletionFilterModel(QSortFilterProxyModel):
|
||||||
|
|
||||||
"""Subclass of QSortFilterProxyModel with custom sorting/filtering."""
|
"""Subclass of QSortFilterProxyModel with custom sorting/filtering.
|
||||||
|
|
||||||
_pattern = None
|
Attributes:
|
||||||
srcmodel = None
|
_pattern: The pattern to filter with, used in pattern property.
|
||||||
|
_srcmodel: The source model, accessed via the srcmodel property.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self._srcmodel = None
|
||||||
self._pattern = ''
|
self._pattern = ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -41,16 +45,6 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
|||||||
"""Getter for pattern."""
|
"""Getter for pattern."""
|
||||||
return self._pattern
|
return self._pattern
|
||||||
|
|
||||||
def setsrc(self, model):
|
|
||||||
"""Set a new source model and clear the pattern.
|
|
||||||
|
|
||||||
model -- The new source model.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.setSourceModel(model)
|
|
||||||
self.srcmodel = model
|
|
||||||
self.pattern = ''
|
|
||||||
|
|
||||||
@pattern.setter
|
@pattern.setter
|
||||||
def pattern(self, val):
|
def pattern(self, val):
|
||||||
"""Setter for pattern.
|
"""Setter for pattern.
|
||||||
@ -71,6 +65,33 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
|||||||
self.sort(sortcol)
|
self.sort(sortcol)
|
||||||
self.invalidate()
|
self.invalidate()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def srcmodel(self):
|
||||||
|
"""Getter for srcmodel."""
|
||||||
|
return self._srcmodel
|
||||||
|
|
||||||
|
@srcmodel.setter
|
||||||
|
def srcmodel(self, model):
|
||||||
|
"""Set a new source model and clear the pattern.
|
||||||
|
|
||||||
|
model -- The new source model.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# FIXME change this to a property
|
||||||
|
self.setSourceModel(model)
|
||||||
|
self._srcmodel = model
|
||||||
|
self.pattern = ''
|
||||||
|
|
||||||
|
def first_item(self):
|
||||||
|
"""Return the first item in the model."""
|
||||||
|
cat = self.index(0, 0)
|
||||||
|
return self.index(0, 0, cat)
|
||||||
|
|
||||||
|
def last_item(self):
|
||||||
|
"""Return the last item in the model."""
|
||||||
|
cat = self.index(self.rowCount() - 1, 0)
|
||||||
|
return self.index(self.rowCount(cat) - 1, 0, cat)
|
||||||
|
|
||||||
def filterAcceptsRow(self, row, parent):
|
def filterAcceptsRow(self, row, parent):
|
||||||
"""Custom filter implementation.
|
"""Custom filter implementation.
|
||||||
|
|
||||||
@ -116,13 +137,3 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return left < right
|
return left < right
|
||||||
|
|
||||||
def first_item(self):
|
|
||||||
"""Return the first item in the model."""
|
|
||||||
cat = self.index(0, 0)
|
|
||||||
return self.index(0, 0, cat)
|
|
||||||
|
|
||||||
def last_item(self):
|
|
||||||
"""Return the last item in the model."""
|
|
||||||
cat = self.index(self.rowCount() - 1, 0)
|
|
||||||
return self.index(self.rowCount(cat) - 1, 0, cat)
|
|
||||||
|
188
qutebrowser/qutebrowser.conf
Normal file
188
qutebrowser/qutebrowser.conf
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# vim: ft=dosini
|
||||||
|
|
||||||
|
# Configfile for qutebrowser.
|
||||||
|
#
|
||||||
|
# This configfile is parsed by python's configparser in extended interpolation
|
||||||
|
# mode. The format is very INI-like, so there are categories like [general]
|
||||||
|
# with "key = value"-pairs.
|
||||||
|
#
|
||||||
|
# Comments start with ; or # and may only start at the beginning of a line.
|
||||||
|
#
|
||||||
|
# Interpolation looks like ${value} or ${section:value} and will be replaced
|
||||||
|
# by the respective value.
|
||||||
|
#
|
||||||
|
# This is the default config, so if you want to remove anything from here (as
|
||||||
|
# opposed to change/add), for example a keybinding, set it to an empty value.
|
||||||
|
|
||||||
|
[general]
|
||||||
|
# show_completion: bool, whether to show the autocompletion window or not.
|
||||||
|
# ignorecase: bool, whether to do case-insensitive searching.
|
||||||
|
# wrapsearch: bool, whether to wrap search to the top when arriving at the end.
|
||||||
|
# startpage: The default pages to open at the start, multiple pages can be
|
||||||
|
# separated with commas.
|
||||||
|
# auto_search: Whether to start a search automatically when something
|
||||||
|
# which is not an url is entered.
|
||||||
|
# true/naive: Use simple/naive check
|
||||||
|
# dns: Use DNS matching (might be slow)
|
||||||
|
# false: Never search automatically
|
||||||
|
show_completion = true
|
||||||
|
ignorecase = true
|
||||||
|
wrapsearch = true
|
||||||
|
startpage = http://www.duckduckgo.com/
|
||||||
|
auto_search = naive
|
||||||
|
|
||||||
|
[tabbar]
|
||||||
|
# movable: bool, whether tabs should be movable
|
||||||
|
# closebuttons: bool, whether tabs should have a close button
|
||||||
|
# scrollbuttons: bool, whether there should be scroll buttons if there are too
|
||||||
|
# many tabs open.
|
||||||
|
# position: Position of the tab bar, either north, south, east or west.
|
||||||
|
# select_on_remove: Which tab to select when the focused tab is removed. Either
|
||||||
|
# 'previous', 'left' or 'right'.
|
||||||
|
# last_close; Behavour when the last tab is closed - 'ignore' (don't do
|
||||||
|
# anything), 'blank' (load about:blank) or 'quit' (quit
|
||||||
|
# qutebrowser).
|
||||||
|
movable = true
|
||||||
|
closebuttons = false
|
||||||
|
scrollbuttons = false
|
||||||
|
position = north
|
||||||
|
select_on_remove = previous
|
||||||
|
last_close = quit
|
||||||
|
|
||||||
|
[searchengines]
|
||||||
|
# Definitions of search engines which can be used via the address bar.
|
||||||
|
# The searchengine named DEFAULT is used when general.auto_search is true and
|
||||||
|
# something else than an URL was entered to be opened.
|
||||||
|
# Other search engines can be used via the bang-syntax, e.g.
|
||||||
|
# "qutebrowser !google". The string "{}" will be replaced by the search term,
|
||||||
|
# use "{{" and "}}" for literal {/} signs.
|
||||||
|
DEFAULT = ${duckduckgo}
|
||||||
|
duckduckgo = https://duckduckgo.com/?q={}
|
||||||
|
ddg = ${duckduckgo}
|
||||||
|
google = https://encrypted.google.com/search?q={}
|
||||||
|
g = ${google}
|
||||||
|
wikipedia = http://en.wikipedia.org/w/index.php?title=Special:Search&search={}
|
||||||
|
wiki = ${wikipedia}
|
||||||
|
|
||||||
|
[keybind]
|
||||||
|
# Bindings from a key(chain) to a command. For special keys (can't be part of a
|
||||||
|
# keychain), enclose them in @-signs. For modifiers, you can use either - or +
|
||||||
|
# as delimiters, and these names:
|
||||||
|
# Control: Control, Ctrl
|
||||||
|
# Meta: Meta, Windows, Mod4
|
||||||
|
# Alt: Alt, Mod1
|
||||||
|
# Shift: Shift
|
||||||
|
# For simple keys (no @ signs), a capital letter means the key is pressed with
|
||||||
|
# Shift. For modifier keys (with @ signs), you need to explicitely add "Shift-"
|
||||||
|
# to match a key pressed with shift.
|
||||||
|
# You can bind multiple commands by separating them with ";;".
|
||||||
|
o = open
|
||||||
|
go = opencur
|
||||||
|
O = tabopen
|
||||||
|
gO = tabopencur
|
||||||
|
ga = tabopen about:blank
|
||||||
|
d = tabclose
|
||||||
|
J = tabnext
|
||||||
|
K = tabprev
|
||||||
|
r = reload
|
||||||
|
H = back
|
||||||
|
L = forward
|
||||||
|
h = scroll -50 0
|
||||||
|
j = scroll 0 50
|
||||||
|
k = scroll 0 -50
|
||||||
|
l = scroll 50 0
|
||||||
|
u = undo
|
||||||
|
gg = scroll_perc_y 0
|
||||||
|
G = scroll_perc_y
|
||||||
|
n = nextsearch
|
||||||
|
yy = yank
|
||||||
|
yY = yank sel
|
||||||
|
yt = yanktitle
|
||||||
|
yT = yanktitle sel
|
||||||
|
pp = paste
|
||||||
|
pP = paste sel
|
||||||
|
Pp = tabpaste
|
||||||
|
PP = tabpaste sel
|
||||||
|
@Ctrl-Q@ = quit
|
||||||
|
@Ctrl-Shift-T@ = undo
|
||||||
|
@Ctrl-W@ = tabclose
|
||||||
|
@Ctrl-T@ = tabopen about:blank
|
||||||
|
@Ctrl-F@ = scroll_page 0 1
|
||||||
|
@Ctrl-B@ = scroll_page 0 -1
|
||||||
|
@Ctrl-D@ = scroll_page 0 0.5
|
||||||
|
@Ctrl-U@ = scroll_page 0 -0.5
|
||||||
|
|
||||||
|
[colors]
|
||||||
|
# Colors used in the UI. A value can be in one of the following format:
|
||||||
|
# - #RGB/#RRGGBB/#RRRGGGBBB/#RRRRGGGGBBBB
|
||||||
|
# - A SVG color name as specified in [1].
|
||||||
|
# - transparent (no color)
|
||||||
|
# - rgb(r, g, b) / rgba(r, g, b, a) (values 0-255 or percentages)
|
||||||
|
# - hsv(h, s, v) / hsva(h, s, v, a) (values 0-255, hue 0-359)
|
||||||
|
# - A gradient as explained at [2] under "Gradient"
|
||||||
|
# [1] http://www.w3.org/TR/SVG/types.html#ColorKeywords
|
||||||
|
# [2] http://qt-project.org/doc/qt-4.8/stylesheet-reference.html#list-of-property-types
|
||||||
|
#
|
||||||
|
## Completion widget
|
||||||
|
# Text color
|
||||||
|
completion.fg = #333333
|
||||||
|
# Background of a normal item
|
||||||
|
completion.item.bg = white
|
||||||
|
# Background of a category header
|
||||||
|
completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||||
|
stop:0 #e4e4e4, stop:1 #dbdbdb)
|
||||||
|
# Borders of a category header
|
||||||
|
completion.category.border.top = #808080
|
||||||
|
completion.category.border.bottom = #bbbbbb
|
||||||
|
# Text/background color for the currently selected item
|
||||||
|
completion.item.selected.fg = #333333
|
||||||
|
completion.item.selected.bg = #ffec8b
|
||||||
|
# Borders for the selected item
|
||||||
|
completion.item.selected.border.top = #f2f2c0
|
||||||
|
completion.item.selected.border.bottom = #e6e680
|
||||||
|
# Matched text in a completion
|
||||||
|
completion.match.fg = red
|
||||||
|
## Statusbar widget
|
||||||
|
# Normal colors for the statusbar.
|
||||||
|
statusbar.bg = black
|
||||||
|
statusbar.fg = white
|
||||||
|
# Statusbar colors when there is an error
|
||||||
|
statusbar.bg.error = red
|
||||||
|
# Colors of the progress bar
|
||||||
|
statusbar.progress.bg = white
|
||||||
|
# Colors of the URL shown in the statusbar
|
||||||
|
# Unknown/default status
|
||||||
|
statusbar.url.fg = ${statusbar.fg}
|
||||||
|
# Page loaded successfully
|
||||||
|
statusbar.url.fg.success = lime
|
||||||
|
# Error while loading page
|
||||||
|
statusbar.url.fg.error = orange
|
||||||
|
# Warning while loading page
|
||||||
|
statusbar.url.fg.warn = yellow
|
||||||
|
# Link under the mouse cursor
|
||||||
|
statusbar.url.fg.hover = aqua
|
||||||
|
## Tabbar
|
||||||
|
# Tabbar colors
|
||||||
|
tab.fg = white
|
||||||
|
tab.bg = grey
|
||||||
|
# Color of the selected tab
|
||||||
|
tab.bg.selected = black
|
||||||
|
# Seperator between tabs
|
||||||
|
tab.seperator = white
|
||||||
|
|
||||||
|
[fonts]
|
||||||
|
# Fonts used for the UI, with optional style/weight/size.
|
||||||
|
# Style: normal/italic/oblique
|
||||||
|
# Weight: normal, bold, 100..900
|
||||||
|
# Size: Number + px/pt
|
||||||
|
#
|
||||||
|
# Default monospace fonts
|
||||||
|
_monospace = Monospace, "DejaVu Sans Mono", Consolas, Monaco,
|
||||||
|
"Bitstream Vera Sans Mono", "Andale Mono", "Liberation Mono",
|
||||||
|
"Courier New", Courier, monospace, Fixed, Terminal
|
||||||
|
# Font used in the completion widget.
|
||||||
|
completion = 8pt ${_monospace}
|
||||||
|
# Font used in the tabbar
|
||||||
|
tabbar = 8pt ${_monospace}
|
||||||
|
# Font used in the statusbar.
|
||||||
|
statusbar = 8pt ${_monospace}
|
@ -15,131 +15,32 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""Configuration storage and config-related utilities.
|
"""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
|
import os
|
||||||
import io
|
import io
|
||||||
import os.path
|
import os.path
|
||||||
import logging
|
import logging
|
||||||
from configparser import ConfigParser, ExtendedInterpolation
|
from configparser import (ConfigParser, ExtendedInterpolation, NoSectionError,
|
||||||
|
NoOptionError)
|
||||||
|
|
||||||
|
from qutebrowser.utils.misc import read_file
|
||||||
|
|
||||||
config = None
|
config = None
|
||||||
|
state = None
|
||||||
colordict = {}
|
colordict = {}
|
||||||
fontdict = {}
|
fontdict = {}
|
||||||
|
|
||||||
default_config = """
|
# Special value for an unset fallback, so None can be passed as fallback.
|
||||||
[general]
|
_UNSET = object()
|
||||||
show_completion = true
|
|
||||||
ignorecase = true
|
|
||||||
wrapsearch = true
|
|
||||||
startpage = http://www.duckduckgo.com/
|
|
||||||
addressbar_dns_lookup = false
|
|
||||||
auto_search = true
|
|
||||||
|
|
||||||
[tabbar]
|
|
||||||
movable = true
|
|
||||||
closebuttons = false
|
|
||||||
scrollbuttons = false
|
|
||||||
# north, south, east, west
|
|
||||||
position = north
|
|
||||||
# previous, left, right
|
|
||||||
select_on_remove = previous
|
|
||||||
# ignore, blank, quit
|
|
||||||
last_close = quit
|
|
||||||
|
|
||||||
[searchengines]
|
|
||||||
DEFAULT = ${duckduckgo}
|
|
||||||
duckduckgo = https://duckduckgo.com/?q={}
|
|
||||||
ddg = ${duckduckgo}
|
|
||||||
google = https://encrypted.google.com/search?q={}
|
|
||||||
g = ${google}
|
|
||||||
wikipedia = http://en.wikipedia.org/w/index.php?title=Special:Search&search={}
|
|
||||||
wiki = ${wikipedia}
|
|
||||||
|
|
||||||
[keybind]
|
|
||||||
o = open
|
|
||||||
go = opencur
|
|
||||||
O = tabopen
|
|
||||||
gO = tabopencur
|
|
||||||
ga = tabopen about:blank
|
|
||||||
d = tabclose
|
|
||||||
J = tabnext
|
|
||||||
K = tabprev
|
|
||||||
r = reload
|
|
||||||
H = back
|
|
||||||
L = forward
|
|
||||||
h = scroll -50 0
|
|
||||||
j = scroll 0 50
|
|
||||||
k = scroll 0 -50
|
|
||||||
l = scroll 50 0
|
|
||||||
u = undo
|
|
||||||
gg = scroll_perc_y 0
|
|
||||||
G = scroll_perc_y
|
|
||||||
n = nextsearch
|
|
||||||
yy = yank
|
|
||||||
yY = yank sel
|
|
||||||
yt = yanktitle
|
|
||||||
yT = yanktitle sel
|
|
||||||
pp = paste
|
|
||||||
pP = paste sel
|
|
||||||
Pp = tabpaste
|
|
||||||
PP = tabpaste sel
|
|
||||||
@Ctrl-Q@ = quit
|
|
||||||
@Ctrl-Shift-T@ = undo
|
|
||||||
@Ctrl-W@ = tabclose
|
|
||||||
@Ctrl-T@ = tabopen about:blank
|
|
||||||
@Ctrl-F@ = scroll_page 0 1
|
|
||||||
@Ctrl-B@ = scroll_page 0 -1
|
|
||||||
@Ctrl-D@ = scroll_page 0 0.5
|
|
||||||
@Ctrl-U@ = scroll_page 0 -0.5
|
|
||||||
|
|
||||||
[colors]
|
|
||||||
completion.fg = #333333
|
|
||||||
completion.item.bg = white
|
|
||||||
completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
|
||||||
stop:0 #e4e4e4, stop:1 #dbdbdb)
|
|
||||||
completion.category.border.top = #808080
|
|
||||||
completion.category.border.bottom = #bbbbbb
|
|
||||||
completion.item.selected.fg = #333333
|
|
||||||
completion.item.selected.bg = #ffec8b
|
|
||||||
completion.item.selected.border.top = #f2f2c0
|
|
||||||
completion.item.selected.border.bottom = #e6e680
|
|
||||||
completion.match.fg = red
|
|
||||||
statusbar.progress.bg = white
|
|
||||||
statusbar.bg = black
|
|
||||||
statusbar.fg = white
|
|
||||||
statusbar.bg.error = red
|
|
||||||
statusbar.url.fg = ${statusbar.fg}
|
|
||||||
statusbar.url.fg.success = lime
|
|
||||||
statusbar.url.fg.error = orange
|
|
||||||
statusbar.url.fg.warn = yellow
|
|
||||||
statusbar.url.fg.hover = aqua
|
|
||||||
tab.bg = grey
|
|
||||||
tab.bg.selected = black
|
|
||||||
tab.fg = white
|
|
||||||
tab.seperator = white
|
|
||||||
|
|
||||||
[fonts]
|
|
||||||
_monospace = Monospace, "DejaVu Sans Mono", Consolas, Monaco,
|
|
||||||
"Bitstream Vera Sans Mono", "Andale Mono", "Liberation Mono",
|
|
||||||
"Courier New", Courier, monospace, Fixed, Terminal
|
|
||||||
completion = 8pt ${_monospace}
|
|
||||||
tabbar = 8pt ${_monospace}
|
|
||||||
statusbar = 8pt ${_monospace}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def init(confdir):
|
def init(confdir):
|
||||||
"""Initialize the global objects based on the config in configdir."""
|
"""Initialize the global objects based on the config in configdir."""
|
||||||
global config, colordict, fontdict
|
global config, state, colordict, fontdict
|
||||||
config = Config(confdir)
|
logging.debug("Config init, confdir {}".format(confdir))
|
||||||
|
config = Config(confdir, 'qutebrowser.conf', read_file('qutebrowser.conf'))
|
||||||
|
state = Config(confdir, 'state', always_save=True)
|
||||||
try:
|
try:
|
||||||
colordict = ColorDict(config['colors'])
|
colordict = ColorDict(config['colors'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -229,33 +130,44 @@ class FontDict(dict):
|
|||||||
|
|
||||||
class Config(ConfigParser):
|
class Config(ConfigParser):
|
||||||
|
|
||||||
"""Our own ConfigParser subclass."""
|
"""Our own ConfigParser subclass.
|
||||||
|
|
||||||
configdir = None
|
Attributes:
|
||||||
FNAME = 'config'
|
_configdir: The dictionary to save the config in.
|
||||||
default_cp = None
|
_default_cp: The ConfigParser instance supplying the default values.
|
||||||
config_loaded = False
|
_config_loaded: Whether the config was loaded successfully.
|
||||||
|
|
||||||
def __init__(self, configdir):
|
"""
|
||||||
|
|
||||||
|
def __init__(self, configdir, fname, default_config=None,
|
||||||
|
always_save=False):
|
||||||
"""Config constructor.
|
"""Config constructor.
|
||||||
|
|
||||||
configdir -- directory to store the config in.
|
configdir -- directory to store the config in.
|
||||||
|
fname -- Filename of the config file.
|
||||||
|
default_config -- Default config as string.
|
||||||
|
always_save -- Whether to always save the config, even when it wasn't
|
||||||
|
loaded.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super().__init__(interpolation=ExtendedInterpolation())
|
super().__init__(interpolation=ExtendedInterpolation())
|
||||||
self.default_cp = ConfigParser(interpolation=ExtendedInterpolation())
|
self._config_loaded = False
|
||||||
self.default_cp.optionxform = lambda opt: opt # be case-insensitive
|
self.always_save = always_save
|
||||||
self.default_cp.read_string(default_config)
|
self._configdir = configdir
|
||||||
if not self.configdir:
|
self._default_cp = ConfigParser(interpolation=ExtendedInterpolation())
|
||||||
|
self._default_cp.optionxform = lambda opt: opt # be case-insensitive
|
||||||
|
if default_config is not None:
|
||||||
|
self._default_cp.read_string(default_config)
|
||||||
|
if not self._configdir:
|
||||||
return
|
return
|
||||||
self.optionxform = lambda opt: opt # be case-insensitive
|
self.optionxform = lambda opt: opt # be case-insensitive
|
||||||
self.configdir = configdir
|
self._configdir = configdir
|
||||||
self.configfile = os.path.join(self.configdir, self.FNAME)
|
self.configfile = os.path.join(self._configdir, fname)
|
||||||
if not os.path.isfile(self.configfile):
|
if not os.path.isfile(self.configfile):
|
||||||
return
|
return
|
||||||
logging.debug("Reading config from {}".format(self.configfile))
|
logging.debug("Reading config from {}".format(self.configfile))
|
||||||
self.read(self.configfile)
|
self.read(self.configfile)
|
||||||
self.config_loaded = True
|
self._config_loaded = True
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
"""Get an item from the configparser or default dict.
|
"""Get an item from the configparser or default dict.
|
||||||
@ -266,25 +178,42 @@ class Config(ConfigParser):
|
|||||||
try:
|
try:
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return self.default_cp[key]
|
return self._default_cp[key]
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, raw=False, vars=None, fallback=_UNSET):
|
||||||
"""Get an item from the configparser or default dict.
|
"""Get an item from the configparser or default dict.
|
||||||
|
|
||||||
Extend ConfigParser's get().
|
Extend ConfigParser's get().
|
||||||
|
|
||||||
|
This is a bit of a hack, but it (hopefully) works like this:
|
||||||
|
- Get value from original configparser.
|
||||||
|
- If that's not available, try the default_cp configparser
|
||||||
|
- If that's not available, try the fallback given as kwarg
|
||||||
|
- If that's not available, we're doomed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if 'fallback' in kwargs:
|
# pylint: disable=redefined-builtin
|
||||||
del kwargs['fallback']
|
try:
|
||||||
fallback = self.default_cp.get(*args, **kwargs)
|
return super().get(*args, raw=raw, vars=vars)
|
||||||
return super().get(*args, fallback=fallback, **kwargs)
|
except (NoSectionError, NoOptionError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return self._default_cp.get(*args, raw=raw, vars=vars)
|
||||||
|
except (NoSectionError, NoOptionError):
|
||||||
|
if fallback is _UNSET:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return fallback
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save the config file."""
|
"""Save the config file."""
|
||||||
if self.configdir is None or not self.config_loaded:
|
if self._configdir is None or (not self._config_loaded and
|
||||||
|
not self.always_save):
|
||||||
|
logging.error("Not saving config (dir {}, loaded {})".format(
|
||||||
|
self._configdir, self._config_loaded))
|
||||||
return
|
return
|
||||||
if not os.path.exists(self.configdir):
|
if not os.path.exists(self._configdir):
|
||||||
os.makedirs(self.configdir, 0o755)
|
os.makedirs(self._configdir, 0o755)
|
||||||
logging.debug("Saving config to {}".format(self.configfile))
|
logging.debug("Saving config to {}".format(self.configfile))
|
||||||
with open(self.configfile, 'w') as f:
|
with open(self.configfile, 'w') as f:
|
||||||
self.write(f)
|
self.write(f)
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
"""Other utilities which don't fit anywhere else."""
|
"""Other utilities which don't fit anywhere else."""
|
||||||
|
|
||||||
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtRemoveInputHook
|
from PyQt5.QtCore import pyqtRemoveInputHook
|
||||||
@ -50,3 +51,16 @@ def read_file(filename):
|
|||||||
fn = os.path.join(qutebrowser.basedir, filename)
|
fn = os.path.join(qutebrowser.basedir, filename)
|
||||||
with open(fn, 'r', encoding='UTF-8') as f:
|
with open(fn, 'r', encoding='UTF-8') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def trace_lines(do_trace):
|
||||||
|
"""Turn on/off printing each executed line."""
|
||||||
|
def trace(frame, event, _):
|
||||||
|
"""Trace function passed to sys.settrace."""
|
||||||
|
print("{}, {}:{}".format(event, frame.f_code.co_filename,
|
||||||
|
frame.f_lineno))
|
||||||
|
return trace
|
||||||
|
if do_trace:
|
||||||
|
sys.settrace(trace)
|
||||||
|
else:
|
||||||
|
sys.settrace(None)
|
||||||
|
@ -42,10 +42,13 @@ def dbg_signal(sig, args):
|
|||||||
|
|
||||||
class SignalCache(QObject):
|
class SignalCache(QObject):
|
||||||
|
|
||||||
"""Cache signals emitted by an object, and re-emit them later."""
|
"""Cache signals emitted by an object, and re-emit them later.
|
||||||
|
|
||||||
uncached = None
|
Attributes:
|
||||||
signal_dict = None
|
_uncached: A list of signals which should not be cached.
|
||||||
|
_signal_dict: The internal mapping of signals we got.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, uncached=None):
|
def __init__(self, uncached=None):
|
||||||
"""Create a new SignalCache.
|
"""Create a new SignalCache.
|
||||||
@ -56,10 +59,14 @@ class SignalCache(QObject):
|
|||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if uncached is None:
|
if uncached is None:
|
||||||
self.uncached = []
|
self._uncached = []
|
||||||
else:
|
else:
|
||||||
self.uncached = uncached
|
self._uncached = uncached
|
||||||
self.signal_dict = OrderedDict()
|
self._signal_dict = OrderedDict()
|
||||||
|
|
||||||
|
def _signal_needs_caching(self, signal):
|
||||||
|
"""Return True if a signal should be cached, false otherwise."""
|
||||||
|
return not signal_name(signal) in self._uncached
|
||||||
|
|
||||||
def add(self, sig, args):
|
def add(self, sig, args):
|
||||||
"""Add a new signal to the signal cache.
|
"""Add a new signal to the signal cache.
|
||||||
@ -71,21 +78,17 @@ class SignalCache(QObject):
|
|||||||
"""
|
"""
|
||||||
if not self._signal_needs_caching(sig):
|
if not self._signal_needs_caching(sig):
|
||||||
return
|
return
|
||||||
had_signal = sig.signal in self.signal_dict
|
had_signal = sig.signal in self._signal_dict
|
||||||
self.signal_dict[sig.signal] = (sig, args)
|
self._signal_dict[sig.signal] = (sig, args)
|
||||||
if had_signal:
|
if had_signal:
|
||||||
self.signal_dict.move_to_end(sig.signal)
|
self._signal_dict.move_to_end(sig.signal)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Clear/purge the signal cache."""
|
"""Clear/purge the signal cache."""
|
||||||
self.signal_dict.clear()
|
self._signal_dict.clear()
|
||||||
|
|
||||||
def replay(self):
|
def replay(self):
|
||||||
"""Replay all cached signals."""
|
"""Replay all cached signals."""
|
||||||
for (signal, args) in self.signal_dict.values():
|
for (signal, args) in self._signal_dict.values():
|
||||||
logging.debug('emitting {}'.format(dbg_signal(signal, args)))
|
logging.debug('emitting {}'.format(dbg_signal(signal, args)))
|
||||||
signal.emit(*args)
|
signal.emit(*args)
|
||||||
|
|
||||||
def _signal_needs_caching(self, signal):
|
|
||||||
"""Return True if a signal should be cached, false otherwise."""
|
|
||||||
return not signal_name(signal) in self.uncached
|
|
||||||
|
@ -38,6 +38,9 @@ class Style(QCommonStyle):
|
|||||||
http://stackoverflow.com/a/17294081
|
http://stackoverflow.com/a/17294081
|
||||||
https://code.google.com/p/makehuman/source/browse/trunk/makehuman/lib/qtgui.py # noqa # pylint: disable=line-too-long
|
https://code.google.com/p/makehuman/source/browse/trunk/makehuman/lib/qtgui.py # noqa # pylint: disable=line-too-long
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_style: The base/"parent" style.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, style):
|
def __init__(self, style):
|
||||||
|
@ -27,6 +27,52 @@ from PyQt5.QtCore import QUrl
|
|||||||
import qutebrowser.utils.config as config
|
import qutebrowser.utils.config as config
|
||||||
|
|
||||||
|
|
||||||
|
def _get_search_url(txt):
|
||||||
|
"""Return a search engine URL (QUrl) for a text."""
|
||||||
|
logging.debug('Finding search engine for "{}"'.format(txt))
|
||||||
|
r = re.compile(r'(^|\s+)!(\w+)($|\s+)')
|
||||||
|
m = r.search(txt)
|
||||||
|
if m:
|
||||||
|
engine = m.group(2)
|
||||||
|
# FIXME why doesn't fallback work?!
|
||||||
|
template = config.config.get('searchengines', engine, fallback=None)
|
||||||
|
term = r.sub('', txt)
|
||||||
|
logging.debug('engine {}, term "{}"'.format(engine, term))
|
||||||
|
else:
|
||||||
|
template = config.config.get('searchengines', 'DEFAULT',
|
||||||
|
fallback=None)
|
||||||
|
term = txt
|
||||||
|
logging.debug('engine: default, term "{}"'.format(txt))
|
||||||
|
if template is None or not term:
|
||||||
|
raise ValueError
|
||||||
|
# pylint: disable=maybe-no-member
|
||||||
|
return QUrl.fromUserInput(template.format(urllib.parse.quote(term)))
|
||||||
|
|
||||||
|
|
||||||
|
def _is_url_naive(url):
|
||||||
|
"""Naive check if given url (QUrl) is really an url."""
|
||||||
|
PROTOCOLS = ['http://', 'https://']
|
||||||
|
u = urlstring(url)
|
||||||
|
return (any(u.startswith(proto) for proto in PROTOCOLS) or '.' in u or
|
||||||
|
u == 'localhost')
|
||||||
|
|
||||||
|
|
||||||
|
def _is_url_dns(url):
|
||||||
|
"""Check if an url (QUrl) is really an url via DNS."""
|
||||||
|
# FIXME we could probably solve this in a nicer way by attempting to open
|
||||||
|
# the page in the webview, and then open the search if that fails.
|
||||||
|
host = url.host()
|
||||||
|
logging.debug("DNS request for {}".format(host))
|
||||||
|
if not host:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
socket.gethostbyname(host)
|
||||||
|
except socket.gaierror:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def qurl(url):
|
def qurl(url):
|
||||||
"""Get a QUrl from an url string."""
|
"""Get a QUrl from an url string."""
|
||||||
return url if isinstance(url, QUrl) else QUrl(url)
|
return url if isinstance(url, QUrl) else QUrl(url)
|
||||||
@ -49,7 +95,7 @@ def fuzzy_url(url):
|
|||||||
"""
|
"""
|
||||||
u = qurl(url)
|
u = qurl(url)
|
||||||
urlstr = urlstring(url)
|
urlstr = urlstring(url)
|
||||||
if (not config.config.getboolean('general', 'auto_search')) or is_url(u):
|
if is_url(u):
|
||||||
# probably an address
|
# probably an address
|
||||||
logging.debug("url is a fuzzy address")
|
logging.debug("url is a fuzzy address")
|
||||||
newurl = QUrl.fromUserInput(urlstr)
|
newurl = QUrl.fromUserInput(urlstr)
|
||||||
@ -64,27 +110,6 @@ def fuzzy_url(url):
|
|||||||
return newurl
|
return newurl
|
||||||
|
|
||||||
|
|
||||||
def _get_search_url(txt):
|
|
||||||
"""Return a search engine URL (QUrl) for a text."""
|
|
||||||
logging.debug('Finding search engine for "{}"'.format(txt))
|
|
||||||
r = re.compile(r'(^|\s+)!(\w+)($|\s+)')
|
|
||||||
m = r.search(txt)
|
|
||||||
if m:
|
|
||||||
engine = m.group(2)
|
|
||||||
# FIXME why doesn't fallback work?!
|
|
||||||
template = config.config.get('searchengines', engine, fallback=None)
|
|
||||||
term = r.sub('', txt)
|
|
||||||
logging.debug('engine {}, term "{}"'.format(engine, term))
|
|
||||||
else:
|
|
||||||
template = config.config.get('searchengines', 'DEFAULT',
|
|
||||||
fallback=None)
|
|
||||||
term = txt
|
|
||||||
logging.debug('engine: default, term "{}"'.format(txt))
|
|
||||||
if template is None or not term:
|
|
||||||
raise ValueError
|
|
||||||
return QUrl.fromUserInput(template.format(urllib.parse.quote(term)))
|
|
||||||
|
|
||||||
|
|
||||||
def is_about_url(url):
|
def is_about_url(url):
|
||||||
"""Return True if url (QUrl) is an about:... or other special URL."""
|
"""Return True if url (QUrl) is an about:... or other special URL."""
|
||||||
return urlstring(url).replace('http://', '').startswith('about:')
|
return urlstring(url).replace('http://', '').startswith('about:')
|
||||||
@ -93,38 +118,37 @@ def is_about_url(url):
|
|||||||
def is_url(url):
|
def is_url(url):
|
||||||
"""Return True if url (QUrl) seems to be a valid URL."""
|
"""Return True if url (QUrl) seems to be a valid URL."""
|
||||||
urlstr = urlstring(url)
|
urlstr = urlstring(url)
|
||||||
logging.debug('Checking if "{}" is an URL'.format(urlstr))
|
|
||||||
|
try:
|
||||||
|
autosearch = config.config.getboolean('general', 'auto_search')
|
||||||
|
except ValueError:
|
||||||
|
autosearch = config.config.get('general', 'auto_search')
|
||||||
|
else:
|
||||||
|
if autosearch:
|
||||||
|
autosearch = 'naive'
|
||||||
|
else:
|
||||||
|
autosearch = None
|
||||||
|
|
||||||
|
logging.debug('Checking if "{}" is an URL (autosearch={}).'.format(
|
||||||
|
urlstr, autosearch))
|
||||||
|
|
||||||
|
if autosearch is None:
|
||||||
|
# no autosearch, so everything is an URL.
|
||||||
|
return True
|
||||||
|
|
||||||
if ' ' in urlstr:
|
if ' ' in urlstr:
|
||||||
# An URL will never contain a space
|
# An URL will never contain a space
|
||||||
logging.debug('Contains space -> no url')
|
logging.debug('Contains space -> no url')
|
||||||
return False
|
return False
|
||||||
elif config.config.getboolean('general', 'addressbar_dns_lookup'):
|
elif is_about_url(url):
|
||||||
|
# About URLs are always URLs, even with autosearch=False
|
||||||
|
logging.debug('Is an about URL.')
|
||||||
|
return True
|
||||||
|
elif autosearch == 'dns':
|
||||||
logging.debug('Checking via DNS')
|
logging.debug('Checking via DNS')
|
||||||
return _is_url_dns(QUrl.fromUserInput(urlstr))
|
return _is_url_dns(QUrl.fromUserInput(urlstr))
|
||||||
else:
|
elif autosearch == 'naive':
|
||||||
logging.debug('Checking via naive check')
|
logging.debug('Checking via naive check')
|
||||||
return _is_url_naive(url)
|
return _is_url_naive(url)
|
||||||
|
|
||||||
|
|
||||||
def _is_url_naive(url):
|
|
||||||
"""Naive check if given url (QUrl) is really an url."""
|
|
||||||
PROTOCOLS = ['http://', 'https://']
|
|
||||||
u = urlstring(url)
|
|
||||||
return (any(u.startswith(proto) for proto in PROTOCOLS) or '.' in u or
|
|
||||||
is_about_url(url) or u == 'localhost')
|
|
||||||
|
|
||||||
|
|
||||||
def _is_url_dns(url):
|
|
||||||
"""Check if an url (QUrl) is really an url via DNS."""
|
|
||||||
# FIXME we could probably solve this in a nicer way by attempting to open
|
|
||||||
# the page in the webview, and then open the search if that fails.
|
|
||||||
host = url.host()
|
|
||||||
logging.debug("DNS request for {}".format(host))
|
|
||||||
if not host:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
socket.gethostbyname(host)
|
|
||||||
except socket.gaierror:
|
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
return True
|
raise ValueError("Invalid autosearch value")
|
||||||
|
@ -28,6 +28,29 @@ from PyQt5.QtWebKit import qWebKitVersion
|
|||||||
import qutebrowser
|
import qutebrowser
|
||||||
|
|
||||||
|
|
||||||
|
def _git_str():
|
||||||
|
"""Try to find out git version and return a string if possible.
|
||||||
|
|
||||||
|
Return None if there was an error or we're not in a git repo.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if hasattr(sys, "frozen"):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
gitpath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||||
|
os.path.pardir, os.path.pardir)
|
||||||
|
except NameError:
|
||||||
|
return None
|
||||||
|
if not os.path.isdir(os.path.join(gitpath, ".git")):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(
|
||||||
|
['git', 'describe', '--tags', '--dirty', '--always'],
|
||||||
|
cwd=gitpath).decode('UTF-8').strip()
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def version():
|
def version():
|
||||||
"""Return a string with various version informations."""
|
"""Return a string with various version informations."""
|
||||||
if sys.platform == 'linux':
|
if sys.platform == 'linux':
|
||||||
@ -56,26 +79,3 @@ def version():
|
|||||||
lines.append('\nGit commit: {}'.format(gitver))
|
lines.append('\nGit commit: {}'.format(gitver))
|
||||||
|
|
||||||
return ''.join(lines)
|
return ''.join(lines)
|
||||||
|
|
||||||
|
|
||||||
def _git_str():
|
|
||||||
"""Try to find out git version and return a string if possible.
|
|
||||||
|
|
||||||
Return None if there was an error or we're not in a git repo.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if hasattr(sys, "frozen"):
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
gitpath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|
||||||
os.path.pardir, os.path.pardir)
|
|
||||||
except NameError:
|
|
||||||
return None
|
|
||||||
if not os.path.isdir(os.path.join(gitpath, ".git")):
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return subprocess.check_output(
|
|
||||||
['git', 'describe', '--tags', '--dirty', '--always'],
|
|
||||||
cwd=gitpath).decode('UTF-8').strip()
|
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
||||||
return None
|
|
||||||
|
@ -56,23 +56,40 @@ class TabbedBrowser(TabWidget):
|
|||||||
- the signal gets filtered with _filter_signals and self.cur_* gets
|
- the signal gets filtered with _filter_signals and self.cur_* gets
|
||||||
emitted if the signal occured in the current tab.
|
emitted if the signal occured in the current tab.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_url_stack: Stack of URLs of closed tabs.
|
||||||
|
_space: Space QShortcut to avoid garbage collection
|
||||||
|
_tabs: A list of open tabs.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
cur_progress: Progress of the current tab changed (loadProgress).
|
||||||
|
cur_load_started: Current tab started loading (loadStarted)
|
||||||
|
cur_load_finished: Current tab finished loading (loadFinished)
|
||||||
|
cur_statusbar_message: Current tab got a statusbar message
|
||||||
|
(statusBarMessage)
|
||||||
|
cur_url_changed: Current URL changed (urlChanged)
|
||||||
|
cur_link_hovered: Link hovered in current tab (linkHovered)
|
||||||
|
cur_scroll_perc_changed: Scroll percentage of current tab changed.
|
||||||
|
arg 1: x-position in %.
|
||||||
|
arg 2: y-position in %.
|
||||||
|
keypress: A key was pressed.
|
||||||
|
arg: The QKeyEvent leading to the keypress.
|
||||||
|
shutdown_complete: The shuttdown is completed.
|
||||||
|
quit: The last tab was closed, quit application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cur_progress = pyqtSignal(int) # Progress of the current tab changed
|
cur_progress = pyqtSignal(int)
|
||||||
cur_load_started = pyqtSignal() # Current tab started loading
|
cur_load_started = pyqtSignal()
|
||||||
cur_load_finished = pyqtSignal(bool) # Current tab finished loading
|
cur_load_finished = pyqtSignal(bool)
|
||||||
cur_statusbar_message = pyqtSignal(str) # Status bar message
|
cur_statusbar_message = pyqtSignal(str)
|
||||||
cur_url_changed = pyqtSignal('QUrl') # Current URL changed
|
cur_url_changed = pyqtSignal('QUrl')
|
||||||
cur_link_hovered = pyqtSignal(str, str, str) # Link hovered in cur tab
|
cur_link_hovered = pyqtSignal(str, str, str)
|
||||||
# Current tab changed scroll position
|
|
||||||
cur_scroll_perc_changed = pyqtSignal(int, int)
|
cur_scroll_perc_changed = pyqtSignal(int, int)
|
||||||
set_cmd_text = pyqtSignal(str) # Set commandline to a given text
|
set_cmd_text = pyqtSignal(str)
|
||||||
keypress = pyqtSignal('QKeyEvent')
|
keypress = pyqtSignal('QKeyEvent')
|
||||||
shutdown_complete = pyqtSignal() # All tabs have been shut down.
|
shutdown_complete = pyqtSignal()
|
||||||
quit = pyqtSignal() # Last tab closed, quit application.
|
quit = pyqtSignal()
|
||||||
_url_stack = [] # Stack of URLs of closed tabs
|
|
||||||
_space = None # Space QShortcut
|
|
||||||
_tabs = None
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -171,9 +188,10 @@ class TabbedBrowser(TabWidget):
|
|||||||
try:
|
try:
|
||||||
self._tabs.remove(tab)
|
self._tabs.remove(tab)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.error("tab {} could not be removed from tabs {}.".format(
|
logging.exception("tab {} could not be removed".format(tab))
|
||||||
tab, self._tabs))
|
logging.debug("Tabs after removing: {}".format(self._tabs))
|
||||||
if not self._tabs: # all tabs shut down
|
if not self._tabs: # all tabs shut down
|
||||||
|
logging.debug("Tab shutdown complete.")
|
||||||
self.shutdown_complete.emit()
|
self.shutdown_complete.emit()
|
||||||
|
|
||||||
def cur_reload(self, count=None):
|
def cur_reload(self, count=None):
|
||||||
@ -205,7 +223,7 @@ class TabbedBrowser(TabWidget):
|
|||||||
# FIXME that does not what I expect
|
# FIXME that does not what I expect
|
||||||
tab = self._widget(count)
|
tab = self._widget(count)
|
||||||
if tab is not None:
|
if tab is not None:
|
||||||
preview = QPrintPreviewDialog()
|
preview = QPrintPreviewDialog(self)
|
||||||
preview.paintRequested.connect(tab.print)
|
preview.paintRequested.connect(tab.print)
|
||||||
preview.exec_()
|
preview.exec_()
|
||||||
|
|
||||||
@ -451,12 +469,12 @@ class TabbedBrowser(TabWidget):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
tabcount = self.count()
|
tabcount = self.count()
|
||||||
logging.debug("Shutting down {} tabs...".format(tabcount))
|
|
||||||
if tabcount == 0:
|
if tabcount == 0:
|
||||||
|
logging.debug("No tabs -> shutdown complete")
|
||||||
self.shutdown_complete.emit()
|
self.shutdown_complete.emit()
|
||||||
return
|
return
|
||||||
for tabidx in range(tabcount):
|
for tabidx in range(tabcount):
|
||||||
logging.debug("shutdown {}".format(tabidx))
|
logging.debug("Shutting down tab {}/{}".format(tabidx, tabcount))
|
||||||
tab = self.widget(tabidx)
|
tab = self.widget(tabidx)
|
||||||
tab.shutdown(callback=functools.partial(self._cb_tab_shutdown,
|
tab.shutdown(callback=functools.partial(self._cb_tab_shutdown,
|
||||||
tab))
|
tab))
|
||||||
@ -468,30 +486,39 @@ class BrowserTab(QWebView):
|
|||||||
|
|
||||||
Our own subclass of a QWebView with some added bells and whistles.
|
Our own subclass of a QWebView with some added bells and whistles.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
page_: The QWebPage behind the view
|
||||||
|
signal_cache: The signal cache associated with the view.
|
||||||
|
_scroll_pos: The old scroll position.
|
||||||
|
_shutdown_callback: Callback to be called after shutdown.
|
||||||
|
_open_new_tab: Whether to open a new tab for the next action.
|
||||||
|
_shutdown_callback: The callback to call after shutting down.
|
||||||
|
_destroyed: Dict of all items to be destroyed on shtudown.
|
||||||
|
Signals:
|
||||||
|
scroll_pos_changed: Scroll percentage of current tab changed.
|
||||||
|
arg 1: x-position in %.
|
||||||
|
arg 2: y-position in %.
|
||||||
|
open_tab: A new tab should be opened.
|
||||||
|
arg: The address to open
|
||||||
|
linkHovered: QWebPages linkHovered signal exposed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
progress = 0
|
|
||||||
scroll_pos_changed = pyqtSignal(int, int)
|
scroll_pos_changed = pyqtSignal(int, int)
|
||||||
open_tab = pyqtSignal('QUrl')
|
open_tab = pyqtSignal('QUrl')
|
||||||
linkHovered = pyqtSignal(str, str, str)
|
linkHovered = pyqtSignal(str, str, str)
|
||||||
_scroll_pos = (-1, -1)
|
|
||||||
_shutdown_callback = None # callback to be called after shutdown
|
|
||||||
_open_new_tab = False # open new tab for the next action
|
|
||||||
_destroyed = None # Dict of all items to be destroyed.
|
|
||||||
page_ = None # QWebPage
|
|
||||||
# dict of tab specific signals, and the values we last got from them.
|
|
||||||
signal_cache = None
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self._scroll_pos = (-1, -1)
|
||||||
|
self._shutdown_callback = None
|
||||||
|
self._open_new_tab = False
|
||||||
self._destroyed = {}
|
self._destroyed = {}
|
||||||
self.page_ = BrowserPage(self)
|
self.page_ = BrowserPage(self)
|
||||||
self.setPage(self.page_)
|
self.setPage(self.page_)
|
||||||
self.signal_cache = SignalCache(uncached=['linkHovered'])
|
self.signal_cache = SignalCache(uncached=['linkHovered'])
|
||||||
self.loadProgress.connect(self.on_load_progress)
|
|
||||||
self.page_.setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
self.page_.setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
||||||
self.page_.linkHovered.connect(self.linkHovered)
|
self.page_.linkHovered.connect(self.linkHovered)
|
||||||
self.installEventFilter(self)
|
|
||||||
self.linkClicked.connect(self.on_link_clicked)
|
self.linkClicked.connect(self.on_link_clicked)
|
||||||
# FIXME find some way to hide scrollbars without setScrollBarPolicy
|
# FIXME find some way to hide scrollbars without setScrollBarPolicy
|
||||||
|
|
||||||
@ -531,17 +558,6 @@ class BrowserTab(QWebView):
|
|||||||
else:
|
else:
|
||||||
self.openurl(url)
|
self.openurl(url)
|
||||||
|
|
||||||
@pyqtSlot(int)
|
|
||||||
def on_load_progress(self, prog):
|
|
||||||
"""Update the progress property if the loading progress changed.
|
|
||||||
|
|
||||||
Slot for the loadProgress signal.
|
|
||||||
|
|
||||||
prog -- New progress.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.progress = prog
|
|
||||||
|
|
||||||
def shutdown(self, callback=None):
|
def shutdown(self, callback=None):
|
||||||
"""Shut down the tab cleanly and remove it.
|
"""Shut down the tab cleanly and remove it.
|
||||||
|
|
||||||
@ -561,40 +577,43 @@ class BrowserTab(QWebView):
|
|||||||
self.settings().setAttribute(QWebSettings.JavascriptEnabled, False)
|
self.settings().setAttribute(QWebSettings.JavascriptEnabled, False)
|
||||||
|
|
||||||
self._destroyed[self.page_] = False
|
self._destroyed[self.page_] = False
|
||||||
self.page_.destroyed.connect(functools.partial(self.on_destroyed,
|
self.page_.destroyed.connect(functools.partial(self._on_destroyed,
|
||||||
self.page_))
|
self.page_))
|
||||||
self.page_.deleteLater()
|
self.page_.deleteLater()
|
||||||
|
|
||||||
self._destroyed[self] = False
|
self._destroyed[self] = False
|
||||||
self.destroyed.connect(functools.partial(self.on_destroyed, self))
|
self.destroyed.connect(functools.partial(self._on_destroyed, self))
|
||||||
self.deleteLater()
|
self.deleteLater()
|
||||||
|
|
||||||
netman = self.page_.network_access_manager
|
netman = self.page_.network_access_manager
|
||||||
self._destroyed[netman] = False
|
self._destroyed[netman] = False
|
||||||
netman.abort_requests()
|
netman.abort_requests()
|
||||||
netman.destroyed.connect(functools.partial(self.on_destroyed, netman))
|
netman.destroyed.connect(functools.partial(self._on_destroyed, netman))
|
||||||
netman.deleteLater()
|
netman.deleteLater()
|
||||||
logging.debug("Shutdown scheduled")
|
logging.debug("Tab shutdown scheduled")
|
||||||
|
|
||||||
def on_destroyed(self, sender):
|
def _on_destroyed(self, sender):
|
||||||
"""Called when a subsystem has been destroyed during shutdown."""
|
"""Called when a subsystem has been destroyed during shutdown."""
|
||||||
self._destroyed[sender] = True
|
self._destroyed[sender] = True
|
||||||
|
dbgout = '\n'.join(['{}: {}'.format(k.__class__.__name__, v)
|
||||||
|
for (k, v) in self._destroyed.items()])
|
||||||
|
logging.debug("{} has been destroyed, new status:\n{}".format(
|
||||||
|
sender.__class__.__name__, dbgout))
|
||||||
if all(self._destroyed.values()):
|
if all(self._destroyed.values()):
|
||||||
if self._shutdown_callback is not None:
|
if self._shutdown_callback is not None:
|
||||||
|
logging.debug("Everything destroyed, calling callback")
|
||||||
self._shutdown_callback()
|
self._shutdown_callback()
|
||||||
|
|
||||||
def eventFilter(self, watched, e):
|
def paintEvent(self, e):
|
||||||
"""Dirty hack to emit a signal if the scroll position changed.
|
"""Extend paintEvent to emit a signal if the scroll position changed.
|
||||||
|
|
||||||
We listen to repaint requests here, in the hope a repaint will always
|
This is a bit of a hack: We listen to repaint requests here, in the
|
||||||
be requested when scrolling, and if the scroll position actually
|
hope a repaint will always be requested when scrolling, and if the
|
||||||
changed, we emit a signal.
|
scroll position actually changed, we emit a signal.
|
||||||
|
|
||||||
watched -- The watched Qt object.
|
e -- The QPaintEvent.
|
||||||
e -- The new event.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if watched == self and e.type() == QEvent.Paint:
|
|
||||||
frame = self.page_.mainFrame()
|
frame = self.page_.mainFrame()
|
||||||
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
||||||
frame.scrollBarValue(Qt.Vertical))
|
frame.scrollBarValue(Qt.Vertical))
|
||||||
@ -607,9 +626,8 @@ class BrowserTab(QWebView):
|
|||||||
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
||||||
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
||||||
self.scroll_pos_changed.emit(*perc)
|
self.scroll_pos_changed.emit(*perc)
|
||||||
# we're not actually filtering something, let superclass handle the
|
# Let superclass handle the event
|
||||||
# event
|
return super().paintEvent(e)
|
||||||
return super().eventFilter(watched, e)
|
|
||||||
|
|
||||||
def event(self, e):
|
def event(self, e):
|
||||||
"""Check if a link was clicked with the middle button or Ctrl.
|
"""Check if a link was clicked with the middle button or Ctrl.
|
||||||
@ -630,10 +648,13 @@ class BrowserTab(QWebView):
|
|||||||
|
|
||||||
class BrowserPage(QWebPage):
|
class BrowserPage(QWebPage):
|
||||||
|
|
||||||
"""Our own QWebPage with advanced features."""
|
"""Our own QWebPage with advanced features.
|
||||||
|
|
||||||
_extension_handlers = None
|
Attributes:
|
||||||
network_access_manager = None
|
_extension_handlers: Mapping of QWebPage extensions to their handlers.
|
||||||
|
network_access_manager: The QNetworkAccessManager used.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -677,9 +698,12 @@ class BrowserPage(QWebPage):
|
|||||||
|
|
||||||
class NetworkManager(QNetworkAccessManager):
|
class NetworkManager(QNetworkAccessManager):
|
||||||
|
|
||||||
"""Our own QNetworkAccessManager."""
|
"""Our own QNetworkAccessManager.
|
||||||
|
|
||||||
_requests = None
|
Attributes:
|
||||||
|
_requests: Pending requests.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
self._requests = {}
|
self._requests = {}
|
||||||
|
@ -45,9 +45,22 @@ class CompletionView(QTreeView):
|
|||||||
|
|
||||||
Highlights completions based on marks in the UserRole.
|
Highlights completions based on marks in the UserRole.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_STYLESHEET: The stylesheet template for the CompletionView.
|
||||||
|
_completion_models: dict of available completion models.
|
||||||
|
_ignore_next: Whether to ignore the next cmd_text_changed signal.
|
||||||
|
_enabled: Whether showing the CompletionView is enabled.
|
||||||
|
_completing: Whether we're currently completing something.
|
||||||
|
_height: The height to use for the CompletionView.
|
||||||
|
_delegate: The item delegate used.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
append_cmd_text: Command text which should be appended to the
|
||||||
|
statusbar.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_stylesheet = """
|
_STYLESHEET = """
|
||||||
QTreeView {{
|
QTreeView {{
|
||||||
{font[completion]}
|
{font[completion]}
|
||||||
{color[completion.fg]}
|
{color[completion.fg]}
|
||||||
@ -77,25 +90,24 @@ class CompletionView(QTreeView):
|
|||||||
# like one anymore
|
# like one anymore
|
||||||
# FIXME somehow only the first column is yellow, even with
|
# FIXME somehow only the first column is yellow, even with
|
||||||
# setAllColumnsShowFocus
|
# setAllColumnsShowFocus
|
||||||
completion_models = {}
|
|
||||||
append_cmd_text = pyqtSignal(str)
|
append_cmd_text = pyqtSignal(str)
|
||||||
ignore_next = False
|
|
||||||
enabled = True
|
|
||||||
completing = False
|
|
||||||
height = QPoint(0, 200)
|
|
||||||
_delegate = None
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.enabled = config.config.getboolean('general', 'show_completion')
|
self._height = QPoint(0, 200) # FIXME make that configurable
|
||||||
self.completion_models[''] = None
|
self._enabled = config.config.getboolean('general', 'show_completion')
|
||||||
self.completion_models['command'] = CommandCompletionModel()
|
self._completion_models = {}
|
||||||
|
self._completion_models[''] = None
|
||||||
|
self._completion_models['command'] = CommandCompletionModel()
|
||||||
|
self._ignore_next = False
|
||||||
|
self._completing = False
|
||||||
|
|
||||||
self.model = CompletionFilterModel()
|
self.model = CompletionFilterModel()
|
||||||
self.setModel(self.model)
|
self.setModel(self.model)
|
||||||
self.setmodel('command')
|
self.setmodel('command')
|
||||||
self._delegate = CompletionItemDelegate(self)
|
self._delegate = _CompletionItemDelegate(self)
|
||||||
self.setItemDelegate(self._delegate)
|
self.setItemDelegate(self._delegate)
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
self.setStyleSheet(config.get_stylesheet(self._STYLESHEET))
|
||||||
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum)
|
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum)
|
||||||
self.setHeaderHidden(True)
|
self.setHeaderHidden(True)
|
||||||
self.setIndentation(0)
|
self.setIndentation(0)
|
||||||
@ -117,7 +129,7 @@ class CompletionView(QTreeView):
|
|||||||
model -- A QAbstractItemModel with available completions.
|
model -- A QAbstractItemModel with available completions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.model.setsrc(self.completion_models[model])
|
self.model.srcmodel = self._completion_models[model]
|
||||||
self.expandAll()
|
self.expandAll()
|
||||||
self.resizeColumnToContents(0)
|
self.resizeColumnToContents(0)
|
||||||
|
|
||||||
@ -131,7 +143,7 @@ class CompletionView(QTreeView):
|
|||||||
"""
|
"""
|
||||||
bottomleft = geom.topLeft()
|
bottomleft = geom.topLeft()
|
||||||
bottomright = geom.topRight()
|
bottomright = geom.topRight()
|
||||||
topleft = bottomleft - self.height
|
topleft = bottomleft - self._height
|
||||||
assert topleft.x() < bottomright.x()
|
assert topleft.x() < bottomright.x()
|
||||||
assert topleft.y() < bottomright.y()
|
assert topleft.y() < bottomright.y()
|
||||||
self.setGeometry(QRect(topleft, bottomright))
|
self.setGeometry(QRect(topleft, bottomright))
|
||||||
@ -144,7 +156,7 @@ class CompletionView(QTreeView):
|
|||||||
pos -- A QPoint containing the statusbar position.
|
pos -- A QPoint containing the statusbar position.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.move(pos - self.height)
|
self.move(pos - self._height)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def on_cmd_text_changed(self, text):
|
def on_cmd_text_changed(self, text):
|
||||||
@ -154,22 +166,22 @@ class CompletionView(QTreeView):
|
|||||||
text -- The new text
|
text -- The new text
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.ignore_next:
|
if self._ignore_next:
|
||||||
# Text changed by a completion, so we don't have to complete again.
|
# Text changed by a completion, so we don't have to complete again.
|
||||||
self.ignore_next = False
|
self._ignore_next = False
|
||||||
return
|
return
|
||||||
# FIXME more sophisticated completions
|
# FIXME more sophisticated completions
|
||||||
if ' ' in text or not text.startswith(':'):
|
if ' ' in text or not text.startswith(':'):
|
||||||
self.hide()
|
self.hide()
|
||||||
self.completing = False
|
self._completing = False
|
||||||
return
|
return
|
||||||
|
|
||||||
self.completing = True
|
self._completing = True
|
||||||
self.setmodel('command')
|
self.setmodel('command')
|
||||||
text = text.lstrip(':')
|
text = text.lstrip(':')
|
||||||
self.model.pattern = text
|
self.model.pattern = text
|
||||||
self.model.srcmodel.mark_all_items(text)
|
self.model.srcmodel.mark_all_items(text)
|
||||||
if self.enabled:
|
if self._enabled:
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
@pyqtSlot(bool)
|
@pyqtSlot(bool)
|
||||||
@ -182,7 +194,7 @@ class CompletionView(QTreeView):
|
|||||||
shift -- Whether shift is pressed or not.
|
shift -- Whether shift is pressed or not.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.completing:
|
if not self._completing:
|
||||||
# No completion running at the moment, ignore keypress
|
# No completion running at the moment, ignore keypress
|
||||||
return
|
return
|
||||||
idx = self._next_idx(shift)
|
idx = self._next_idx(shift)
|
||||||
@ -190,7 +202,7 @@ class CompletionView(QTreeView):
|
|||||||
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._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, upwards):
|
def _next_idx(self, upwards):
|
||||||
@ -217,7 +229,7 @@ class CompletionView(QTreeView):
|
|||||||
return idx
|
return idx
|
||||||
|
|
||||||
|
|
||||||
class CompletionItemDelegate(QStyledItemDelegate):
|
class _CompletionItemDelegate(QStyledItemDelegate):
|
||||||
|
|
||||||
"""Delegate used by CompletionView to draw individual items.
|
"""Delegate used by CompletionView to draw individual items.
|
||||||
|
|
||||||
@ -227,12 +239,20 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
Original implementation:
|
Original implementation:
|
||||||
qt/src/gui/styles/qcommonstyle.cpp:drawControl:2153
|
qt/src/gui/styles/qcommonstyle.cpp:drawControl:2153
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_opt: The QStyleOptionViewItem which is used.
|
||||||
|
_style: The style to be used.
|
||||||
|
_painter: The QPainter to be used.
|
||||||
|
_doc: The QTextDocument to be used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
opt = None
|
def __init__(self, parent=None):
|
||||||
style = None
|
self._painter = None
|
||||||
painter = None
|
self._opt = None
|
||||||
doc = None
|
self._doc = None
|
||||||
|
self._style = None
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
def sizeHint(self, option, index):
|
def sizeHint(self, option, index):
|
||||||
"""Override sizeHint of QStyledItemDelegate.
|
"""Override sizeHint of QStyledItemDelegate.
|
||||||
@ -244,49 +264,48 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
value = index.data(Qt.SizeHintRole)
|
value = index.data(Qt.SizeHintRole)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
return value
|
return value
|
||||||
self.opt = QStyleOptionViewItem(option)
|
self._opt = QStyleOptionViewItem(option)
|
||||||
self.initStyleOption(self.opt, index)
|
self.initStyleOption(self._opt, index)
|
||||||
self.style = self.opt.widget.style()
|
self._style = self._opt.widget.style()
|
||||||
self._get_textdoc(index)
|
self._get_textdoc(index)
|
||||||
docsize = self.doc.size().toSize()
|
docsize = self._doc.size().toSize()
|
||||||
size = self.style.sizeFromContents(QStyle.CT_ItemViewItem, self.opt,
|
size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt,
|
||||||
docsize, self.opt.widget)
|
docsize, self._opt.widget)
|
||||||
return size + QSize(10, 1)
|
return size + QSize(10, 1)
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
"""Override the QStyledItemDelegate paint function."""
|
"""Override the QStyledItemDelegate paint function."""
|
||||||
painter.save()
|
self._painter = painter
|
||||||
|
self._painter.save()
|
||||||
self.painter = painter
|
self._opt = QStyleOptionViewItem(option)
|
||||||
self.opt = QStyleOptionViewItem(option)
|
self.initStyleOption(self._opt, index)
|
||||||
self.initStyleOption(self.opt, index)
|
self._style = self._opt.widget.style()
|
||||||
self.style = self.opt.widget.style()
|
|
||||||
|
|
||||||
self._draw_background()
|
self._draw_background()
|
||||||
self._draw_icon()
|
self._draw_icon()
|
||||||
self._draw_text(index)
|
self._draw_text(index)
|
||||||
self._draw_focus_rect()
|
self._draw_focus_rect()
|
||||||
|
|
||||||
painter.restore()
|
self._painter.restore()
|
||||||
|
|
||||||
def _draw_background(self):
|
def _draw_background(self):
|
||||||
"""Draw the background of an ItemViewItem."""
|
"""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."""
|
"""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)
|
||||||
|
|
||||||
mode = QIcon.Normal
|
mode = QIcon.Normal
|
||||||
if not self.opt.state & QStyle.State_Enabled:
|
if not self._opt.state & QStyle.State_Enabled:
|
||||||
mode = QIcon.Disabled
|
mode = QIcon.Disabled
|
||||||
elif self.opt.state & QStyle.State_Selected:
|
elif self._opt.state & QStyle.State_Selected:
|
||||||
mode = QIcon.Selected
|
mode = QIcon.Selected
|
||||||
state = QIcon.On if self.opt.state & QStyle.State_Open else QIcon.Off
|
state = QIcon.On if self._opt.state & QStyle.State_Open else QIcon.Off
|
||||||
self.opt.icon.paint(self.painter, icon_rect,
|
self._opt.icon.paint(self._painter, icon_rect,
|
||||||
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.
|
"""Draw the text of an ItemViewItem.
|
||||||
@ -297,13 +316,13 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
index of the item of the item -- The QModelIndex of the item to draw.
|
index of the item of the item -- The QModelIndex of the item to draw.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.opt.text:
|
if not self._opt.text:
|
||||||
return
|
return
|
||||||
|
|
||||||
text_rect_ = self.style.subElementRect(self.style.SE_ItemViewItemText,
|
text_rect_ = self._style.subElementRect(
|
||||||
self.opt, self.opt.widget)
|
self._style.SE_ItemViewItemText, self._opt, self._opt.widget)
|
||||||
margin = self.style.pixelMetric(QStyle.PM_FocusFrameHMargin, self.opt,
|
margin = self._style.pixelMetric(QStyle.PM_FocusFrameHMargin,
|
||||||
self.opt.widget) + 1
|
self._opt, self._opt.widget) + 1
|
||||||
# remove width padding
|
# remove width padding
|
||||||
text_rect = text_rect_.adjusted(margin, 0, -margin, 0)
|
text_rect = text_rect_.adjusted(margin, 0, -margin, 0)
|
||||||
# move text upwards a bit
|
# move text upwards a bit
|
||||||
@ -311,8 +330,8 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
text_rect.adjust(0, -1, 0, -1)
|
text_rect.adjust(0, -1, 0, -1)
|
||||||
else:
|
else:
|
||||||
text_rect.adjust(0, -2, 0, -2)
|
text_rect.adjust(0, -2, 0, -2)
|
||||||
self.painter.save()
|
self._painter.save()
|
||||||
state = self.opt.state
|
state = self._opt.state
|
||||||
if state & QStyle.State_Enabled and state & QStyle.State_Active:
|
if state & QStyle.State_Enabled and state & QStyle.State_Active:
|
||||||
cg = QPalette.Normal
|
cg = QPalette.Normal
|
||||||
elif state & QStyle.State_Enabled:
|
elif state & QStyle.State_Enabled:
|
||||||
@ -321,22 +340,22 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
cg = QPalette.Disabled
|
cg = QPalette.Disabled
|
||||||
|
|
||||||
if state & QStyle.State_Selected:
|
if state & QStyle.State_Selected:
|
||||||
self.painter.setPen(self.opt.palette.color(
|
self._painter.setPen(self._opt.palette.color(
|
||||||
cg, QPalette.HighlightedText))
|
cg, QPalette.HighlightedText))
|
||||||
# FIXME this is a dirty fix for the text jumping by one pixel...
|
# FIXME this is a dirty fix for the text jumping by one pixel...
|
||||||
# we really should do this properly somehow
|
# we really should do this properly somehow
|
||||||
text_rect.adjust(0, -1, 0, 0)
|
text_rect.adjust(0, -1, 0, 0)
|
||||||
else:
|
else:
|
||||||
self.painter.setPen(self.opt.palette.color(cg, QPalette.Text))
|
self._painter.setPen(self._opt.palette.color(cg, QPalette.Text))
|
||||||
|
|
||||||
if state & QStyle.State_Editing:
|
if state & QStyle.State_Editing:
|
||||||
self.painter.setPen(self.opt.palette.color(cg, QPalette.Text))
|
self._painter.setPen(self._opt.palette.color(cg, QPalette.Text))
|
||||||
self.painter.drawRect(text_rect_.adjusted(0, 0, -1, -1))
|
self._painter.drawRect(text_rect_.adjusted(0, 0, -1, -1))
|
||||||
|
|
||||||
self.painter.translate(text_rect.left(), text_rect.top())
|
self._painter.translate(text_rect.left(), text_rect.top())
|
||||||
self._get_textdoc(index)
|
self._get_textdoc(index)
|
||||||
self._draw_textdoc(text_rect)
|
self._draw_textdoc(text_rect)
|
||||||
self.painter.restore()
|
self._painter.restore()
|
||||||
|
|
||||||
def _draw_textdoc(self, text_rect):
|
def _draw_textdoc(self, text_rect):
|
||||||
"""Draw the QTextDocument of an item.
|
"""Draw the QTextDocument of an item.
|
||||||
@ -345,7 +364,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
clip = QRectF(0, 0, text_rect.width(), text_rect.height())
|
clip = QRectF(0, 0, text_rect.width(), text_rect.height())
|
||||||
self.doc.drawContents(self.painter, clip)
|
self._doc.drawContents(self._painter, clip)
|
||||||
|
|
||||||
def _get_textdoc(self, index):
|
def _get_textdoc(self, index):
|
||||||
"""Create the QTextDocument of an item.
|
"""Create the QTextDocument of an item.
|
||||||
@ -356,32 +375,32 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
# FIXME we probably should do eliding here. See
|
# FIXME we probably should do eliding here. See
|
||||||
# qcommonstyle.cpp:viewItemDrawText
|
# qcommonstyle.cpp:viewItemDrawText
|
||||||
text_option = QTextOption()
|
text_option = QTextOption()
|
||||||
if self.opt.features & QStyleOptionViewItem.WrapText:
|
if self._opt.features & QStyleOptionViewItem.WrapText:
|
||||||
text_option.setWrapMode(QTextOption.WordWrap)
|
text_option.setWrapMode(QTextOption.WordWrap)
|
||||||
else:
|
else:
|
||||||
text_option.setWrapMode(QTextOption.ManualWrap)
|
text_option.setWrapMode(QTextOption.ManualWrap)
|
||||||
text_option.setTextDirection(self.opt.direction)
|
text_option.setTextDirection(self._opt.direction)
|
||||||
text_option.setAlignment(QStyle.visualAlignment(
|
text_option.setAlignment(QStyle.visualAlignment(
|
||||||
self.opt.direction, self.opt.displayAlignment))
|
self._opt.direction, self._opt.displayAlignment))
|
||||||
|
|
||||||
self.doc = QTextDocument()
|
self._doc = QTextDocument(self)
|
||||||
if index.parent().isValid():
|
if index.parent().isValid():
|
||||||
self.doc.setPlainText(self.opt.text)
|
self._doc.setPlainText(self._opt.text)
|
||||||
else:
|
else:
|
||||||
self.doc.setHtml('<b>{}</b>'.format(html.escape(self.opt.text)))
|
self._doc.setHtml('<b>{}</b>'.format(html.escape(self._opt.text)))
|
||||||
self.doc.setDefaultFont(self.opt.font)
|
self._doc.setDefaultFont(self._opt.font)
|
||||||
self.doc.setDefaultTextOption(text_option)
|
self._doc.setDefaultTextOption(text_option)
|
||||||
self.doc.setDefaultStyleSheet(config.get_stylesheet("""
|
self._doc.setDefaultStyleSheet(config.get_stylesheet("""
|
||||||
.highlight {{
|
.highlight {{
|
||||||
{color[completion.match.fg]}
|
{color[completion.match.fg]}
|
||||||
}}
|
}}
|
||||||
"""))
|
"""))
|
||||||
self.doc.setDocumentMargin(2)
|
self._doc.setDocumentMargin(2)
|
||||||
|
|
||||||
if index.column() == 0:
|
if index.column() == 0:
|
||||||
marks = index.data(Qt.UserRole)
|
marks = index.data(Qt.UserRole)
|
||||||
for mark in marks:
|
for mark in marks:
|
||||||
cur = QTextCursor(self.doc)
|
cur = QTextCursor(self._doc)
|
||||||
cur.setPosition(mark[0])
|
cur.setPosition(mark[0])
|
||||||
cur.setPosition(mark[1], QTextCursor.KeepAnchor)
|
cur.setPosition(mark[1], QTextCursor.KeepAnchor)
|
||||||
txt = cur.selectedText()
|
txt = cur.selectedText()
|
||||||
@ -391,12 +410,12 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
|
|
||||||
def _draw_focus_rect(self):
|
def _draw_focus_rect(self):
|
||||||
"""Draw the focus rectangle of an ItemViewItem."""
|
"""Draw 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
|
||||||
o = self.opt
|
o = self._opt
|
||||||
o.rect = self.style.subElementRect(self.style.SE_ItemViewItemFocusRect,
|
o.rect = self._style.subElementRect(
|
||||||
self.opt, self.opt.widget)
|
self._style.SE_ItemViewItemFocusRect, self._opt, self._opt.widget)
|
||||||
o.state |= QStyle.State_KeyboardFocusChange | QStyle.State_Item
|
o.state |= QStyle.State_KeyboardFocusChange | QStyle.State_Item
|
||||||
if state & QStyle.State_Enabled:
|
if state & QStyle.State_Enabled:
|
||||||
cg = QPalette.Normal
|
cg = QPalette.Normal
|
||||||
@ -406,6 +425,6 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
role = QPalette.Highlight
|
role = QPalette.Highlight
|
||||||
else:
|
else:
|
||||||
role = QPalette.Window
|
role = QPalette.Window
|
||||||
o.backgroundColor = self.opt.palette.color(cg, role)
|
o.backgroundColor = self._opt.palette.color(cg, role)
|
||||||
self.style.drawPrimitive(QStyle.PE_FrameFocusRect, o, self.painter,
|
self._style.drawPrimitive(QStyle.PE_FrameFocusRect, o, self._painter,
|
||||||
self.opt.widget)
|
self._opt.widget)
|
||||||
|
@ -29,14 +29,18 @@ from qutebrowser.utils.version import version
|
|||||||
|
|
||||||
class CrashDialog(QDialog):
|
class CrashDialog(QDialog):
|
||||||
|
|
||||||
"""Dialog which gets shown after there was a crash."""
|
"""Dialog which gets shown after there was a crash.
|
||||||
|
|
||||||
vbox = None
|
Attributes:
|
||||||
lbl = None
|
These are just here to have a static reference to avoid GCing.
|
||||||
txt = None
|
_vbox: The main QVBoxLayout
|
||||||
hbox = None
|
_lbl: The QLabel with the static text
|
||||||
btn_quit = None
|
_txt: The QTextEdit with the crash information
|
||||||
btn_restore = None
|
_hbox: The QHboxLayout containing the buttons
|
||||||
|
_btn_quit: The quit button
|
||||||
|
_btn_restore: the restore button
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, pages, cmdhist, exc):
|
def __init__(self, pages, cmdhist, exc):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -44,8 +48,8 @@ class CrashDialog(QDialog):
|
|||||||
self.setWindowTitle('Whoops!')
|
self.setWindowTitle('Whoops!')
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
|
|
||||||
self.vbox = QVBoxLayout()
|
self._vbox = QVBoxLayout(self)
|
||||||
self.lbl = QLabel(self)
|
self._lbl = QLabel()
|
||||||
text = ('Argh! qutebrowser crashed unexpectedly.<br/>'
|
text = ('Argh! qutebrowser crashed unexpectedly.<br/>'
|
||||||
'Please review the info below to remove sensitive data and '
|
'Please review the info below to remove sensitive data and '
|
||||||
'then submit it to <a href="mailto:crash@qutebrowser.org">'
|
'then submit it to <a href="mailto:crash@qutebrowser.org">'
|
||||||
@ -53,29 +57,29 @@ class CrashDialog(QDialog):
|
|||||||
if pages:
|
if pages:
|
||||||
text += ('You can click "Restore tabs" to attempt to reopen your '
|
text += ('You can click "Restore tabs" to attempt to reopen your '
|
||||||
'open tabs.')
|
'open tabs.')
|
||||||
self.lbl.setText(text)
|
self._lbl.setText(text)
|
||||||
self.lbl.setWordWrap(True)
|
self._lbl.setWordWrap(True)
|
||||||
self.vbox.addWidget(self.lbl)
|
self._vbox.addWidget(self._lbl)
|
||||||
|
|
||||||
self.txt = QTextEdit(self)
|
self._txt = QTextEdit()
|
||||||
self.txt.setReadOnly(True)
|
self._txt.setReadOnly(True)
|
||||||
self.txt.setText(self._crash_info(pages, cmdhist, exc))
|
self._txt.setText(self._crash_info(pages, cmdhist, exc))
|
||||||
self.vbox.addWidget(self.txt)
|
self._vbox.addWidget(self._txt)
|
||||||
self.setLayout(self.vbox)
|
|
||||||
|
|
||||||
self.hbox = QHBoxLayout()
|
self._hbox = QHBoxLayout()
|
||||||
self.btn_quit = QPushButton(self)
|
self._hbox.addStretch()
|
||||||
self.btn_quit.setText('Quit')
|
self._btn_quit = QPushButton()
|
||||||
self.btn_quit.clicked.connect(self.reject)
|
self._btn_quit.setText('Quit')
|
||||||
self.hbox.addWidget(self.btn_quit)
|
self._btn_quit.clicked.connect(self.reject)
|
||||||
|
self._hbox.addWidget(self._btn_quit)
|
||||||
if pages:
|
if pages:
|
||||||
self.btn_restore = QPushButton(self)
|
self._btn_restore = QPushButton()
|
||||||
self.btn_restore.setText('Restore tabs')
|
self._btn_restore.setText('Restore tabs')
|
||||||
self.btn_restore.clicked.connect(self.accept)
|
self._btn_restore.clicked.connect(self.accept)
|
||||||
self.btn_restore.setDefault(True)
|
self._btn_restore.setDefault(True)
|
||||||
self.hbox.addWidget(self.btn_restore)
|
self._hbox.addWidget(self._btn_restore)
|
||||||
|
|
||||||
self.vbox.addLayout(self.hbox)
|
self._vbox.addLayout(self._hbox)
|
||||||
|
|
||||||
def _crash_info(self, pages, cmdhist, exc):
|
def _crash_info(self, pages, cmdhist, exc):
|
||||||
"""Gather crash information to display."""
|
"""Gather crash information to display."""
|
||||||
|
@ -17,11 +17,16 @@
|
|||||||
|
|
||||||
"""The main window of QuteBrowser."""
|
"""The main window of QuteBrowser."""
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QRect
|
||||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||||
|
|
||||||
from qutebrowser.widgets.statusbar import StatusBar
|
from qutebrowser.widgets.statusbar import StatusBar
|
||||||
from qutebrowser.widgets.browser import TabbedBrowser
|
from qutebrowser.widgets.browser import TabbedBrowser
|
||||||
from qutebrowser.widgets.completion import CompletionView
|
from qutebrowser.widgets.completion import CompletionView
|
||||||
|
import qutebrowser.utils.config as config
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QWidget):
|
class MainWindow(QWidget):
|
||||||
@ -31,30 +36,41 @@ class MainWindow(QWidget):
|
|||||||
Adds all needed components to a vbox, initializes subwidgets and connects
|
Adds all needed components to a vbox, initializes subwidgets and connects
|
||||||
signals.
|
signals.
|
||||||
|
|
||||||
"""
|
Attributes:
|
||||||
|
tabs: The TabbedBrowser widget.
|
||||||
|
status: The StatusBar widget.
|
||||||
|
_vbox: The main QVBoxLayout.
|
||||||
|
|
||||||
vbox = None
|
"""
|
||||||
tabs = None
|
|
||||||
status = None
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.setWindowTitle('qutebrowser')
|
self.setWindowTitle('qutebrowser')
|
||||||
# FIXME maybe store window position/size on exit
|
try:
|
||||||
self.resize(800, 600)
|
geom = b64decode(config.state['geometry']['mainwindow'],
|
||||||
|
validate=True)
|
||||||
|
except binascii.Error:
|
||||||
|
self._set_default_geometry()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ok = self.restoreGeometry(geom)
|
||||||
|
except KeyError:
|
||||||
|
self._set_default_geometry()
|
||||||
|
if not ok:
|
||||||
|
self._set_default_geometry()
|
||||||
|
|
||||||
self.vbox = QVBoxLayout(self)
|
self._vbox = QVBoxLayout(self)
|
||||||
self.vbox.setContentsMargins(0, 0, 0, 0)
|
self._vbox.setContentsMargins(0, 0, 0, 0)
|
||||||
self.vbox.setSpacing(0)
|
self._vbox.setSpacing(0)
|
||||||
|
|
||||||
self.tabs = TabbedBrowser()
|
self.tabs = TabbedBrowser()
|
||||||
self.vbox.addWidget(self.tabs)
|
self._vbox.addWidget(self.tabs)
|
||||||
|
|
||||||
self.completion = CompletionView(self)
|
self.completion = CompletionView(self)
|
||||||
|
|
||||||
self.status = StatusBar()
|
self.status = StatusBar()
|
||||||
self.vbox.addWidget(self.status)
|
self._vbox.addWidget(self.status)
|
||||||
|
|
||||||
self.status.resized.connect(self.completion.resize_to_bar)
|
self.status.resized.connect(self.completion.resize_to_bar)
|
||||||
self.status.moved.connect(self.completion.move_to_bar)
|
self.status.moved.connect(self.completion.move_to_bar)
|
||||||
@ -80,3 +96,7 @@ class MainWindow(QWidget):
|
|||||||
#self.retranslateUi(MainWindow)
|
#self.retranslateUi(MainWindow)
|
||||||
#self.tabWidget.setCurrentIndex(0)
|
#self.tabWidget.setCurrentIndex(0)
|
||||||
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||||
|
|
||||||
|
def _set_default_geometry(self):
|
||||||
|
"""Set some sensible default geometry."""
|
||||||
|
self.setGeometry(QRect(50, 50, 800, 600))
|
||||||
|
@ -21,8 +21,7 @@ import logging
|
|||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt
|
||||||
from PyQt5.QtWidgets import (QWidget, QLineEdit, QProgressBar, QLabel,
|
from PyQt5.QtWidgets import (QWidget, QLineEdit, QProgressBar, QLabel,
|
||||||
QHBoxLayout, QSizePolicy, QShortcut, QStyle,
|
QHBoxLayout, QSizePolicy, QShortcut)
|
||||||
QStyleOption)
|
|
||||||
from PyQt5.QtGui import QPainter, QKeySequence, QValidator
|
from PyQt5.QtGui import QPainter, QKeySequence, QValidator
|
||||||
|
|
||||||
import qutebrowser.utils.config as config
|
import qutebrowser.utils.config as config
|
||||||
@ -32,20 +31,33 @@ from qutebrowser.utils.url import urlstring
|
|||||||
|
|
||||||
class StatusBar(QWidget):
|
class StatusBar(QWidget):
|
||||||
|
|
||||||
"""The statusbar at the bottom of the mainwindow."""
|
"""The statusbar at the bottom of the mainwindow.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
cmd: The Command widget in the statusbar.
|
||||||
|
txt: The Text widget in the statusbar.
|
||||||
|
keystring: The KeyString widget in the statusbar.
|
||||||
|
percentage: The Percentage widget in the statusbar.
|
||||||
|
url: The Url widget in the statusbar.
|
||||||
|
prog: The Progress widget in the statusbar.
|
||||||
|
_hbox: The main QHBoxLayout.
|
||||||
|
_error: If there currently is an error, accessed through the error
|
||||||
|
property.
|
||||||
|
_STYLESHEET: The stylesheet template.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
resized: Emitted when the statusbar has resized, so the completion
|
||||||
|
widget can adjust its size to it.
|
||||||
|
arg: The new size.
|
||||||
|
moved: Emitted when the statusbar has moved, so the completion widget
|
||||||
|
can move the the right position.
|
||||||
|
arg: The new position.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
hbox = None
|
|
||||||
cmd = None
|
|
||||||
txt = None
|
|
||||||
keystring = None
|
|
||||||
percentage = None
|
|
||||||
url = None
|
|
||||||
prog = None
|
|
||||||
resized = pyqtSignal('QRect')
|
resized = pyqtSignal('QRect')
|
||||||
moved = pyqtSignal('QPoint')
|
moved = pyqtSignal('QPoint')
|
||||||
_error = False
|
_STYLESHEET = """
|
||||||
_option = None
|
|
||||||
_stylesheet = """
|
|
||||||
QWidget#StatusBar[error="false"] {{
|
QWidget#StatusBar[error="false"] {{
|
||||||
{color[statusbar.bg]}
|
{color[statusbar.bg]}
|
||||||
}}
|
}}
|
||||||
@ -60,36 +72,39 @@ class StatusBar(QWidget):
|
|||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: the statusbar should be a bit smaller
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setObjectName(self.__class__.__name__)
|
self.setObjectName(self.__class__.__name__)
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
self.setAttribute(Qt.WA_StyledBackground)
|
||||||
|
self.setStyleSheet(config.get_stylesheet(self._STYLESHEET))
|
||||||
|
|
||||||
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||||
|
|
||||||
self.hbox = QHBoxLayout(self)
|
self._error = False
|
||||||
self.hbox.setContentsMargins(0, 0, 0, 0)
|
self._option = None
|
||||||
self.hbox.setSpacing(5)
|
|
||||||
|
|
||||||
self.cmd = Command(self)
|
self._hbox = QHBoxLayout(self)
|
||||||
self.hbox.addWidget(self.cmd)
|
self._hbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self._hbox.setSpacing(5)
|
||||||
|
|
||||||
self.txt = Text(self)
|
self.cmd = _Command(self)
|
||||||
self.hbox.addWidget(self.txt)
|
self._hbox.addWidget(self.cmd)
|
||||||
self.hbox.addStretch()
|
|
||||||
|
|
||||||
self.keystring = KeyString(self)
|
self.txt = _Text(self)
|
||||||
self.hbox.addWidget(self.keystring)
|
self._hbox.addWidget(self.txt)
|
||||||
|
self._hbox.addStretch()
|
||||||
|
|
||||||
self.url = Url(self)
|
self.keystring = _KeyString(self)
|
||||||
self.hbox.addWidget(self.url)
|
self._hbox.addWidget(self.keystring)
|
||||||
|
|
||||||
self.percentage = Percentage(self)
|
self.url = _Url(self)
|
||||||
self.hbox.addWidget(self.percentage)
|
self._hbox.addWidget(self.url)
|
||||||
|
|
||||||
self.prog = Progress(self)
|
self.percentage = _Percentage(self)
|
||||||
self.hbox.addWidget(self.prog)
|
self._hbox.addWidget(self.percentage)
|
||||||
|
|
||||||
|
self.prog = _Progress(self)
|
||||||
|
self._hbox.addWidget(self.prog)
|
||||||
|
|
||||||
@pyqtProperty(bool)
|
@pyqtProperty(bool)
|
||||||
def error(self):
|
def error(self):
|
||||||
@ -106,20 +121,11 @@ class StatusBar(QWidget):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self._error = val
|
self._error = val
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
self.setStyleSheet(config.get_stylesheet(self._STYLESHEET))
|
||||||
|
|
||||||
def paintEvent(self, e):
|
|
||||||
"""Override QWIidget.paintEvent to handle stylesheets."""
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
self._option = QStyleOption()
|
|
||||||
self._option.initFrom(self)
|
|
||||||
painter = QPainter(self)
|
|
||||||
self.style().drawPrimitive(QStyle.PE_Widget, self._option,
|
|
||||||
painter, self)
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def disp_error(self, text):
|
def disp_error(self, text):
|
||||||
"""Displaysan error in the statusbar."""
|
"""Display an error in the statusbar."""
|
||||||
self.error = True
|
self.error = True
|
||||||
self.txt.set_error(text)
|
self.txt.set_error(text)
|
||||||
|
|
||||||
@ -147,24 +153,40 @@ class StatusBar(QWidget):
|
|||||||
self.moved.emit(e.pos())
|
self.moved.emit(e.pos())
|
||||||
|
|
||||||
|
|
||||||
class Command(QLineEdit):
|
class _Command(QLineEdit):
|
||||||
|
|
||||||
"""The commandline part of the statusbar."""
|
"""The commandline part of the statusbar.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
history: The command history, with newer commands at the bottom.
|
||||||
|
_statusbar: The statusbar (parent) QWidget.
|
||||||
|
_shortcuts: Defined QShortcuts to prevent GCing.
|
||||||
|
_tmphist: The temporary history for history browsing
|
||||||
|
_histpos: The current position inside _tmphist
|
||||||
|
_validator: The current command validator.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
got_cmd: Emitted when a command is triggered by the user.
|
||||||
|
arg: The command string.
|
||||||
|
got_search: Emitted when the user started a new search.
|
||||||
|
arg: The search term.
|
||||||
|
got_rev_search: Emitted when the user started a new reverse search.
|
||||||
|
arg: The search term.
|
||||||
|
esc_pressed: Emitted when the escape key was pressed.
|
||||||
|
tab_pressed: Emitted when the tab key was pressed.
|
||||||
|
arg: Whether shift has been pressed.
|
||||||
|
hide_completion: Emitted when the completion widget should be hidden.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# FIXME we should probably use a proper model for the command history.
|
||||||
|
|
||||||
# Emitted when a command is triggered by the user
|
|
||||||
got_cmd = pyqtSignal(str)
|
got_cmd = pyqtSignal(str)
|
||||||
# Emitted for searches triggered by the user
|
|
||||||
got_search = pyqtSignal(str)
|
got_search = pyqtSignal(str)
|
||||||
got_search_rev = pyqtSignal(str)
|
got_search_rev = pyqtSignal(str)
|
||||||
statusbar = None # The status bar object
|
esc_pressed = pyqtSignal()
|
||||||
esc_pressed = pyqtSignal() # Emitted when escape is pressed
|
tab_pressed = pyqtSignal(bool)
|
||||||
tab_pressed = pyqtSignal(bool) # Emitted when tab is pressed (arg: shift)
|
hide_completion = pyqtSignal()
|
||||||
hide_completion = pyqtSignal() # Hide completion window
|
|
||||||
history = [] # The command history, with newer commands at the bottom
|
|
||||||
_shortcuts = []
|
|
||||||
_tmphist = []
|
|
||||||
_histpos = None
|
|
||||||
_validator = None # CommandValidator
|
|
||||||
|
|
||||||
# FIXME won't the tab key switch to the next widget?
|
# FIXME won't the tab key switch to the next widget?
|
||||||
# See [0] for a possible fix.
|
# See [0] for a possible fix.
|
||||||
@ -173,7 +195,9 @@ class Command(QLineEdit):
|
|||||||
def __init__(self, statusbar):
|
def __init__(self, statusbar):
|
||||||
super().__init__(statusbar)
|
super().__init__(statusbar)
|
||||||
# FIXME
|
# FIXME
|
||||||
self.statusbar = statusbar
|
self._statusbar = statusbar
|
||||||
|
self._histpos = None
|
||||||
|
self._tmphist = []
|
||||||
self.setStyleSheet("""
|
self.setStyleSheet("""
|
||||||
QLineEdit {
|
QLineEdit {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
@ -181,16 +205,18 @@ class Command(QLineEdit):
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
self._validator = CommandValidator(self)
|
self._validator = _CommandValidator(self)
|
||||||
self.setValidator(self._validator)
|
self.setValidator(self._validator)
|
||||||
self.returnPressed.connect(self.on_return_pressed)
|
self.returnPressed.connect(self._on_return_pressed)
|
||||||
self.textEdited.connect(self._histbrowse_stop)
|
self.textEdited.connect(self._histbrowse_stop)
|
||||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
|
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
|
||||||
|
self.history = []
|
||||||
|
|
||||||
|
self._shortcuts = []
|
||||||
for (key, handler) in [
|
for (key, handler) in [
|
||||||
(Qt.Key_Escape, self.esc_pressed),
|
(Qt.Key_Escape, self.esc_pressed),
|
||||||
(Qt.Key_Up, self.on_key_up_pressed),
|
(Qt.Key_Up, self._on_key_up_pressed),
|
||||||
(Qt.Key_Down, self.on_key_down_pressed),
|
(Qt.Key_Down, self._on_key_down_pressed),
|
||||||
(Qt.Key_Tab | Qt.SHIFT, lambda: self.tab_pressed.emit(True)),
|
(Qt.Key_Tab | Qt.SHIFT, lambda: self.tab_pressed.emit(True)),
|
||||||
(Qt.Key_Tab, lambda: self.tab_pressed.emit(False))
|
(Qt.Key_Tab, lambda: self.tab_pressed.emit(False))
|
||||||
]:
|
]:
|
||||||
@ -201,7 +227,7 @@ class Command(QLineEdit):
|
|||||||
self._shortcuts.append(sc)
|
self._shortcuts.append(sc)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_return_pressed(self):
|
def _on_return_pressed(self):
|
||||||
"""Handle the command in the status bar."""
|
"""Handle the command in the status bar."""
|
||||||
signals = {
|
signals = {
|
||||||
':': self.got_cmd,
|
':': self.got_cmd,
|
||||||
@ -217,7 +243,7 @@ class Command(QLineEdit):
|
|||||||
signals[text[0]].emit(text.lstrip(text[0]))
|
signals[text[0]].emit(text.lstrip(text[0]))
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def on_set_cmd_text(self, text):
|
def set_cmd_text(self, text):
|
||||||
"""Preset the statusbar to some text."""
|
"""Preset the statusbar to some text."""
|
||||||
self.setText(text)
|
self.setText(text)
|
||||||
self.setFocus()
|
self.setFocus()
|
||||||
@ -240,7 +266,7 @@ class Command(QLineEdit):
|
|||||||
|
|
||||||
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):
|
||||||
@ -264,7 +290,7 @@ class Command(QLineEdit):
|
|||||||
self._histpos = None
|
self._histpos = None
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_key_up_pressed(self):
|
def _on_key_up_pressed(self):
|
||||||
"""Handle Up presses (go back in history)."""
|
"""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:
|
||||||
@ -277,10 +303,10 @@ class Command(QLineEdit):
|
|||||||
return
|
return
|
||||||
logging.debug("history up: {} / len {} / pos {}".format(
|
logging.debug("history up: {} / len {} / pos {}".format(
|
||||||
self._tmphist, len(self._tmphist), self._histpos))
|
self._tmphist, len(self._tmphist), self._histpos))
|
||||||
self.set_cmd(self._tmphist[self._histpos])
|
self.set_cmd_text(self._tmphist[self._histpos])
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_key_down_pressed(self):
|
def _on_key_down_pressed(self):
|
||||||
"""Handle Down presses (go forward in history)."""
|
"""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))
|
||||||
@ -291,10 +317,10 @@ class Command(QLineEdit):
|
|||||||
self._histpos += 1
|
self._histpos += 1
|
||||||
logging.debug("history up: {} / len {} / pos {}".format(
|
logging.debug("history up: {} / len {} / pos {}".format(
|
||||||
self._tmphist, len(self._tmphist), self._histpos))
|
self._tmphist, len(self._tmphist), self._histpos))
|
||||||
self.set_cmd(self._tmphist[self._histpos])
|
self.set_cmd_text(self._tmphist[self._histpos])
|
||||||
|
|
||||||
|
|
||||||
class CommandValidator(QValidator):
|
class _CommandValidator(QValidator):
|
||||||
|
|
||||||
"""Validator to prevent the : from getting deleted."""
|
"""Validator to prevent the : from getting deleted."""
|
||||||
|
|
||||||
@ -313,13 +339,17 @@ class CommandValidator(QValidator):
|
|||||||
return (QValidator.Invalid, string, pos)
|
return (QValidator.Invalid, string, pos)
|
||||||
|
|
||||||
|
|
||||||
class Progress(QProgressBar):
|
class _Progress(QProgressBar):
|
||||||
|
|
||||||
"""The progress bar part of the status bar."""
|
"""The progress bar part of the status bar.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_STYLESHEET: The stylesheet template.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
statusbar = None
|
|
||||||
# FIXME for some reason, margin-left is not shown
|
# FIXME for some reason, margin-left is not shown
|
||||||
_stylesheet = """
|
_STYLESHEET = """
|
||||||
QProgressBar {{
|
QProgressBar {{
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
@ -332,10 +362,9 @@ class Progress(QProgressBar):
|
|||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, statusbar):
|
def __init__(self, parent):
|
||||||
super().__init__(statusbar)
|
super().__init__(parent)
|
||||||
self.statusbar = statusbar
|
self.setStyleSheet(config.get_stylesheet(self._STYLESHEET))
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
|
||||||
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Ignored)
|
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Ignored)
|
||||||
self.setTextVisible(False)
|
self.setTextVisible(False)
|
||||||
self.hide()
|
self.hide()
|
||||||
@ -356,15 +385,17 @@ class TextBase(QLabel):
|
|||||||
Eliding is loosly based on
|
Eliding is loosly based on
|
||||||
http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html
|
http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html
|
||||||
|
|
||||||
"""
|
Attributes:
|
||||||
|
_elidemode: Where to elide the text.
|
||||||
|
_elided_text: The current elided text.
|
||||||
|
|
||||||
elidemode = None
|
"""
|
||||||
_elided_text = None
|
|
||||||
|
|
||||||
def __init__(self, bar, elidemode=Qt.ElideRight):
|
def __init__(self, bar, elidemode=Qt.ElideRight):
|
||||||
super().__init__(bar)
|
super().__init__(bar)
|
||||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
||||||
self.elidemode = elidemode
|
self._elidemode = elidemode
|
||||||
|
self._elided_text = ''
|
||||||
|
|
||||||
def setText(self, txt):
|
def setText(self, txt):
|
||||||
"""Extend QLabel::setText to update the elided text afterwards."""
|
"""Extend QLabel::setText to update the elided text afterwards."""
|
||||||
@ -383,11 +414,11 @@ class TextBase(QLabel):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self._elided_text = self.fontMetrics().elidedText(
|
self._elided_text = self.fontMetrics().elidedText(
|
||||||
self.text(), self.elidemode, width, Qt.TextShowMnemonic)
|
self.text(), self._elidemode, width, Qt.TextShowMnemonic)
|
||||||
|
|
||||||
def paintEvent(self, e):
|
def paintEvent(self, e):
|
||||||
"""Override QLabel::paintEvent to draw elided text."""
|
"""Override QLabel::paintEvent to draw elided text."""
|
||||||
if self.elidemode == Qt.ElideNone:
|
if self._elidemode == Qt.ElideNone:
|
||||||
super().paintEvent(e)
|
super().paintEvent(e)
|
||||||
else:
|
else:
|
||||||
painter = QPainter(self)
|
painter = QPainter(self)
|
||||||
@ -396,30 +427,37 @@ class TextBase(QLabel):
|
|||||||
self._elided_text)
|
self._elided_text)
|
||||||
|
|
||||||
|
|
||||||
class Text(TextBase):
|
class _Text(TextBase):
|
||||||
|
|
||||||
"""Text displayed in the statusbar."""
|
"""Text displayed in the statusbar.
|
||||||
|
|
||||||
old_text = ''
|
Attributes:
|
||||||
|
_old_text: The text displayed before the temporary error message.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._old_text = ''
|
||||||
|
|
||||||
def set_error(self, text):
|
def set_error(self, text):
|
||||||
"""Display an error message and save current text in old_text."""
|
"""Display an error message and save current text in old_text."""
|
||||||
self.old_text = self.text()
|
self._old_text = self.text()
|
||||||
self.setText(text)
|
self.setText(text)
|
||||||
|
|
||||||
def clear_error(self):
|
def clear_error(self):
|
||||||
"""Clear a displayed error message."""
|
"""Clear a displayed error message."""
|
||||||
self.setText(self.old_text)
|
self.setText(self._old_text)
|
||||||
|
|
||||||
|
|
||||||
class KeyString(TextBase):
|
class _KeyString(TextBase):
|
||||||
|
|
||||||
"""Keychain string displayed in the statusbar."""
|
"""Keychain string displayed in the statusbar."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Percentage(TextBase):
|
class _Percentage(TextBase):
|
||||||
|
|
||||||
"""Reading percentage displayed in the statusbar."""
|
"""Reading percentage displayed in the statusbar."""
|
||||||
|
|
||||||
@ -434,15 +472,20 @@ class Percentage(TextBase):
|
|||||||
self.setText('[{:2}%]'.format(y))
|
self.setText('[{:2}%]'.format(y))
|
||||||
|
|
||||||
|
|
||||||
class Url(TextBase):
|
class _Url(TextBase):
|
||||||
|
|
||||||
"""URL displayed in the statusbar."""
|
"""URL displayed in the statusbar.
|
||||||
|
|
||||||
_old_url = None
|
Attributes:
|
||||||
_old_urltype = None
|
_old_url: The URL displayed before the hover URL.
|
||||||
_urltype = None # 'normal', 'ok', 'error', 'warn, 'hover'
|
_old_urltype: The type of the URL displayed before the hover URL.
|
||||||
|
_urltype: The current URL type. One of normal/ok/error/warn/hover.
|
||||||
|
Accessed via the urltype property.
|
||||||
|
_STYLESHEET: The stylesheet template.
|
||||||
|
|
||||||
_stylesheet = """
|
"""
|
||||||
|
|
||||||
|
_STYLESHEET = """
|
||||||
QLabel#Url[urltype="normal"] {{
|
QLabel#Url[urltype="normal"] {{
|
||||||
{color[statusbar.url.fg]}
|
{color[statusbar.url.fg]}
|
||||||
}}
|
}}
|
||||||
@ -468,7 +511,10 @@ class Url(TextBase):
|
|||||||
"""Override TextBase::__init__ to elide in the middle by default."""
|
"""Override TextBase::__init__ to elide in the middle by default."""
|
||||||
super().__init__(bar, elidemode)
|
super().__init__(bar, elidemode)
|
||||||
self.setObjectName(self.__class__.__name__)
|
self.setObjectName(self.__class__.__name__)
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
self.setStyleSheet(config.get_stylesheet(self._STYLESHEET))
|
||||||
|
self._urltype = None
|
||||||
|
self._old_urltype = None
|
||||||
|
self._old_url = None
|
||||||
|
|
||||||
@pyqtProperty(str)
|
@pyqtProperty(str)
|
||||||
def urltype(self):
|
def urltype(self):
|
||||||
@ -480,7 +526,7 @@ class Url(TextBase):
|
|||||||
def urltype(self, val):
|
def urltype(self, val):
|
||||||
"""Setter for self.urltype, so it can be used as Qt property."""
|
"""Setter for self.urltype, so it can be used as Qt property."""
|
||||||
self._urltype = val
|
self._urltype = val
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
self.setStyleSheet(config.get_stylesheet(self._STYLESHEET))
|
||||||
|
|
||||||
@pyqtSlot(bool)
|
@pyqtSlot(bool)
|
||||||
def on_loading_finished(self, ok):
|
def on_loading_finished(self, ok):
|
||||||
|
@ -26,12 +26,17 @@ from qutebrowser.utils.style import Style
|
|||||||
|
|
||||||
class TabWidget(QTabWidget):
|
class TabWidget(QTabWidget):
|
||||||
|
|
||||||
"""The tabwidget used for TabbedBrowser."""
|
"""The tabwidget used for TabbedBrowser.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_STYLESHEET: The stylesheet template to be used.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
# 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...
|
||||||
|
|
||||||
_stylesheet = """
|
_STYLESHEET = """
|
||||||
QTabWidget::pane {{
|
QTabWidget::pane {{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@ -63,7 +68,7 @@ class TabWidget(QTabWidget):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
self.setStyle(Style(self.style()))
|
self.setStyle(Style(self.style()))
|
||||||
self.setStyleSheet(config.get_stylesheet(self._stylesheet))
|
self.setStyleSheet(config.get_stylesheet(self._STYLESHEET))
|
||||||
self.setDocumentMode(True)
|
self.setDocumentMode(True)
|
||||||
self.setElideMode(Qt.ElideRight)
|
self.setElideMode(Qt.ElideRight)
|
||||||
self._init_config()
|
self._init_config()
|
||||||
|
Loading…
Reference in New Issue
Block a user