Repository: qutebrowser/qutebrowser

Issue:
Get rid of QtFont config type?
Only `fonts.tabs` and `fonts.debug_console` use the `QtFont` config type. After #5510, the only remaining place using it will be `fonts.debug_console`. I don't think there's a good reason for it to use `QtFont` rather than `Font`, or at least I don't see any in 73214836da3b2723a11b13ec2890484dedaf455e when it was added.

Thus, after #5510, it can probably be removed entirely.

cc @kontheocharis if you happen to want to work on this after #5510.


Discussion:
I can work on this.

Resolving patch:
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -56,7 +56,7 @@
 import attr
 import yaml
 from PyQt5.QtCore import QUrl, Qt
-from PyQt5.QtGui import QColor, QFont, QFontDatabase
+from PyQt5.QtGui import QColor, QFontDatabase
 from PyQt5.QtWidgets import QTabWidget, QTabBar, QApplication
 from PyQt5.QtNetwork import QNetworkProxy
 
@@ -1157,7 +1157,7 @@ def to_py(self, value: _StrUnset) -> _StrUnsetNone:
 
 class FontBase(BaseType):
 
-    """Base class for Font/QtFont/FontFamily."""
+    """Base class for Font/FontFamily."""
 
     # Gets set when the config is initialized.
     default_family = None  # type: str
@@ -1292,96 +1292,6 @@ def to_py(self, value: _StrUnset) -> _StrUnsetNone:
         return value
 
 
