Repository: qutebrowser/qutebrowser
Pull request: Add ability to customise tab font based on selected status
Closes #5510 .
I have created two settings: `fonts.tabs.selected` and `fonts.tabs.unselected`. I did not create `{odd,even}` variants because I would probably have to mess with `paintEvent()`; Qt does not have `:nth-child` selectors AFAIK. In either case, I doubt that odd/even variants would be used by anyone. I could take a look at it though if you think otherwise @The-Compiler. 

Transitioning to `Font` was mostly painless. There aren't any more uses of QFont in `configdata.yml`, so I can work on #5511 after this.

Problem statement:
Introduce separate font setting for selected tab
In my fork of qutebrowser, I have set the selected tab font to bold; this way it is quicker for the eye to differentiate the selected tab from the rest. I thought that other people might find this ability useful. The idea would be to add a `fonts.tabs.selected` config value (the same idea as `colors.*.selected`), which people can set to whatever font they want (e.g. bold). It is a small change and I would be happy to make a pull request for it. I just want to first make sure that it would be a welcome addition.

Hints from issue discussion:
I assume you mean this? https://github.com/qutebrowser/qutebrowser/commit/8b9cb2f98883e46ab438d0c469c8f36708fff336

Seems fine to me to add a font setting - though I'm not sure how easy it is, because `TabBar` doesn't use a stylesheet for its font currently. I went digging a bit for why that is:

- 95df8a045ca6febac8ff8da20a2942c81696c288 avoided that stylesheet because of this Qt bug: [[QTBUG-40263] Radio buttons/checkboxes in QWebView broken if using stylesheet in a QTabWidget - Qt Bug Tracker](https://bugreports.qt.io/browse/QTBUG-40263)
- However, that only affects QtWebKit on Windows, so probably really isn't relevant anymore
- Since 2dd8966fdcf11972062c540b7a787e4d0de8d372, we set a stylesheet anyways, and nobody has complained so far

So it'd probably be a good idea to switch to stylesheets for the font entirely instead of using `setFont` for the fonts as part of this change - would you be willing to work on that? To do so, change the type of `fonts.tabs` to `Font` (instead of `QtFont`) and add it to the stylesheet. I *think* no config migration should be needed, because anything that's a valid `QtFont` string should also be valid for `Font`.
Ah, also let me note that keeping `fonts.tabs` and adding `fonts.tabs.selected` won't work, because the latter will shadow the former. You'd probably need to rename `fonts.tabs` to `fonts.tabs.unselected` (or perhaps even `fonts.tabs.{odd,even}`, like with `colors.tabs.{odd,even}`).
Sounds good. I created a PR for this.

Commit message: Remove unnecessary call to update()

Also, split a line that pylint complains is too long.

Patch to review:
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index cc020ea8830..648168a606f 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -214,7 +214,8 @@
 |<<fonts.messages.warning,fonts.messages.warning>>|Font used for warning messages.
 |<<fonts.prompts,fonts.prompts>>|Font used for prompts.
 |<<fonts.statusbar,fonts.statusbar>>|Font used in the statusbar.
-|<<fonts.tabs,fonts.tabs>>|Font used in the tab bar.
+|<<fonts.tabs.selected,fonts.tabs.selected>>|Font used in the tab bar for selected tabs.
+|<<fonts.tabs.unselected,fonts.tabs.unselected>>|Font used in the tab bar for unselected tabs.
 |<<fonts.web.family.cursive,fonts.web.family.cursive>>|Font family for cursive fonts.
 |<<fonts.web.family.fantasy,fonts.web.family.fantasy>>|Font family for fantasy fonts.
 |<<fonts.web.family.fixed,fonts.web.family.fixed>>|Font family for fixed fonts.
@@ -2785,11 +2786,19 @@ Type: <<types,Font>>
 
 Default: +pass:[default_size default_family]+
 
-[[fonts.tabs]]
-=== fonts.tabs
-Font used in the tab bar.
+[[fonts.tabs.selected]]
+=== fonts.tabs.selected
+Font used in the tab bar for selected tabs.
 
-Type: <<types,QtFont>>
+Type: <<types,Font>>
+
+Default: +pass:[default_size default_family]+
+
+[[fonts.tabs.unselected]]
+=== fonts.tabs.unselected
+Font used in the tab bar for unselected tabs.
+
+Type: <<types,Font>>
 
 Default: +pass:[default_size default_family]+
 
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 664f9facfd8..68b17fdded1 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2872,10 +2872,15 @@ fonts.statusbar:
   type: Font
   desc: Font used in the statusbar.
 
-fonts.tabs:
+fonts.tabs.selected:
   default: default_size default_family
-  type: QtFont
-  desc: Font used in the tab bar.
+  type: Font
+  desc: Font used in the tab bar for selected tabs.
+
+fonts.tabs.unselected:
+  default: default_size default_family
+  type: Font
+  desc: Font used in the tab bar for unselected tabs.
 
 fonts.web.family.standard:
   default: ''
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index 7d567ebd3ce..c07ad04e9da 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -340,6 +340,10 @@ def migrate(self) -> None:
                                        r'(?<!{)\{title\}(?!})',
                                        r'{current_title}')
 
