diff --git a/doc/HACKING.asciidoc b/doc/HACKING.asciidoc index eb1a6744f..6be6a4225 100644 --- a/doc/HACKING.asciidoc +++ b/doc/HACKING.asciidoc @@ -288,7 +288,7 @@ displaying it to the user. * Name a string URL something like `urlstr`, and a `QUrl` something like `url`. * Mention in the docstring whether your function needs a URL string or a `QUrl`. -* Call `qt_ensure_valid` from `utils.misc` whenever getting or creating a +* Call `qt_ensure_valid` from `utils.qt` whenever getting or creating a `QUrl` and take appropriate action if not. diff --git a/doc/TODO b/doc/TODO index 15448059f..d4248808c 100644 --- a/doc/TODO +++ b/doc/TODO @@ -25,11 +25,6 @@ Before 0.1 qt_ensure_valid and qt_check_overflow check return values -Style -===== - -- Move some stuff from utils.misc to utils.qt and move utils.signals there too. - New big features ================ diff --git a/qutebrowser/app.py b/qutebrowser/app.py index d221ecc31..efbd5a4cc 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -57,8 +57,8 @@ from qutebrowser.config.iniparsers import ReadWriteConfigParser from qutebrowser.config.lineparser import LineConfigParser from qutebrowser.browser.cookies import CookieJar from qutebrowser.browser.downloads import DownloadManager -from qutebrowser.utils.misc import (get_standard_dir, actute_warning, - get_qt_args) +from qutebrowser.utils.misc import get_standard_dir, actute_warning +from qutebrowser.utils.qt import get_qt_args from qutebrowser.utils.readline import ReadlineBridge from qutebrowser.utils.usertypes import Timer diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 4fcf6c426..c8ee25836 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -37,8 +37,9 @@ import qutebrowser.utils.webelem as webelem import qutebrowser.browser.quickmarks as quickmarks import qutebrowser.utils.log as log import qutebrowser.utils.url as urlutils -from qutebrowser.utils.misc import (check_overflow, shell_escape, - check_print_compat, qt_ensure_valid) +from qutebrowser.utils.misc import shell_escape +from qutebrowser.utils.qt import (check_overflow, check_print_compat, + qt_ensure_valid) from qutebrowser.utils.editor import ExternalEditor from qutebrowser.commands.exceptions import CommandError from qutebrowser.commands.userscripts import UserscriptRunner diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 281cdb0cc..606f58b02 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -33,7 +33,7 @@ import qutebrowser.utils.webelem as webelem from qutebrowser.commands.exceptions import CommandError from qutebrowser.utils.usertypes import enum from qutebrowser.utils.log import hints as logger -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid ElemTuple = namedtuple('ElemTuple', 'elem, label') diff --git a/qutebrowser/browser/quickmarks.py b/qutebrowser/browser/quickmarks.py index cf2738ad9..4e8f7df75 100644 --- a/qutebrowser/browser/quickmarks.py +++ b/qutebrowser/browser/quickmarks.py @@ -33,7 +33,8 @@ import qutebrowser.utils.message as message import qutebrowser.commands.utils as cmdutils from qutebrowser.utils.usertypes import PromptMode from qutebrowser.config.lineparser import LineConfigParser -from qutebrowser.utils.misc import get_standard_dir, qt_ensure_valid +from qutebrowser.utils.misc import get_standard_dir +from qutebrowser.utils.qt import qt_ensure_valid from qutebrowser.commands.exceptions import CommandError diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index f6c714f1a..6c86541ec 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -30,7 +30,8 @@ from PyQt5.QtWebKitWidgets import QWebPage import qutebrowser.utils.message as message import qutebrowser.config.config as config import qutebrowser.utils.log as log -from qutebrowser.utils.misc import read_file, check_print_compat +from qutebrowser.utils.misc import read_file +from qutebrowser.utils.qt import check_print_compat from qutebrowser.utils.usertypes import PromptMode diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index dcbe473ca..5acb92414 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -26,7 +26,7 @@ Module attributes: import inspect from collections import Iterable -import qutebrowser.utils.misc as utils +import qutebrowser.utils.qt as qtutils from qutebrowser.commands.command import Command from qutebrowser.commands.exceptions import CommandError @@ -46,7 +46,7 @@ def check_overflow(arg, ctype): """ # FIXME we somehow should have nicer exceptions... try: - utils.check_overflow(arg, ctype) + qtutils.check_overflow(arg, ctype) except OverflowError: raise CommandError("Numeric argument is too large for internal {} " "representation.".format(ctype)) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index bfab28891..b03ea27f5 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -32,7 +32,7 @@ from collections import OrderedDict from qutebrowser.config.value import SettingValue import qutebrowser.config.conftypes as types import qutebrowser.config.sections as sect -from qutebrowser.utils.misc import MAXVALS +from qutebrowser.utils.qt import MAXVALS FIRST_COMMENT = r""" diff --git a/qutebrowser/models/basecompletion.py b/qutebrowser/models/basecompletion.py index 575f20696..6d0453f03 100644 --- a/qutebrowser/models/basecompletion.py +++ b/qutebrowser/models/basecompletion.py @@ -27,7 +27,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QStandardItemModel, QStandardItem from qutebrowser.utils.usertypes import enum -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid Role = enum('marks', 'sort', start=Qt.UserRole) diff --git a/qutebrowser/models/completion.py b/qutebrowser/models/completion.py index b19b35430..fe5d707bd 100644 --- a/qutebrowser/models/completion.py +++ b/qutebrowser/models/completion.py @@ -26,7 +26,7 @@ import qutebrowser.config.configdata as configdata from qutebrowser.models.basecompletion import BaseCompletionModel from qutebrowser.commands.utils import cmd_dict from qutebrowser.utils.log import completion as logger -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class SettingSectionCompletionModel(BaseCompletionModel): diff --git a/qutebrowser/models/completionfilter.py b/qutebrowser/models/completionfilter.py index acd3315eb..1156500b8 100644 --- a/qutebrowser/models/completionfilter.py +++ b/qutebrowser/models/completionfilter.py @@ -27,7 +27,7 @@ from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex from qutebrowser.models.basecompletion import Role from qutebrowser.utils.log import completion as logger -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class CompletionFilterModel(QSortFilterProxyModel): diff --git a/qutebrowser/models/downloadmodel.py b/qutebrowser/models/downloadmodel.py index 15ede1a33..2902636ea 100644 --- a/qutebrowser/models/downloadmodel.py +++ b/qutebrowser/models/downloadmodel.py @@ -25,7 +25,7 @@ from PyQt5.QtWidgets import QApplication import qutebrowser.config.config as config from qutebrowser.utils.usertypes import enum -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid Role = enum('item', start=Qt.UserRole) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 3ecdddb37..80d58d966 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -55,7 +55,7 @@ def _parse_args(): "Default: auto.") # For the Qt args, we use store_const with const=True rather than # store_true because we want the default to be None, to make - # utils.misc:get_qt_args easier. + # utils.qt:get_qt_args easier. debug.add_argument('--qt-style', help="Set the Qt GUI style to use.", metavar='STYLE') debug.add_argument('--qt-stylesheet', help="Override the Qt application " diff --git a/qutebrowser/test/utils/test_misc.py b/qutebrowser/test/utils/test_misc.py index fbf44e146..f79bc63d0 100644 --- a/qutebrowser/test/utils/test_misc.py +++ b/qutebrowser/test/utils/test_misc.py @@ -24,7 +24,6 @@ import os import sys import shutil -import argparse import unittest import os.path import subprocess @@ -62,60 +61,6 @@ class ElidingTests(TestCase): self.assertEqual(utils.elide('foobar', 3), 'fo' + self.ELLIPSIS) -class CheckOverflowTests(TestCase): - - """Test check_overflow. - - Class attributes: - INT32_MIN: Minimum valid value for a signed int32. - INT32_MAX: Maximum valid value for a signed int32. - INT64_MIN: Minimum valid value for a signed int64. - INT64_MAX: Maximum valid value for a signed int64. - GOOD_VALUES: A dict of types mapped to a list of good values. - BAD_VALUES: A dict of types mapped to a list of bad values. - """ - - INT32_MIN = -(2 ** 31) - INT32_MAX = 2 ** 31 - 1 - INT64_MIN = -(2 ** 63) - INT64_MAX = 2 ** 63 - 1 - - GOOD_VALUES = { - 'int': [-1, 0, 1, 23.42, INT32_MIN, INT32_MAX], - 'int64': [-1, 0, 1, 23.42, INT64_MIN, INT64_MAX], - } - - BAD_VALUES = { - 'int': [(INT32_MIN - 1, INT32_MIN), - (INT32_MAX + 1, INT32_MAX), - (float(INT32_MAX + 1), INT32_MAX)], - 'int64': [(INT64_MIN - 1, INT64_MIN), - (INT64_MAX + 1, INT64_MAX), - (float(INT64_MAX + 1), INT64_MAX)], - } - - def test_good_values(self): - """Test values which are inside bounds.""" - for ctype, vals in self.GOOD_VALUES.items(): - for val in vals: - utils.check_overflow(val, ctype) - - def test_bad_values_fatal(self): - """Test values which are outside bounds with fatal=True.""" - for ctype, vals in self.BAD_VALUES.items(): - for (val, _) in vals: - with self.assertRaises(OverflowError, msg=ctype): - utils.check_overflow(val, ctype) - - def test_bad_values_nonfatal(self): - """Test values which are outside bounds with fatal=False.""" - for ctype, vals in self.BAD_VALUES.items(): - for (val, replacement) in vals: - newval = utils.check_overflow(val, ctype, fatal=False) - self.assertEqual(newval, replacement, - "{}: {}".format(ctype, val)) - - class ReadFileTests(TestCase): """Test read_file.""" @@ -383,59 +328,6 @@ class GetStandardDirWindowsTests(TestCase): self.app.quit() -class GetQtArgsTests(TestCase): - - """Tests for get_qt_args.""" - - def setUp(self): - self.parser = argparse.ArgumentParser() - - def _namespace(self, cmdline, flags=None, args=None): - """Get an argparse namespace object based on arguments given. - - Args: - cmdline: The given commandline. - flags: A list of strings (argument names) for flags without an - argument. - args: A list of arguemnt names for flags with an argument. - """ - if flags is not None: - for e in flags: - self.parser.add_argument(e, action='store_true') - if args is not None: - for e in args: - self.parser.add_argument(e, nargs=1) - return self.parser.parse_args(cmdline) - - def test_no_qt_args(self): - """Test commandline with no Qt arguments given.""" - ns = self._namespace(['--foo'], flags=['--foo']) - self.assertEqual(utils.get_qt_args(ns), [sys.argv[0]]) - - def test_qt_flag(self): - """Test commandline with a Qt flag.""" - ns = self._namespace(['--foo', '--qt-reverse', '--bar'], - flags=['--foo', '--qt-reverse', '--bar']) - self.assertEqual(utils.get_qt_args(ns), [sys.argv[0], '-reverse']) - - def test_qt_arg(self): - """Test commandline with a Qt argument.""" - ns = self._namespace(['--qt-stylesheet', 'foobar'], - args=['--qt-stylesheet']) - self.assertEqual(utils.get_qt_args(ns), [sys.argv[0], '-stylesheet', - 'foobar']) - - def test_qt_both(self): - """Test commandline with a Qt argument and flag.""" - ns = self._namespace(['--qt-stylesheet', 'foobar', '--qt-reverse'], - flags=['--qt-reverse'], args=['--qt-stylesheet']) - qt_args = utils.get_qt_args(ns) - self.assertEqual(qt_args[0], sys.argv[0]) - self.assertIn('-reverse', qt_args) - self.assertIn('-stylesheet', qt_args) - self.assertIn('foobar', qt_args) - - class InterpolateColorTests(TestCase): """Tests for interpolate_color. diff --git a/qutebrowser/test/utils/test_qt.py b/qutebrowser/test/utils/test_qt.py new file mode 100644 index 000000000..2b5edd833 --- /dev/null +++ b/qutebrowser/test/utils/test_qt.py @@ -0,0 +1,140 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +# pylint: disable=missing-docstring,blacklisted-name,protected-access + +"""Tests for qutebrowser.utils.qt.""" + +import sys +import argparse +import unittest +from unittest import TestCase + +import qutebrowser.utils.qt as qt + + +class CheckOverflowTests(TestCase): + + """Test check_overflow. + + Class attributes: + INT32_MIN: Minimum valid value for a signed int32. + INT32_MAX: Maximum valid value for a signed int32. + INT64_MIN: Minimum valid value for a signed int64. + INT64_MAX: Maximum valid value for a signed int64. + GOOD_VALUES: A dict of types mapped to a list of good values. + BAD_VALUES: A dict of types mapped to a list of bad values. + """ + + INT32_MIN = -(2 ** 31) + INT32_MAX = 2 ** 31 - 1 + INT64_MIN = -(2 ** 63) + INT64_MAX = 2 ** 63 - 1 + + GOOD_VALUES = { + 'int': [-1, 0, 1, 23.42, INT32_MIN, INT32_MAX], + 'int64': [-1, 0, 1, 23.42, INT64_MIN, INT64_MAX], + } + + BAD_VALUES = { + 'int': [(INT32_MIN - 1, INT32_MIN), + (INT32_MAX + 1, INT32_MAX), + (float(INT32_MAX + 1), INT32_MAX)], + 'int64': [(INT64_MIN - 1, INT64_MIN), + (INT64_MAX + 1, INT64_MAX), + (float(INT64_MAX + 1), INT64_MAX)], + } + + def test_good_values(self): + """Test values which are inside bounds.""" + for ctype, vals in self.GOOD_VALUES.items(): + for val in vals: + qt.check_overflow(val, ctype) + + def test_bad_values_fatal(self): + """Test values which are outside bounds with fatal=True.""" + for ctype, vals in self.BAD_VALUES.items(): + for (val, _) in vals: + with self.assertRaises(OverflowError, msg=ctype): + qt.check_overflow(val, ctype) + + def test_bad_values_nonfatal(self): + """Test values which are outside bounds with fatal=False.""" + for ctype, vals in self.BAD_VALUES.items(): + for (val, replacement) in vals: + newval = qt.check_overflow(val, ctype, fatal=False) + self.assertEqual(newval, replacement, + "{}: {}".format(ctype, val)) + + +class GetQtArgsTests(TestCase): + + """Tests for get_qt_args.""" + + def setUp(self): + self.parser = argparse.ArgumentParser() + + def _namespace(self, cmdline, flags=None, args=None): + """Get an argparse namespace object based on arguments given. + + Args: + cmdline: The given commandline. + flags: A list of strings (argument names) for flags without an + argument. + args: A list of arguemnt names for flags with an argument. + """ + if flags is not None: + for e in flags: + self.parser.add_argument(e, action='store_true') + if args is not None: + for e in args: + self.parser.add_argument(e, nargs=1) + return self.parser.parse_args(cmdline) + + def test_no_qt_args(self): + """Test commandline with no Qt arguments given.""" + ns = self._namespace(['--foo'], flags=['--foo']) + self.assertEqual(qt.get_qt_args(ns), [sys.argv[0]]) + + def test_qt_flag(self): + """Test commandline with a Qt flag.""" + ns = self._namespace(['--foo', '--qt-reverse', '--bar'], + flags=['--foo', '--qt-reverse', '--bar']) + self.assertEqual(qt.get_qt_args(ns), [sys.argv[0], '-reverse']) + + def test_qt_arg(self): + """Test commandline with a Qt argument.""" + ns = self._namespace(['--qt-stylesheet', 'foobar'], + args=['--qt-stylesheet']) + self.assertEqual(qt.get_qt_args(ns), [sys.argv[0], '-stylesheet', + 'foobar']) + + def test_qt_both(self): + """Test commandline with a Qt argument and flag.""" + ns = self._namespace(['--qt-stylesheet', 'foobar', '--qt-reverse'], + flags=['--qt-reverse'], args=['--qt-stylesheet']) + qt_args = qt.get_qt_args(ns) + self.assertEqual(qt_args[0], sys.argv[0]) + self.assertIn('-reverse', qt_args) + self.assertIn('-stylesheet', qt_args) + self.assertIn('foobar', qt_args) + + +if __name__ == '__main__': + unittest.main() diff --git a/qutebrowser/utils/earlyinit.py b/qutebrowser/utils/earlyinit.py index b902328b5..0e5a273c2 100644 --- a/qutebrowser/utils/earlyinit.py +++ b/qutebrowser/utils/earlyinit.py @@ -139,12 +139,11 @@ def check_pyqt_core(): # people only using the GUI notice them as well. def check_qt_version(): """Check if the Qt version is recent enough.""" - # We don't use qutebrowser.utils.misc:qt_version_check because we didn't - # check for pkg_resources yet. - from PyQt5.QtWidgets import QApplication, QMessageBox + import operator from PyQt5.QtCore import qVersion - from distutils.version import StrictVersion as Version - if Version(qVersion()) < Version('5.2.0'): + from PyQt5.QtWidgets import QApplication, QMessageBox + from qutebrowser.utils.qt import qt_version_check + if qt_version_check('5.2.0', operator.lt): app = QApplication(sys.argv) msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!", textwrap.dedent(""" diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index 1e8455cae..30c613a7d 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -17,50 +17,23 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Other utilities which don't fit anywhere else. - -Module attributes: - MAXVAL: A dictionary of C/Qt types (as string) mapped to their maximum - value. -""" +"""Other utilities which don't fit anywhere else. """ import os import re import sys import shlex import os.path -import operator import urllib.request from urllib.parse import urljoin, urlencode from functools import reduce -from distutils.version import StrictVersion as Version -from PyQt5.QtCore import QCoreApplication, QStandardPaths, QEventLoop, qVersion +from PyQt5.QtCore import QCoreApplication, QStandardPaths from PyQt5.QtGui import QColor from pkg_resources import resource_string import qutebrowser - - -MAXVALS = { - 'int': 2 ** 31 - 1, - 'int64': 2 ** 63 - 1, -} - -MINVALS = { - 'int': -(2 ** 31), - 'int64': -(2 ** 63), -} - - -def qt_version_check(version, op=operator.ge): - """Check if the Qt runtime version is the version supplied or newer. - - Args: - version: The version to check against. - op: The operator to use for the check. - """ - return op(Version(qVersion()), Version(version)) +from qutebrowser.utils.qt import qt_version_check, qt_ensure_valid def elide(text, length): @@ -73,38 +46,6 @@ def elide(text, length): return text[:length - 1] + '\u2026' -def check_overflow(arg, ctype, fatal=True): - """Check if the given argument is in bounds for the given type. - - Args: - arg: The argument to check - ctype: The C/Qt type to check as a string. - fatal: Wether to raise exceptions (True) or truncate values (False) - - Return - The truncated argument if fatal=False - The original argument if it's in bounds. - - Raise: - OverflowError if the argument is out of bounds and fatal=True. - """ - # FIXME we somehow should have nicer exceptions... - maxval = MAXVALS[ctype] - minval = MINVALS[ctype] - if arg > maxval: - if fatal: - raise OverflowError(arg) - else: - return maxval - elif arg < minval: - if fatal: - raise OverflowError(arg) - else: - return minval - else: - return arg - - def read_file(filename): """Get the contents of a file contained with qutebrowser. @@ -267,30 +208,6 @@ def actute_warning(): break -def get_qt_args(namespace): - """Get the Qt QApplication arguments based on an argparse namespace. - - Args: - namespace: The argparse namespace. - - Return: - The argv list to be passed to Qt. - """ - argv = [sys.argv[0]] - for argname, val in vars(namespace).items(): - if not argname.startswith('qt_'): - continue - elif val is None: - # flag/argument not given - continue - elif val is True: - argv.append('-' + argname[3:]) - else: - argv.append('-' + argname[3:]) - argv.append(val[0]) - return argv - - def _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent): """Get a color which is percent% interpolated between start and end. @@ -388,53 +305,3 @@ def format_size(size, base=1024, suffix=''): return '{:.02f}{}{}'.format(size, p, suffix) size /= base return '{:.02f}{}{}'.format(size, prefixes[-1], suffix) - - -def check_print_compat(): - """Check if printing should work in the given Qt version.""" - return not (os.name == 'nt' and qt_version_check('5.3.0', operator.lt)) - - -def qt_ensure_valid(obj): - """Ensure a Qt object with an .isValid() method is valid. - - Raise: - QtValueError if the object is invalid. - """ - if not obj.isValid(): - raise QtValueError(obj) - - -class QtValueError(ValueError): - - """Exception which gets raised by qt_ensure_valid.""" - - def __init__(self, obj): - try: - self.reason = obj.errorString() - except AttributeError: - self.reason = None - err = "{} is not valid".format(obj) - if self.reason: - err += ": {}".format(self.reason) - super().__init__(err) - - -class EventLoop(QEventLoop): - - """A thin wrapper around QEventLoop. - - Raises an exception when doing exec_() multiple times. - """ - - def __init__(self, parent=None): - super().__init__(parent) - self._executing = False - - def exec_(self, flags=QEventLoop.AllEvents): - """Override exec_ to raise an exception when re-running.""" - if self._executing: - raise AssertionError("Eventloop is already running!") - self._executing = True - super().exec_(flags) - self._executing = False diff --git a/qutebrowser/utils/qt.py b/qutebrowser/utils/qt.py new file mode 100644 index 000000000..b2ba2521f --- /dev/null +++ b/qutebrowser/utils/qt.py @@ -0,0 +1,162 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Misc. utilities related to Qt. + +Module attributes: + MAXVALS: A dictionary of C/Qt types (as string) mapped to their maximum + value. + MINVALS: A dictionary of C/Qt types (as string) mapped to their minimum + value. +""" + + +import os +import sys +import operator +from distutils.version import StrictVersion as Version + +from PyQt5.QtCore import QEventLoop, qVersion + + +MAXVALS = { + 'int': 2 ** 31 - 1, + 'int64': 2 ** 63 - 1, +} + +MINVALS = { + 'int': -(2 ** 31), + 'int64': -(2 ** 63), +} + + +def qt_version_check(version, op=operator.ge): + """Check if the Qt runtime version is the version supplied or newer. + + Args: + version: The version to check against. + op: The operator to use for the check. + """ + return op(Version(qVersion()), Version(version)) + + +def check_overflow(arg, ctype, fatal=True): + """Check if the given argument is in bounds for the given type. + + Args: + arg: The argument to check + ctype: The C/Qt type to check as a string. + fatal: Wether to raise exceptions (True) or truncate values (False) + + Return + The truncated argument if fatal=False + The original argument if it's in bounds. + + Raise: + OverflowError if the argument is out of bounds and fatal=True. + """ + # FIXME we somehow should have nicer exceptions... + maxval = MAXVALS[ctype] + minval = MINVALS[ctype] + if arg > maxval: + if fatal: + raise OverflowError(arg) + else: + return maxval + elif arg < minval: + if fatal: + raise OverflowError(arg) + else: + return minval + else: + return arg + + +def get_qt_args(namespace): + """Get the Qt QApplication arguments based on an argparse namespace. + + Args: + namespace: The argparse namespace. + + Return: + The argv list to be passed to Qt. + """ + argv = [sys.argv[0]] + for argname, val in vars(namespace).items(): + if not argname.startswith('qt_'): + continue + elif val is None: + # flag/argument not given + continue + elif val is True: + argv.append('-' + argname[3:]) + else: + argv.append('-' + argname[3:]) + argv.append(val[0]) + return argv + + +def check_print_compat(): + """Check if printing should work in the given Qt version.""" + return not (os.name == 'nt' and qt_version_check('5.3.0', operator.lt)) + + +def qt_ensure_valid(obj): + """Ensure a Qt object with an .isValid() method is valid. + + Raise: + QtValueError if the object is invalid. + """ + if not obj.isValid(): + raise QtValueError(obj) + + +class QtValueError(ValueError): + + """Exception which gets raised by qt_ensure_valid.""" + + def __init__(self, obj): + try: + self.reason = obj.errorString() + except AttributeError: + self.reason = None + err = "{} is not valid".format(obj) + if self.reason: + err += ": {}".format(self.reason) + super().__init__(err) + + +class EventLoop(QEventLoop): + + """A thin wrapper around QEventLoop. + + Raises an exception when doing exec_() multiple times. + """ + + def __init__(self, parent=None): + super().__init__(parent) + self._executing = False + + def exec_(self, flags=QEventLoop.AllEvents): + """Override exec_ to raise an exception when re-running.""" + if self._executing: + raise AssertionError("Eventloop is already running!") + self._executing = True + super().exec_(flags) + self._executing = False diff --git a/qutebrowser/widgets/completion.py b/qutebrowser/widgets/completion.py index 00252b9cd..5412c554a 100644 --- a/qutebrowser/widgets/completion.py +++ b/qutebrowser/widgets/completion.py @@ -31,7 +31,7 @@ import qutebrowser.commands.utils as cmdutils from qutebrowser.widgets.completiondelegate import CompletionItemDelegate from qutebrowser.config.style import set_register_stylesheet from qutebrowser.utils.completer import Completer -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class CompletionView(QTreeView): diff --git a/qutebrowser/widgets/completiondelegate.py b/qutebrowser/widgets/completiondelegate.py index 42cb82176..fef24c809 100644 --- a/qutebrowser/widgets/completiondelegate.py +++ b/qutebrowser/widgets/completiondelegate.py @@ -31,7 +31,7 @@ from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption, import qutebrowser.config.config as config from qutebrowser.models.basecompletion import Role from qutebrowser.config.style import get_stylesheet -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class CompletionItemDelegate(QStyledItemDelegate): diff --git a/qutebrowser/widgets/downloads.py b/qutebrowser/widgets/downloads.py index e07bb1f6c..390ce4bf3 100644 --- a/qutebrowser/widgets/downloads.py +++ b/qutebrowser/widgets/downloads.py @@ -24,7 +24,7 @@ from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu from qutebrowser.models.downloadmodel import DownloadModel, Role from qutebrowser.config.style import set_register_stylesheet -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class DownloadView(QListView): diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 99e8669ee..e42a3df6f 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -27,7 +27,6 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout import qutebrowser.commands.utils as cmdutils import qutebrowser.config.config as config -import qutebrowser.utils.misc as utils import qutebrowser.utils.message as message import qutebrowser.utils.log as log from qutebrowser.widgets.statusbar.bar import StatusBar @@ -35,6 +34,7 @@ from qutebrowser.widgets.tabbedbrowser import TabbedBrowser from qutebrowser.widgets.completion import CompletionView from qutebrowser.widgets.downloads import DownloadView from qutebrowser.utils.usertypes import PromptMode +from qutebrowser.utils.qt import check_overflow, qt_ensure_valid class MainWindow(QWidget): @@ -138,7 +138,7 @@ class MainWindow(QWidget): # hpoint now would be the bottom-left edge of the widget if it was on # the top of the main window. topleft_y = self.height() - self.status.height() - height - topleft_y = utils.check_overflow(topleft_y, 'int', fatal=False) + topleft_y = check_overflow(topleft_y, 'int', fatal=False) topleft = QPoint(0, topleft_y) bottomright = self.status.geometry().topRight() rect = QRect(topleft, bottomright) @@ -147,7 +147,7 @@ class MainWindow(QWidget): "topleft: {}, bottomright: {}".format( confheight, self.height(), height, contents_height, self.status.height(), topleft, bottomright)) - utils.qt_ensure_valid(rect) + qt_ensure_valid(rect) self.completion.setGeometry(rect) @cmdutils.register(instance='mainwindow', name=['quit', 'q'], nargs=0) diff --git a/qutebrowser/widgets/statusbar/prompt.py b/qutebrowser/widgets/statusbar/prompt.py index 74b55ee39..852ba92ed 100644 --- a/qutebrowser/widgets/statusbar/prompt.py +++ b/qutebrowser/widgets/statusbar/prompt.py @@ -27,7 +27,7 @@ import qutebrowser.commands.utils as cmdutils from qutebrowser.widgets.statusbar.textbase import TextBase from qutebrowser.widgets.misc import MinimalLineEdit from qutebrowser.utils.usertypes import PromptMode, Question -from qutebrowser.utils.misc import EventLoop +from qutebrowser.utils.qt import EventLoop class Prompt(QWidget): diff --git a/qutebrowser/widgets/statusbar/textbase.py b/qutebrowser/widgets/statusbar/textbase.py index 392fce658..ea5688c6f 100644 --- a/qutebrowser/widgets/statusbar/textbase.py +++ b/qutebrowser/widgets/statusbar/textbase.py @@ -23,7 +23,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QLabel, QSizePolicy from PyQt5.QtGui import QPainter -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class TextBase(QLabel): diff --git a/qutebrowser/widgets/tabbedbrowser.py b/qutebrowser/widgets/tabbedbrowser.py index 54c54dbc2..e7ee0e850 100644 --- a/qutebrowser/widgets/tabbedbrowser.py +++ b/qutebrowser/widgets/tabbedbrowser.py @@ -32,7 +32,7 @@ from qutebrowser.widgets.tabwidget import TabWidget, EmptyTabIcon from qutebrowser.widgets.webview import WebView from qutebrowser.browser.signalfilter import SignalFilter from qutebrowser.browser.commands import CommandDispatcher -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class TabbedBrowser(TabWidget): diff --git a/qutebrowser/widgets/tabwidget.py b/qutebrowser/widgets/tabwidget.py index 11ee1d7a9..6bf73e3da 100644 --- a/qutebrowser/widgets/tabwidget.py +++ b/qutebrowser/widgets/tabwidget.py @@ -26,7 +26,7 @@ from PyQt5.QtGui import QIcon, QPixmap import qutebrowser.config.config as config from qutebrowser.config.style import set_register_stylesheet from qutebrowser.utils.style import Style -from qutebrowser.utils.misc import qt_ensure_valid +from qutebrowser.utils.qt import qt_ensure_valid class EmptyTabIcon(QIcon): diff --git a/qutebrowser/widgets/webview.py b/qutebrowser/widgets/webview.py index ee1a16d74..fa8daa7ba 100644 --- a/qutebrowser/widgets/webview.py +++ b/qutebrowser/widgets/webview.py @@ -31,7 +31,8 @@ import qutebrowser.keyinput.modeman as modeman import qutebrowser.utils.message as message import qutebrowser.utils.webelem as webelem import qutebrowser.utils.log as log -from qutebrowser.utils.misc import elide, qt_ensure_valid +from qutebrowser.utils.misc import elide +from qutebrowser.utils.qt import qt_ensure_valid from qutebrowser.browser.webpage import BrowserPage from qutebrowser.browser.hints import HintManager from qutebrowser.utils.usertypes import NeighborList, enum