Use QApplication for global singletons

This commit is contained in:
Florian Bruhin 2014-05-05 17:56:14 +02:00
parent ff272df6e4
commit 667b255d01
20 changed files with 123 additions and 160 deletions

1
TODO
View File

@ -6,7 +6,6 @@ All kind of FIXMEs
Style
=====
_foo = QApplication.instance().obj.foo for global singletons?
initialize completion models at some nicer place (not in widget)
Major features

View File

@ -49,16 +49,19 @@ import qutebrowser.commands.utils as cmdutils
import qutebrowser.config.style as style
import qutebrowser.config.config as config
import qutebrowser.network.qutescheme as qutescheme
import qutebrowser.keyinput.modeman as modeman
import qutebrowser.utils.message as message
import qutebrowser.config.websettings as websettings
import qutebrowser.network.networkmanager as networkmanager
import qutebrowser.browser.cookies as cookies
from qutebrowser.network.networkmanager import NetworkManager
from qutebrowser.config.config import ConfigManager
from qutebrowser.keyinput.modeman import ModeManager
from qutebrowser.widgets.mainwindow import MainWindow
from qutebrowser.widgets.crash import CrashDialog
from qutebrowser.keyinput.modeparsers import NormalKeyParser, HintKeyParser
from qutebrowser.keyinput.keyparser import PassthroughKeyParser
from qutebrowser.commands.managers import CommandManager, SearchManager
from qutebrowser.config.iniparsers import ReadWriteConfigParser
from qutebrowser.config.lineparser import LineConfigParser
from qutebrowser.browser.cookies import CookieJar
from qutebrowser.utils.message import MessageBridge
from qutebrowser.utils.appdirs import AppDirs
from qutebrowser.utils.misc import dotted_getattr
from qutebrowser.utils.debug import set_trace # pylint: disable=unused-import
@ -77,6 +80,13 @@ class QuteBrowser(QApplication):
mainwindow: The MainWindow QWidget.
commandmanager: The main CommandManager instance.
searchmanager: The main SearchManager instance.
config: The main ConfigManager
stateconfig: The "state" ReadWriteConfigParser instance.
cmd_history: The "cmd_history" LineConfigParser instance.
messagebridge: The global MessageBridge instance.
modeman: The global ModeManager instance.
networkmanager: The global NetworkManager instance.
cookiejar: The global CookieJar instance.
_keyparsers: A mapping from modes to keyparsers.
_dirs: AppDirs instance for config/cache directories.
_args: ArgumentParser instance.
@ -86,12 +96,22 @@ class QuteBrowser(QApplication):
_opened_urls: List of opened URLs.
"""
# This also holds all our globals, so we're a bit over the top here.
# pylint: disable=too-many-instance-attributes
def __init__(self):
super().__init__(sys.argv)
self._quit_status = {}
self._timers = []
self._opened_urls = []
self._shutting_down = False
self._keyparsers = None
self._dirs = None
self.messagebridge = None
self.stateconfig = None
self.modeman = None
self.cmd_history = None
self.config = None
sys.excepthook = self._exception_hook
@ -101,18 +121,18 @@ class QuteBrowser(QApplication):
self._init_config()
self._init_modes()
websettings.init(self._dirs.user_cache_dir)
cookies.init(self._dirs.user_data_dir)
networkmanager.init(cookies.cookiejar)
self.cookiejar = CookieJar(self._dirs.user_data_dir)
self.networkmanager = NetworkManager(self.cookiejar)
self.commandmanager = CommandManager()
self.searchmanager = SearchManager()
self._init_cmds()
self.mainwindow = MainWindow()
self.installEventFilter(modeman.manager)
self.installEventFilter(self.modeman)
self.setQuitOnLastWindowClosed(False)
self._connect_signals()
modeman.enter('normal')
self.modeman.enter('normal')
self.mainwindow.show()
self._python_hacks()
@ -148,7 +168,7 @@ class QuteBrowser(QApplication):
else:
confdir = self._args.confdir
try:
config.init(confdir)
self.config = ConfigManager(confdir, 'qutebrowser.conf')
except (config.ValidationError,
config.NoOptionError,
configparser.InterpolationError,
@ -165,7 +185,9 @@ class QuteBrowser(QApplication):
msgbox.exec_()
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
self.config = config.instance
self.stateconfig = ReadWriteConfigParser(confdir, 'state')
self.cmd_history = LineConfigParser(confdir, 'cmd_history',
('completion', 'history-length'))
def _init_modes(self):
"""Inizialize the mode manager and the keyparsers."""
@ -176,17 +198,16 @@ class QuteBrowser(QApplication):
'passthrough': PassthroughKeyParser('keybind.passthrough', self),
'command': PassthroughKeyParser('keybind.command', self),
}
modeman.init(self)
modeman.manager.register('normal', self._keyparsers['normal'].handle)
modeman.manager.register('hint', self._keyparsers['hint'].handle)
modeman.manager.register('insert', self._keyparsers['insert'].handle,
passthrough=True)
modeman.manager.register('passthrough',
self._keyparsers['passthrough'].handle,
passthrough=True)
modeman.manager.register('command', self._keyparsers['command'].handle,
passthrough=True)
self.modeman = modeman.manager # for commands
self.modeman = ModeManager()
self.modeman.register('normal', self._keyparsers['normal'].handle)
self.modeman.register('hint', self._keyparsers['hint'].handle)
self.modeman.register('insert', self._keyparsers['insert'].handle,
passthrough=True)
self.modeman.register('passthrough',
self._keyparsers['passthrough'].handle,
passthrough=True)
self.modeman.register('command', self._keyparsers['command'].handle,
passthrough=True)
def _init_log(self):
"""Initialisation of the logging output.
@ -210,7 +231,7 @@ class QuteBrowser(QApplication):
os.environ['QT_FATAL_WARNINGS'] = '1'
self.setApplicationName("qutebrowser")
self.setApplicationVersion(qutebrowser.__version__)
message.init()
self.messagebridge = MessageBridge()
def _init_cmds(self):
"""Initialisation of the qutebrowser commands.
@ -247,7 +268,7 @@ class QuteBrowser(QApplication):
if self.mainwindow.tabs.count() == 0:
logging.debug("Opening startpage")
for url in config.get('general', 'startpage'):
for url in self.config.get('general', 'startpage'):
self.mainwindow.tabs.tabopen(url)
def _python_hacks(self):
@ -278,10 +299,10 @@ class QuteBrowser(QApplication):
tabs.currentChanged.connect(self.mainwindow.update_inspector)
# status bar
modeman.manager.entered.connect(status.on_mode_entered)
modeman.manager.left.connect(status.on_mode_left)
modeman.manager.left.connect(status.cmd.on_mode_left)
modeman.manager.key_pressed.connect(status.on_key_pressed)
self.modeman.entered.connect(status.on_mode_entered)
self.modeman.left.connect(status.on_mode_left)
self.modeman.left.connect(status.cmd.on_mode_left)
self.modeman.key_pressed.connect(status.on_key_pressed)
# commands
cmd.got_cmd.connect(self.commandmanager.run_safely)
@ -298,15 +319,15 @@ class QuteBrowser(QApplication):
tabs.hint_strings_updated.connect(kp['hint'].on_hint_strings_updated)
# messages
message.bridge.error.connect(status.disp_error)
message.bridge.info.connect(status.txt.set_temptext)
message.bridge.text.connect(status.txt.set_normaltext)
message.bridge.set_cmd_text.connect(cmd.set_cmd_text)
self.messagebridge.error.connect(status.disp_error)
self.messagebridge.info.connect(status.txt.set_temptext)
self.messagebridge.text.connect(status.txt.set_normaltext)
self.messagebridge.set_cmd_text.connect(cmd.set_cmd_text)
# config
self.config.style_changed.connect(style.invalidate_caches)
for obj in [tabs, completion, self.mainwindow, config.cmd_history,
websettings, kp['normal'], modeman.manager]:
for obj in [tabs, completion, self.mainwindow, self.cmd_history,
websettings, kp['normal'], self.modeman]:
self.config.changed.connect(obj.on_config_changed)
# statusbar
@ -320,7 +341,7 @@ class QuteBrowser(QApplication):
tabs.cur_link_hovered.connect(status.url.set_hover_url)
# command input / completion
modeman.manager.left.connect(tabs.on_mode_left)
self.modeman.left.connect(tabs.on_mode_left)
cmd.clear_completion_selection.connect(
completion.on_clear_completion_selection)
cmd.hide_completion.connect(completion.hide)
@ -353,10 +374,10 @@ class QuteBrowser(QApplication):
"""Save the window geometry to the state config."""
geom = b64encode(bytes(self.mainwindow.saveGeometry())).decode('ASCII')
try:
config.state.add_section('geometry')
self.stateconfig.add_section('geometry')
except configparser.DuplicateSectionError:
pass
config.state['geometry']['mainwindow'] = geom
self.stateconfig['geometry']['mainwindow'] = geom
def _exception_hook(self, exctype, excvalue, tb):
"""Handle uncaught python exceptions.
@ -473,22 +494,22 @@ class QuteBrowser(QApplication):
return
self._shutting_down = True
logging.debug("Shutting down... (do_quit={})".format(do_quit))
if config.get('general', 'auto-save-config'):
if self.config.get('general', 'auto-save-config'):
try:
config.instance.save()
self.config.save()
except AttributeError:
logging.exception("Could not save config.")
try:
config.cmd_history.save()
self.cmd_history.save()
except AttributeError:
logging.exception("Could not save command history.")
try:
self._save_geometry()
config.state.save()
self.stateconfig.save()
except AttributeError:
logging.exception("Could not save window geometry.")
try:
cookies.cookiejar.save()
self.cookiejar.save()
except AttributeError:
logging.exception("Could not save cookies.")
try:

View File

@ -23,19 +23,6 @@ from qutebrowser.config.lineparser import LineConfigParser
from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
cookiejar = None
def init(datadir):
"""Initialize the global cookie jar.
Args:
datadir: The directory to store the cookie file in.
"""
global cookiejar
cookiejar = CookieJar(datadir)
class CookieJar(QNetworkCookieJar):
"""Our own cookie jar to save cookies to disk if desired."""

View File

@ -99,7 +99,7 @@ class HintManager(QObject):
self._target = None
self._baseurl = None
self._to_follow = None
modeman.manager.left.connect(self.on_mode_left)
modeman.instance().left.connect(self.on_mode_left)
def _hint_strings(self, elems):
"""Calculate the hint strings for elems.
@ -205,7 +205,7 @@ class HintManager(QObject):
"""
rect = elem.geometry()
css = self.HINT_CSS.format(left=rect.x(), top=rect.y(),
config=config.instance)
config=config.instance())
doc = self._frame.documentElement()
# It seems impossible to create an empty QWebElement for which isNull()
# is false so we can work with it.
@ -463,7 +463,7 @@ class HintManager(QObject):
for elems in self._elems.values():
rect = elems.elem.geometry()
css = self.HINT_CSS.format(left=rect.x(), top=rect.y(),
config=config.instance)
config=config.instance())
elems.label.setAttribute('style', css)
@pyqtSlot(str)