+        self._migrate_to_multiple('fonts.tabs',
+                                  ('fonts.tabs.selected',
+                                   'fonts.tabs.unselected'))
+
         # content.headers.user_agent can't be empty to get the default anymore.
         setting = 'content.headers.user_agent'
         self._migrate_none(setting, configdata.DATA[setting].default)
@@ -446,6 +450,19 @@ def _migrate_none(self, name: str, value: str) -> None:
                 self._settings[name][scope] = value
                 self.changed.emit()
 
+    def _migrate_to_multiple(self, old_name: str,
+                             new_names: typing.Iterator[str]) -> None:
+        if old_name not in self._settings:
+            return
+
+        for new_name in new_names:
+            self._settings[new_name] = {}
+            for scope, val in self._settings[old_name].items():
+                self._settings[new_name][scope] = val
+
+        del self._settings[old_name]
+        self.changed.emit()
+
     def _migrate_string_value(self, name: str,
                               source: str,
                               target: str) -> None:
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index 558713fdf9a..60006fa147f 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -385,8 +385,13 @@ class TabBar(QTabBar):
 
     STYLESHEET = """
         TabBar {
+            font: {{ conf.fonts.tabs.unselected }};
             background-color: {{ conf.colors.tabs.bar.bg }};
         }
+
+        TabBar::tab:selected {
+            font: {{ conf.fonts.tabs.selected }};
+        }
     """
 
     new_tab_requested = pyqtSignal()
@@ -395,8 +400,6 @@ def __init__(self, win_id, parent=None):
         super().__init__(parent)
         self._win_id = win_id
         self.setStyle(TabBarStyle())
-        self._set_font()
-        config.instance.changed.connect(self._on_config_changed)
         self.vertical = False
         self._auto_hide_timer = QTimer()
         self._auto_hide_timer.setSingleShot(True)
@@ -405,6 +408,9 @@ def __init__(self, win_id, parent=None):
         self.setAutoFillBackground(True)
         self.drag_in_progress = False
         stylesheet.set_register(self)
+        self.ensurePolished()
+        config.instance.changed.connect(self._on_config_changed)
+        self._set_icon_size()
         QTimer.singleShot(0, self.maybe_hide)
 
     def __repr__(self):
@@ -416,8 +422,9 @@ def _current_tab(self):
 
     @pyqtSlot(str)
     def _on_config_changed(self, option: str) -> None:
-        if option == 'fonts.tabs':
-            self._set_font()
+        if option.startswith('fonts.tabs.'):
+            self.ensurePolished()
+            self._set_icon_size()
         elif option == 'tabs.favicons.scale':
             self._set_icon_size()
         elif option == 'tabs.show_switching_delay':
@@ -433,7 +440,9 @@ def _on_config_changed(self, option: str) -> None:
                       "tabs.padding",
                       "tabs.indicator.width",
                       "tabs.min_width",
-                      "tabs.pinned.shrink"]:
+                      "tabs.pinned.shrink",
+                      "fonts.tabs.selected",
+                      "fonts.tabs.unselected"]:
             self._minimum_tab_size_hint_helper.cache_clear()
             self._minimum_tab_height.cache_clear()
 
@@ -506,14 +515,6 @@ def refresh(self):
         # code sets layoutDirty so it actually relayouts the tabs.
         self.setIconSize(self.iconSize())
 
-    def _set_font(self):
-        """Set the tab bar font."""
-        self.setFont(config.val.fonts.tabs)
-        self._set_icon_size()
-        # clear tab size cache
-        self._minimum_tab_size_hint_helper.cache_clear()
-        self._minimum_tab_height.cache_clear()
-
     def _set_icon_size(self):
         """Set the tab bar favicon size."""
         size = self.fontMetrics().height() - 2
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index 4c0b6305cc6..953ab60b5f3 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -588,6 +588,17 @@ def test_font_default_family(self, yaml, autoconfig, old, new):
     def test_font_replacements(self, migration_test, setting, old, new):
         migration_test(setting, old, new)
 
+    def test_migrate_multiple(self, yaml, autoconfig):
+        val = '10pt default_family'
+        autoconfig.write({'fonts.tabs': {'global': val}})
+
+        yaml.load()
+        yaml._save()
+
+        data = autoconfig.read()
+        assert data['fonts.tabs.unselected']['global'] == val
+        assert data['fonts.tabs.selected']['global'] == val
+
 
 class ConfPy:
 


Code review comments:
diff_hunk: @@ -2872,10 +2872,15 @@ fonts.statusbar:
   type: Font
   desc: Font used in the statusbar.
 
-fonts.tabs:
+fonts.tabs.selected:
   default: default_size default_family
-  type: QtFont
-  desc: Font used in the tab bar.
+  type: Font
+  desc: Font used in the tab bar for selected tabs.
+
+fonts.tabs.unselected:
+  default: default_size default_family
+  type: Font
+  desc: Font used in the tab bar for unselected tabs.
path: qutebrowser/config/configdata.yml
text: @user1:
```suggestion
  desc: Font used for selected tabs.

fonts.tabs.unselected:
  default: default_size default_family
  type: Font
  desc: Font used for unselected tabs.
```