-class QtFont(FontBase):
-
-    """A Font which gets converted to a QFont."""
-
-    __doc__ = Font.__doc__  # for src2asciidoc.py
-
-    def _parse_families(self, family_str: str) -> configutils.FontFamilies:
-        if family_str == 'default_family' and self.default_family is not None:
-            family_str = self.default_family
-
-        return configutils.FontFamilies.from_str(family_str)
-
-    def _set_style(self, font: QFont, match: typing.Match) -> None:
-        style = match.group('style')
-        style_map = {
-            'normal': QFont.StyleNormal,
-            'italic': QFont.StyleItalic,
-            'oblique': QFont.StyleOblique,
-        }
-        if style:
-            font.setStyle(style_map[style])
-        else:
-            font.setStyle(QFont.StyleNormal)
-
-    def _set_weight(self, font: QFont, match: typing.Match) -> None:
-        weight = match.group('weight')
-        namedweight = match.group('namedweight')
-        weight_map = {
-            'normal': QFont.Normal,
-            'bold': QFont.Bold,
-        }
-        if namedweight:
-            font.setWeight(weight_map[namedweight])
-        elif weight:
-            # based on qcssparser.cpp:setFontWeightFromValue
-            font.setWeight(min(int(weight) // 8, 99))
-        else:
-            font.setWeight(QFont.Normal)
-
-    def _set_size(self, font: QFont, match: typing.Match) -> None:
-        size = match.group('size')
-        if size:
-            if size == 'default_size':
-                size = self.default_size
-
-            if size is None:
-                # initial validation before default_size is set up.
-                pass
-            elif size.lower().endswith('pt'):
-                font.setPointSizeF(float(size[:-2]))
-            elif size.lower().endswith('px'):
-                font.setPixelSize(int(size[:-2]))
-            else:
-                # This should never happen as the regex only lets pt/px
-                # through.
-                raise ValueError("Unexpected size unit in {!r}!".format(
-                    size))  # pragma: no cover
-
-    def _set_families(self, font: QFont, match: typing.Match) -> None:
-        family_str = match.group('family')
-        families = self._parse_families(family_str)
-        if hasattr(font, 'setFamilies'):
-            # Added in Qt 5.13
-            font.setFamily(families.family)  # type: ignore[arg-type]
-            font.setFamilies(list(families))
-        else:  # pragma: no cover
-            font.setFamily(families.to_str(quote=False))
-
-    def to_py(self, value: _StrUnset) -> typing.Union[_UnsetNone, QFont]:
-        self._basic_py_validation(value, str)
-        if isinstance(value, usertypes.Unset):
-            return value
-        elif not value:
-            return None
-
-        match = self.font_regex.fullmatch(value)
-        if not match:  # pragma: no cover
-            # This should never happen, as the regex always matches everything
-            # as family.
-            raise configexc.ValidationError(value, "must be a valid font")
-
-        font = QFont()
-        self._set_style(font, match)
-        self._set_weight(font, match)
-        self._set_size(font, match)
-        self._set_families(font, match)
-
-        return font
-
-
 class Regex(BaseType):
 
     """A regular expression.
diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py
--- a/qutebrowser/misc/consolewidget.py
+++ b/qutebrowser/misc/consolewidget.py
@@ -27,7 +27,7 @@
 from PyQt5.QtWidgets import QTextEdit, QWidget, QVBoxLayout, QApplication
 from PyQt5.QtGui import QTextCursor
 
-from qutebrowser.config import config
+from qutebrowser.config import stylesheet
 from qutebrowser.misc import cmdhistory, miscwidgets
 from qutebrowser.utils import utils, objreg
 
@@ -55,8 +55,6 @@ def __init__(self, _namespace, parent):
             _namespace: The local namespace of the interpreter.
         """
         super().__init__(parent=parent)
-        self._update_font()
-        config.instance.changed.connect(self._update_font)
         self._history = cmdhistory.History(parent=self)
         self.returnPressed.connect(self.on_return_pressed)
 
@@ -106,11 +104,6 @@ def keyPressEvent(self, e):
         else:
             super().keyPressEvent(e)
 
-    @config.change_filter('fonts.debug_console')
-    def _update_font(self):
-        """Set the correct font."""
-        self.setFont(config.val.fonts.debug_console)
-
 
 class ConsoleTextEdit(QTextEdit):
 
@@ -120,18 +113,11 @@ def __init__(self, parent=None):
         super().__init__(parent)
         self.setAcceptRichText(False)
         self.setReadOnly(True)
-        config.instance.changed.connect(self._update_font)
-        self._update_font()
         self.setFocusPolicy(Qt.ClickFocus)
 
     def __repr__(self):
         return utils.get_repr(self)
 
-    @config.change_filter('fonts.debug_console')
-    def _update_font(self):
-        """Update font when config changed."""
-        self.setFont(config.val.fonts.debug_console)
-
     def append_text(self, text):
         """Append new text and scroll output to bottom.
 
@@ -157,6 +143,12 @@ class ConsoleWidget(QWidget):
         _interpreter: The InteractiveInterpreter to execute code with.
     """
 
+    STYLESHEET = """
+        ConsoleWidget > ConsoleTextEdit, ConsoleWidget > ConsoleLineEdit {
+            font: {{ conf.fonts.debug_console }};
+        }
+    """
+
     def __init__(self, parent=None):
         super().__init__(parent)
         if not hasattr(sys, 'ps1'):
@@ -182,6 +174,7 @@ def __init__(self, parent=None):
         self._vbox.setSpacing(0)
         self._vbox.addWidget(self._output)
         self._vbox.addWidget(self._lineedit)
+        stylesheet.set_register(self)
         self.setLayout(self._vbox)
         self._lineedit.setFocus()
         self._interpreter = code.InteractiveInterpreter(namespace)


Test patch:
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -578,8 +578,6 @@ def test_font_default_family(self, yaml, autoconfig, old, new):
     @pytest.mark.parametrize('setting, old, new', [
         # Font
         ('fonts.hints', '10pt monospace', '10pt default_family'),
-        # QtFont
-        ('fonts.debug_console', '10pt monospace', '10pt default_family'),
         # String
         ('content.headers.accept_language', 'x monospace', 'x monospace'),
         # Not at end of string
diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py
--- a/tests/unit/config/test_configinit.py
+++ b/tests/unit/config/test_configinit.py
@@ -344,12 +344,10 @@ def test_late_init(self, init_patch, monkeypatch, fake_save_manager, args,
         # fonts.default_family and font settings customized
         # https://github.com/qutebrowser/qutebrowser/issues/3096
         ([('fonts.default_family', 'Comic Sans MS'),
-          ('fonts.debug_console', '12pt default_family'),
           ('fonts.keyhint', '12pt default_family')], 12, 'Comic Sans MS'),
         # as above, but with default_size
         ([('fonts.default_family', 'Comic Sans MS'),
           ('fonts.default_size', '23pt'),
-          ('fonts.debug_console', 'default_size default_family'),
           ('fonts.keyhint', 'default_size default_family')],
          23, 'Comic Sans MS'),
     ])
@@ -381,10 +379,6 @@ def test_fonts_defaults_init(self, init_patch, args, config_tmpdir,
         # Font
         expected = '{}pt "{}"'.format(size, family)
         assert config.instance.get('fonts.keyhint') == expected
-        # QtFont
-        font = config.instance.get('fonts.debug_console')
-        assert font.pointSize() == size
-        assert font.family() == family
 
     @pytest.fixture
     def run_configinit(self, init_patch, fake_save_manager, args):
@@ -405,10 +399,6 @@ def test_fonts_defaults_later(self, run_configinit):
 
         assert 'fonts.keyhint' in changed_options  # Font
         assert config.instance.get('fonts.keyhint') == '23pt "Comic Sans MS"'
-        assert 'fonts.debug_console' in changed_options  # QtFont
-        debug_console_font = config.instance.get('fonts.debug_console')
-        assert debug_console_font.family() == 'Comic Sans MS'
-        assert debug_console_font.pointSize() == 23
 
         # Font subclass, but doesn't end with "default_family"
         assert 'fonts.web.family.standard' not in changed_options
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -255,8 +255,8 @@ def test_from_str_hypothesis(self, klass, s):
 
         # For some types, we don't actually get the internal (YAML-like) value
         # back from from_str(), so we can't convert it back.
-        if klass in [configtypes.FuzzyUrl, configtypes.QtFont,
-                     configtypes.ShellCommand, configtypes.Url]:
+        if klass in [configtypes.FuzzyUrl, configtypes.ShellCommand,
+                     configtypes.Url]:
             return
 
         converted = typ.to_str(val)
@@ -1381,8 +1381,6 @@ class FontDesc:
 
 class TestFont:
 
-    """Test Font/QtFont."""
-
     TESTS = {
         # (style, weight, pointsize, pixelsize, family
         '"Foobar Neue"':
@@ -1430,52 +1428,17 @@ class TestFont:
 
     font_xfail = pytest.mark.xfail(reason='FIXME: #103')
 
-    @pytest.fixture(params=[configtypes.Font, configtypes.QtFont])
-    def klass(self, request):
-        return request.param
-
     @pytest.fixture
-    def font_class(self):
+    def klass(self):
         return configtypes.Font
 
     @pytest.fixture
-    def qtfont_class(self):
-        return configtypes.QtFont
+    def font_class(self):
+        return configtypes.Font
 
     @pytest.mark.parametrize('val, desc', sorted(TESTS.items()))
     def test_to_py_valid(self, klass, val, desc):
-        if klass is configtypes.Font:
-            expected = val
-        elif klass is configtypes.QtFont:
-            expected = Font.fromdesc(desc)
-        assert klass().to_py(val) == expected
-
-    def test_qtfont(self, qtfont_class):
-        """Test QtFont's to_py."""
-        value = Font(qtfont_class().to_py('10pt "Foobar Neue", Fubar'))
-
-        if hasattr(value, 'families'):
-            # Added in Qt 5.13
-            assert value.family() == 'Foobar Neue'
-            assert value.families() == ['Foobar Neue', 'Fubar']
-        else:
-            assert value.family() == 'Foobar Neue, Fubar'
-
-        assert value.weight() == QFont.Normal
-        assert value.style() == QFont.StyleNormal
-
-        assert value.pointSize() == 10
-
-    def test_qtfont_float(self, qtfont_class):
-        """Test QtFont's to_py with a float as point size.
-
-        We can't test the point size for equality as Qt seems to do some
-        rounding as appropriate.
-        """
-        value = Font(qtfont_class().to_py('10.5pt Test'))
-        assert value.family() == 'Test'
-        assert value.pointSize() >= 10
-        assert value.pointSize() <= 11
+        assert klass().to_py(val) == val
 
     @pytest.mark.parametrize('val', [
         pytest.param('green "Foobar Neue"', marks=font_xfail),
@@ -1495,13 +1458,7 @@ def test_to_py_invalid(self, klass, val):
 
     def test_defaults_replacement(self, klass, monkeypatch):
         configtypes.FontBase.set_defaults(['Terminus'], '23pt')
-        if klass is configtypes.Font:
-            expected = '23pt Terminus'
-        elif klass is configtypes.QtFont:
-            desc = FontDesc(QFont.StyleNormal, QFont.Normal, 23, None,
-                            'Terminus')
-            expected = Font.fromdesc(desc)
-        assert klass().to_py('23pt default_family') == expected
+        assert klass().to_py('23pt default_family') == '23pt Terminus'
 
 
 class TestFontFamily:
diff --git a/tests/unit/scripts/test_run_vulture.py b/tests/unit/scripts/test_run_vulture.py
--- a/tests/unit/scripts/test_run_vulture.py
+++ b/tests/unit/scripts/test_run_vulture.py
@@ -18,8 +18,6 @@
 # You should have received a copy of the GNU General Public License
 # along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.
 
-import os
-import pathlib
 import sys
 import textwrap