View File

@ -18,12 +18,12 @@
"""The main browser widgets."""
import sip
from PyQt5.QtWidgets import QApplication
from PyQt5.QtNetwork import QNetworkReply
from PyQt5.QtWebKitWidgets import QWebPage
import qutebrowser.utils.url as urlutils
import qutebrowser.config.config as config
import qutebrowser.network.networkmanager as networkmanager
from qutebrowser.utils.misc import read_file
@ -41,7 +41,7 @@ class BrowserPage(QWebPage):
self._extension_handlers = {
QWebPage.ErrorPageExtension: self._handle_errorpage,
}
self.setNetworkAccessManager(networkmanager.networkmanager)
self.setNetworkAccessManager(QApplication.instance().networkmanager)
def _handle_errorpage(self, opt, out):
"""Display an error page if needed.

View File

@ -23,6 +23,7 @@ from qutebrowser.commands._exceptions import (ArgumentCountError,
PrerequisitesError)
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKit import QWebSettings
@ -82,13 +83,14 @@ class Command(QObject):
ArgumentCountError if the argument count is wrong.
PrerequisitesError if the command can't be called currently.
"""
import qutebrowser.keyinput.modeman as modeman
if self.modes is not None and modeman.manager.mode not in self.modes:
# We don't use modeman.instance() here to avoid a circular import
# of qutebrowser.keyinput.modeman.
curmode = QApplication.instance().modeman.mode
if self.modes is not None and curmode not in self.modes:
raise PrerequisitesError("{}: This command is only allowed in {} "
"mode.".format(self.name,
'/'.join(self.modes)))
elif (self.not_modes is not None and
modeman.manager.mode in self.not_modes):
elif self.not_modes is not None and curmode in self.not_modes:
raise PrerequisitesError("{}: This command is not allowed in {} "
"mode.".format(self.name,
'/'.join(self.not_modes)))

View File

@ -20,11 +20,6 @@
This borrows a lot of ideas from configparser, but also has some things that
are fundamentally different. This is why nothing inherts from configparser, but
we borrow some methods and classes from there where it makes sense.
Module attributes:
instance: The "qutebrowser.conf" Config instance.
state: The "state" ReadWriteConfigParser instance.
cmd_history: The "cmd_history" LineConfigParser instance.
"""
import os
@ -36,37 +31,28 @@ from configparser import ExtendedInterpolation
from collections.abc import MutableMapping
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication
import qutebrowser.config.configdata as configdata
import qutebrowser.commands.utils as cmdutils
import qutebrowser.utils.message as message
from qutebrowser.config.iniparsers import ReadConfigParser
from qutebrowser.config._conftypes import ValidationError
from qutebrowser.config._iniparsers import (ReadConfigParser,
ReadWriteConfigParser)
from qutebrowser.config.lineparser import LineConfigParser
instance = None
state = None
cmd_history = None
def init(configdir):
"""Initialize the global objects based on the config in configdir.
Args:
configdir: The directory where the configs are stored in.
"""
global instance, state, cmd_history
logging.debug("Config init, configdir {}".format(configdir))
instance = ConfigManager(configdir, 'qutebrowser.conf')
state = ReadWriteConfigParser(configdir, 'state')
cmd_history = LineConfigParser(configdir, 'cmd_history',
('completion', 'history-length'))
def instance():
"""Get the global config instance."""
return QApplication.instance().config
def get(*args, **kwargs):
"""Convenience method to call get(...) of the config instance."""
return instance.get(*args, **kwargs)
return instance().get(*args, **kwargs)
def section(sect):
"""Get a config section from the global config."""
return instance()[sect]
class NoSectionError(configparser.NoSectionError):

View File

@ -26,6 +26,7 @@ from functools import partial
import qutebrowser.config.config as config
_colordict = None
_fontdict = None
@ -41,9 +42,9 @@ def get_stylesheet(template):
"""
global _colordict, _fontdict
if _colordict is None:
_colordict = ColorDict(config.instance['colors'])
_colordict = ColorDict(config.section('colors'))
if _fontdict is None:
_fontdict = FontDict(config.instance['fonts'])
_fontdict = FontDict(config.section('fonts'))
return template.strip().format(color=_colordict, font=_fontdict)
@ -59,7 +60,7 @@ def set_register_stylesheet(obj):
Must have a STYLESHEET attribute.
"""
obj.setStyleSheet(get_stylesheet(obj.STYLESHEET))
config.instance.changed.connect(partial(_update_stylesheet, obj))
config.instance().changed.connect(partial(_update_stylesheet, obj))
def _update_stylesheet(obj, _section, _option):

View File

@ -303,7 +303,7 @@ class BaseKeyParser(QObject):
sectname = self._confsectname
else:
self._confsectname = sectname
sect = config.instance[sectname]
sect = config.section(sectname)
if not sect.items():
logging.warn("No keybindings defined!")
for (key, cmd) in sect.items():

View File

@ -25,42 +25,32 @@ import logging
from PyQt5.QtGui import QWindow
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent
from PyQt5.QtWidgets import QApplication
import qutebrowser.config.config as config
import qutebrowser.commands.utils as cmdutils
import qutebrowser.utils.debug as debug
manager = None
def init(parent=None):
"""Initialize the global ModeManager.
This needs to be done by hand because the import time is before Qt is ready
for everything.
Args:
parent: Parent to use for ModeManager.
"""
global manager
manager = ModeManager(parent)
def instance():
"""Get the global modeman instance."""
return QApplication.instance().modeman
def enter(mode):
"""Enter the mode 'mode'."""
manager.enter(mode)
instance().enter(mode)
def leave(mode):
"""Leave the mode 'mode'."""
manager.leave(mode)
instance().leave(mode)
def maybe_leave(mode):
"""Convenience method to leave 'mode' without exceptions."""
try:
manager.leave(mode)
instance().leave(mode)
except ValueError:
pass

View File

@ -20,8 +20,8 @@
import logging
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication
import qutebrowser.config.config as config
from qutebrowser.utils.usertypes import NeighborList
@ -51,10 +51,11 @@ class History:
def __init__(self):
self._tmphist = None
if config.cmd_history.data is None:
history = QApplication.instance().cmd_history.data
if history is None:
self._history = []
else:
self._history = config.cmd_history.data
self._history = history
@property
def browsing(self):

View File

@ -119,7 +119,7 @@ class CommandCompletionModel(BaseCompletionModel):
for obj in set(cmd_dict.values()):
if not obj.hide:
cmdlist.append((obj.name, obj.desc))
for name, cmd in config.instance['aliases'].items():
for name, cmd in config.section('aliases').items():
cmdlist.append((name, "Alias for \"{}\"".format(cmd)))
cat = self.new_category("Commands")
for (name, desc) in sorted(cmdlist):

View File

@ -23,21 +23,6 @@ import qutebrowser.config.config as config
from qutebrowser.network.qutescheme import QuteSchemeHandler
networkmanager = None
def init(cookiejar):
"""Initialize the global NetworkManager.
Args:
cookiejar: The QCookieJar to use.
"""
global networkmanager
networkmanager = NetworkManager()
networkmanager.setCookieJar(cookiejar)
cookiejar.setParent(None)
class NetworkManager(QNetworkAccessManager):
"""Our own QNetworkAccessManager.
@ -48,12 +33,14 @@ class NetworkManager(QNetworkAccessManager):
schemes.
"""
def __init__(self, parent=None):
def __init__(self, cookiejar=None, parent=None):
super().__init__(parent)
self._requests = {}
self._scheme_handlers = {
'qute': QuteSchemeHandler(),
}
if cookiejar is not None:
self.setCookieJar(cookiejar)
def abort_requests(self):
"""Abort all running requests."""

View File

@ -15,51 +15,40 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Message singleton so we don't have to define unneeded signals.
Module attributes:
bridge: The MessageBridge instance.
"""
"""Message singleton so we don't have to define unneeded signals."""
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication
bridge = None
def init():
"""Initialize the global MessageBridge.
This needs to be done by hand because the import time is before Qt is ready
for everything.
"""
global bridge
bridge = MessageBridge()
def instance():
"""Get the global messagebridge instance."""
return QApplication.instance().messagebridge
def error(message):
"""Display an error message in the statusbar."""
bridge.error.emit(message)
instance().error.emit(message)
def info(message):
"""Display a temporary info message in the statusbar."""
bridge.info.emit(message)
instance().info.emit(message)
def text(message):
"""Display a persistent message in the statusbar."""
bridge.text.emit(message)
instance().text.emit(message)
def clear():
"""Clear a persistent message in the statusbar."""
bridge.text.emit('')
instance().text.emit('')
def set_cmd_text(txt):
"""Set the statusbar command line to a preset text."""
bridge.set_cmd_text.emit(txt)
instance().set_cmd_text.emit(txt)
class MessageBridge(QObject):

View File

@ -147,7 +147,7 @@ class CompletionView(QTreeView):
for sectname, sect in configdata.DATA.items():
optmodel = CFM(SettingOptionCompletionModel(sectname, self))
self._models['option'][sectname] = optmodel
config.instance.changed.connect(
config.instance().changed.connect(
optmodel.srcmodel.on_config_changed)
if hasattr(sect, 'valtype'):
# Same type for all values (ValueList)

View File

@ -175,13 +175,13 @@ class StatusBar(QWidget):
@pyqtSlot(str)
def on_mode_entered(self, mode):
"""Mark certain modes in the commandline."""
if mode in modeman.manager.passthrough:
if mode in modeman.instance().passthrough:
self.txt.normaltext = "-- {} MODE --".format(mode.upper())
@pyqtSlot(str)
def on_mode_left(self, mode):
"""Clear marked mode."""
if mode in modeman.manager.passthrough:
if mode in modeman.instance().passthrough:
self.txt.normaltext = ""
def resizeEvent(self, e):

View File

@ -106,7 +106,7 @@ class CrashDialog(QDialog):
("Commandline args", ' '.join(sys.argv[1:])),
]
try:
outputs.append(("Config", config.instance.dump_userconfig()))
outputs.append(("Config", config.instance().dump_userconfig()))
except AttributeError:
pass
chunks = []

View File

@ -21,7 +21,7 @@ import binascii
from base64 import b64decode
from PyQt5.QtCore import pyqtSlot, QRect, QPoint
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication
from PyQt5.QtWebKitWidgets import QWebInspector
from qutebrowser.widgets._statusbar import StatusBar
@ -50,8 +50,9 @@ class MainWindow(QWidget):
self.setWindowTitle('qutebrowser')
try:
geom = b64decode(config.state['geometry']['mainwindow'],
validate=True)
geom = b64decode(
QApplication.instance().stateconfig['geometry']['mainwindow'],
validate=True)
except (KeyError, binascii.Error):
self._set_default_geometry()
else:

View File

@ -30,7 +30,6 @@ import qutebrowser.config.config as config
import qutebrowser.keyinput.modeman as modeman
import qutebrowser.utils.message as message
import qutebrowser.utils.webelem as webelem
import qutebrowser.network.networkmanager as networkmanager
from qutebrowser.browser.webpage import BrowserPage
from qutebrowser.browser.hints import HintManager
from qutebrowser.utils.signals import SignalCache
@ -210,7 +209,7 @@ class WebView(QWebView):
self.destroyed.connect(functools.partial(self._on_destroyed, self))
self.deleteLater()
netman = networkmanager.networkmanager
netman = QApplication.instance().networkmanager
self._destroyed[netman] = False
netman.abort_requests()
netman.destroyed.connect(functools.partial(self._on_destroyed, netman))