Make sure font size changes propagate to QTabWidgets
Due to a bug in Qt, font size changes (which are now possible thanks to https://github.com/glue-viz/glue/pull/1905) don't propagate to QTabWidgets by default, which is why we need code such as:

https://github.com/glue-viz/glue/blob/9f8c2a567192f37f143d2133a0dff43738d4f99e/glue/viewers/image/qt/options_widget.py#L24

When setting up the widgets. We will need a workaround for propagating the font size changes too.


diff --git a/glue/app/qt/application.py b/glue/app/qt/application.py
index 1bdbec2b..fd656cf9 100644
--- a/glue/app/qt/application.py
+++ b/glue/app/qt/application.py
@@ -12,13 +12,13 @@ from qtpy import QtCore, QtWidgets, QtGui, compat
 from qtpy.QtCore import Qt
 
 from glue.core.application_base import Application
-from glue.core.message import ApplicationClosedMessage, DataCollectionMessage
+from glue.core.message import ApplicationClosedMessage, DataCollectionMessage, SettingsChangeMessage
 from glue.core import command, BaseData
 from glue.core.coordinates import WCSCoordinates
 from glue import env
 from glue.main import load_plugins
 from glue.icons.qt import get_icon
-from glue.utils.qt import get_qapp
+from glue.utils.qt import get_qapp, update_global_font_size
 from glue.app.qt.actions import action
 from glue.dialogs.data_wizard.qt import data_wizard
 from glue.dialogs.link_editor.qt import LinkEditor
@@ -476,6 +476,7 @@ class GlueApplication(Application, QtWidgets.QMainWindow):
         self._log.hide()
 
         self._hub.subscribe(self, DataCollectionMessage, handler=self._on_data_collection_change)
+        self._hub.subscribe(self, SettingsChangeMessage, handler=self._on_ui_settings_change)
 
     def _artihmetic_dialog(self, *event):
         dialog = ArithmeticEditorWidget(self.data_collection)
@@ -486,6 +487,9 @@ class GlueApplication(Application, QtWidgets.QMainWindow):
         self._button_link_data.setEnabled(len(self.data_collection) > 1)
         self._button_edit_components.setEnabled(len(self.data_collection) > 0)
 
+    def _on_ui_settings_change(self, *event):
+        update_global_font_size()
+
     def keyPressEvent(self, event):
         if self.current_tab.activeSubWindow() and self.current_tab.activeSubWindow().widget():
             active_window = self.current_tab.activeSubWindow().widget()
diff --git a/glue/app/qt/preferences.py b/glue/app/qt/preferences.py
index a5397378..9f7ced07 100644
--- a/glue/app/qt/preferences.py
+++ b/glue/app/qt/preferences.py
@@ -28,6 +28,7 @@ class PreferencesDialog(QtWidgets.QDialog):
     data_alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1))
     data_apply = ButtonProperty('ui.checkbox_apply')
     save_to_disk = ButtonProperty('ui.checkbox_save')
+    font_size = ValueProperty('ui.spinner_font_size')
 
     def __init__(self, application, parent=None):
 
@@ -57,6 +58,7 @@ class PreferencesDialog(QtWidgets.QDialog):
         self.foreground = settings.FOREGROUND_COLOR
         self.data_color = settings.DATA_COLOR
         self.data_alpha = settings.DATA_ALPHA
+        self.font_size = settings.FONT_SIZE
 
         self._update_theme_from_colors()
 
@@ -107,6 +109,7 @@ class PreferencesDialog(QtWidgets.QDialog):
         settings.BACKGROUND_COLOR = self.background
         settings.DATA_COLOR = self.data_color
         settings.DATA_ALPHA = self.data_alpha
+        settings.FONT_SIZE = self.font_size
 
         for pane in self.panes:
             pane.finalize()
@@ -122,7 +125,7 @@ class PreferencesDialog(QtWidgets.QDialog):
         app = self._app()
 
         if app is not None:
-            app._hub.broadcast(SettingsChangeMessage(self, ('FOREGROUND_COLOR', 'BACKGROUND_COLOR')))
+            app._hub.broadcast(SettingsChangeMessage(self, ('FOREGROUND_COLOR', 'BACKGROUND_COLOR', 'FONT_SIZE')))
             if self.data_apply:  # If requested, trigger data to update color
                 app.set_data_color(settings.DATA_COLOR, settings.DATA_ALPHA)
 
diff --git a/glue/app/qt/preferences.ui b/glue/app/qt/preferences.ui
index c7c789f9..f957464d 100644
--- a/glue/app/qt/preferences.ui
+++ b/glue/app/qt/preferences.ui
@@ -68,62 +68,76 @@
        <string>User interface</string>
       </attribute>
       <layout class="QGridLayout" name="gridLayout_2">
-       <item row="0" column="0">
-        <widget class="QLabel" name="label_5">
+       <item row="8" column="0">
+        <widget class="QLabel" name="label_7">
          <property name="text">
-          <string>Theme for viewers:</string>
+          <string>Global Font Size</string>
          </property>
         </widget>
        </item>
-       <item row="0" column="1">
-        <widget class="QComboBox" name="combo_theme">
-         <property name="sizeAdjustPolicy">
-          <enum>QComboBox::AdjustToMinimumContentsLength</enum>
-         </property>
+       <item row="10" column="0" colspan="2">
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
-          <property name="text">
-           <string>Black on White</string>
-          </property>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
          </item>
          <item>
-          <property name="text">
-           <string>White on Black</string>
-          </property>
+          <widget class="QPushButton" name="button_reset_dialogs">
+           <property name="text">
+            <string>Reset visibility of info messages and warnings</string>
+           </property>
+          </widget>
          </item>
          <item>
-          <property name="text">
-           <string>Custom</string>
-          </property>
+          <spacer name="horizontalSpacer_3">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
          </item>
-        </widget>
+        </layout>
        </item>
-       <item row="1" column="0" colspan="2">
-        <widget class="Line" name="line">
+       <item row="4" column="0" colspan="2">
+        <widget class="Line" name="line_2">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
         </widget>
        </item>
-       <item row="2" column="0">
-        <widget class="QLabel" name="label_2">
+       <item row="5" column="0">
+        <widget class="QLabel" name="label_4">
          <property name="text">
-          <string>Background color:</string>
+          <string>Default data color:</string>
          </property>
         </widget>
        </item>
-       <item row="2" column="1">
-        <widget class="QColorBox" name="color_background">
-         <property name="text">
-          <string/>
+       <item row="11" column="0">
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
          </property>
-        </widget>
-       </item>
-       <item row="3" column="0">
-        <widget class="QLabel" name="label_3">
-         <property name="text">
-          <string>Foreground color:</string>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
          </property>
-        </widget>
+        </spacer>
        </item>
        <item row="3" column="1">
         <widget class="QColorBox" name="color_foreground">
@@ -132,31 +146,37 @@
          </property>
         </widget>
        </item>
-       <item row="4" column="0" colspan="2">
-        <widget class="Line" name="line_2">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
+       <item row="5" column="1">
+        <widget class="QColorBox" name="color_default_data">
+         <property name="text">
+          <string/>
          </property>
         </widget>
        </item>
-       <item row="5" column="0">
-        <widget class="QLabel" name="label_4">
+       <item row="7" column="0">
+        <widget class="QLabel" name="label_6">
          <property name="text">
-          <string>Default data color:</string>
+          <string>Apply to existing datasets:</string>
          </property>
         </widget>
        </item>
-       <item row="5" column="1">
-        <widget class="QColorBox" name="color_default_data">
-         <property name="text">
-          <string/>
+       <item row="9" column="0">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
          </property>
-        </widget>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
        </item>
-       <item row="6" column="0">
-        <widget class="QLabel" name="label">
+       <item row="3" column="0">
+        <widget class="QLabel" name="label_3">
          <property name="text">
-          <string>Default data transparency:</string>
+          <string>Foreground color:</string>
          </property>
         </widget>
        </item>
@@ -170,13 +190,6 @@
          </property>
         </widget>
        </item>
-       <item row="7" column="0">
-        <widget class="QLabel" name="label_6">
-         <property name="text">
-          <string>Apply to existing datasets:</string>
-         </property>
-        </widget>
-       </item>
        <item row="7" column="1">
         <widget class="QCheckBox" name="checkbox_apply">
          <property name="text">
@@ -187,68 +200,87 @@
          </property>
         </widget>
        </item>
-       <item row="8" column="0">
-        <spacer name="verticalSpacer">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
+       <item row="6" column="0">
+        <widget class="QLabel" name="label">
+         <property name="text">
+          <string>Default data transparency:</string>
          </property>
-        </spacer>
+        </widget>
        </item>
-       <item row="10" column="0">
-        <spacer name="verticalSpacer_2">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
+       <item row="0" column="1">
+        <widget class="QComboBox" name="combo_theme">
+         <property name="sizeAdjustPolicy">
+          <enum>QComboBox::AdjustToMinimumContentsLength</enum>
          </property>
-        </spacer>
-       </item>
-       <item row="9" column="0" colspan="2">
-        <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
-          <spacer name="horizontalSpacer_2">
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-           <property name="sizeHint" stdset="0">
-            <size>
-             <width>40</width>
-             <height>20</height>
-            </size>
-           </property>
-          </spacer>
+          <property name="text">
+           <string>Black on White</string>
+          </property>
          </item>
          <item>
-          <widget class="QPushButton" name="button_reset_dialogs">
-           <property name="text">
-            <string>Reset visibility of info messages and warnings</string>
-           </property>
-          </widget>
+          <property name="text">
+           <string>White on Black</string>
+          </property>
          </item>
          <item>
-          <spacer name="horizontalSpacer_3">
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-           <property name="sizeHint" stdset="0">
-            <size>
-             <width>40</width>
-             <height>20</height>
-            </size>
-           </property>
-          </spacer>
+          <property name="text">
+           <string>Custom</string>
+          </property>
          </item>
-        </layout>
+        </widget>
+       </item>
+       <item row="1" column="0" colspan="2">
+        <widget class="Line" name="line">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <widget class="QLabel" name="label_2">
+         <property name="text">
+          <string>Background color:</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <widget class="QColorBox" name="color_background">
+         <property name="text">
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="0">
+        <widget class="QLabel" name="label_5">
+         <property name="text">
+          <string>Theme for viewers:</string>
+         </property>
+        </widget>
+       </item>
+       <item row="8" column="1">
+        <widget class="QDoubleSpinBox" name="spinner_font_size">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="toolTip">
+          <string>Font size for most widgets (Default: 8.0)</string>
+         </property>
+         <property name="decimals">
+          <number>1</number>
+         </property>
+         <property name="minimum">
+          <double>8.000000000000000</double>
+         </property>
+         <property name="maximum">
+          <double>56.000000000000000</double>
+         </property>
+         <property name="value">
+          <double>8.000000000000000</double>
+         </property>
+        </widget>
        </item>
       </layout>
       <zorder>combo_theme</zorder>
@@ -265,9 +297,9 @@
       <zorder>label_6</zorder>
       <zorder>color_background</zorder>
       <zorder>line_2</zorder>
-      <zorder>horizontalLayoutWidget</zorder>
       <zorder>verticalSpacer_2</zorder>
-      <zorder>button_reset_dialogs</zorder>
+      <zorder>label_7</zorder>
+      <zorder>spinner_font_size</zorder>
      </widget>
     </widget>
    </item>
diff --git a/glue/app/qt/tests/test_preferences.py b/glue/app/qt/tests/test_preferences.py
index f7abcf06..4e822275 100644
--- a/glue/app/qt/tests/test_preferences.py
+++ b/glue/app/qt/tests/test_preferences.py
@@ -37,6 +37,7 @@ class TestPreferences():
             settings.BACKGROUND_COLOR = (0, 0.5, 1)
             settings.DATA_COLOR = (1, 0.5, 0.25)
             settings.DATA_ALPHA = 0.3
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -49,6 +50,7 @@ class TestPreferences():
             assert rgb(settings.BACKGROUND_COLOR) == (0, 0.5, 1)
             assert rgb(settings.DATA_COLOR) == (1, 0.5, 0.25)
             assert settings.DATA_ALPHA == 0.3
+            assert settings.FONT_SIZE == 8.0
 
     def test_theme_autodetect(self):
 
@@ -60,6 +62,7 @@ class TestPreferences():
             settings.BACKGROUND_COLOR = 'black'
             settings.DATA_COLOR = '0.75'
             settings.DATA_ALPHA = 0.8
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -70,6 +73,7 @@ class TestPreferences():
             settings.BACKGROUND_COLOR = 'white'
             settings.DATA_COLOR = '0.35'
             settings.DATA_ALPHA = 0.8
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -86,6 +90,7 @@ class TestPreferences():
             settings.BACKGROUND_COLOR = (0, 0.5, 1)
             settings.DATA_COLOR = (1, 0.5, 0.25)
             settings.DATA_ALPHA = 0.3
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -96,6 +101,7 @@ class TestPreferences():
             assert rgb(settings.BACKGROUND_COLOR) == (0, 0, 0)
             assert rgb(settings.DATA_COLOR) == (0.75, 0.75, 0.75)
             assert settings.DATA_ALPHA == 0.8
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -106,6 +112,7 @@ class TestPreferences():
             assert rgb(settings.BACKGROUND_COLOR) == (1, 1, 1)
             assert rgb(settings.DATA_COLOR) == (0.35, 0.35, 0.35)
             assert settings.DATA_ALPHA == 0.8
+            settings.FONT_SIZE = 8.0
 
     def test_custom_changes(self):
 
@@ -117,6 +124,7 @@ class TestPreferences():
             settings.BACKGROUND_COLOR = (0, 0.5, 1)
             settings.DATA_COLOR = (1, 0.5, 0.25)
             settings.DATA_ALPHA = 0.3
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -127,6 +135,7 @@ class TestPreferences():
             assert rgb(settings.BACKGROUND_COLOR) == (0, 0.5, 1)
             assert rgb(settings.DATA_COLOR) == (1, 0.5, 0.25)
             assert settings.DATA_ALPHA == 0.3
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -137,6 +146,7 @@ class TestPreferences():
             assert rgb(settings.BACKGROUND_COLOR) == (1, 0, 1)
             assert rgb(settings.DATA_COLOR) == (1, 0.5, 0.25)
             assert settings.DATA_ALPHA == 0.3
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -147,6 +157,7 @@ class TestPreferences():
             assert rgb(settings.BACKGROUND_COLOR) == (1, 0, 1)
             assert rgb(settings.DATA_COLOR) == (1, 1, 0.5)
             assert settings.DATA_ALPHA == 0.3
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -157,6 +168,18 @@ class TestPreferences():
             assert rgb(settings.BACKGROUND_COLOR) == (1, 0, 1)
             assert rgb(settings.DATA_COLOR) == (1, 1, 0.5)
             assert settings.DATA_ALPHA == 0.4
+            settings.FONT_SIZE = 8.0
+
+            dialog = PreferencesDialog(self.app)
+            dialog.show()
+            dialog.font_size = 16.0
+            dialog.accept()
+
+            assert rgb(settings.FOREGROUND_COLOR) == (0, 1, 1)
+            assert rgb(settings.BACKGROUND_COLOR) == (1, 0, 1)
+            assert rgb(settings.DATA_COLOR) == (1, 1, 0.5)
+            assert settings.DATA_ALPHA == 0.4
+            settings.FONT_SIZE = 16.0
 
     def test_custom_pane(self):
 
@@ -216,6 +239,7 @@ class TestPreferences():
             settings.BACKGROUND_COLOR = (0, 0.5, 1)
             settings.DATA_COLOR = (1, 0.5, 0.25)
             settings.DATA_ALPHA = 0.3
+            settings.FONT_SIZE = 8.0
 
             dialog = PreferencesDialog(self.app)
             dialog.show()
@@ -223,7 +247,7 @@ class TestPreferences():
             dialog.accept()
 
         assert len(listener.received) == 1
-        assert listener.received[0].settings == ('FOREGROUND_COLOR', 'BACKGROUND_COLOR')
+        assert listener.received[0].settings == ('FOREGROUND_COLOR', 'BACKGROUND_COLOR', 'FONT_SIZE')
 
     def test_save_to_disk(self, tmpdir):
 
@@ -234,6 +258,7 @@ class TestPreferences():
                 settings.BACKGROUND_COLOR = (0, 0.5, 1)
                 settings.DATA_COLOR = (1, 0.5, 0.25)
                 settings.DATA_ALPHA = 0.3
+                settings.FONT_SIZE = 8.0
 
                 dialog = PreferencesDialog(self.app)
                 dialog.show()
@@ -338,6 +363,7 @@ def test_foreground_background_settings():
         settings.BACKGROUND_COLOR = 'white'
         settings.DATA_COLOR = '0.5'
         settings.DATA_ALPHA = 0.5
+        settings.FONT_SIZE = 8.0
 
         dialog = PreferencesDialog(app)
         dialog.show()
diff --git a/glue/config.py b/glue/config.py
index 476c7707..1b7d9558 100644
--- a/glue/config.py
+++ b/glue/config.py
@@ -884,3 +884,4 @@ settings.add('FOREGROUND_COLOR', '#000000')
 settings.add('SHOW_LARGE_DATA_WARNING', True, validator=bool)
 settings.add('SHOW_INFO_PROFILE_OPEN', True, validator=bool)
 settings.add('SHOW_WARN_PROFILE_DUPLICATE', True, validator=bool)
+settings.add('FONT_SIZE', -1.0, validator=float)
diff --git a/glue/utils/qt/app.py b/glue/utils/qt/app.py
index 1f04d75f..855a827c 100644
--- a/glue/utils/qt/app.py
+++ b/glue/utils/qt/app.py
@@ -3,11 +3,27 @@ from __future__ import absolute_import, division, print_function
 import platform
 from qtpy import QtCore, QtGui, QtWidgets
 
-__all__ = ['get_qapp', 'fix_tab_widget_fontsize']
+from glue.config import settings
+from glue._settings_helpers import save_settings
+
+__all__ = ['get_qapp', 'fix_tab_widget_fontsize', 'update_global_font_size']
 
 qapp = None
 
 
+def __get_font_size_offset():
+    if platform.system() == 'Darwin':
+        # On Mac, the fonts are generally too large compared to other
+        # applications, so we reduce the default here. In future we should
+        # make this a setting in the system preferences.
+        size_offset = 2
+    else:
+        # On other platforms, we reduce the font size by 1 point to save
+        # space too. Again, this should probably be a global setting.
+        size_offset = 1
+    return size_offset
+
+
 def get_qapp(icon_path=None):
 
     global qapp
@@ -28,18 +44,16 @@ def get_qapp(icon_path=None):
         if icon_path is not None:
             qapp.setWindowIcon(QtGui.QIcon(icon_path))
 
-        if platform.system() == 'Darwin':
-            # On Mac, the fonts are generally too large compared to other
-            # applications, so we reduce the default here. In future we should
-            # make this a setting in the system preferences.
-            size_offset = 2
-        else:
-            # On other platforms, we reduce the font size by 1 point to save
-            # space too. Again, this should probably be a global setting.
-            size_offset = 1
-
+        size_offset = __get_font_size_offset()
         font = qapp.font()
-        font.setPointSize(font.pointSize() - size_offset)
+
+        if settings.FONT_SIZE is None or settings.FONT_SIZE == -1:
+            default_font = QtGui.QFont()
+            settings.FONT_SIZE = default_font.pointSize()
+            save_settings()
+
+        point_size = settings.FONT_SIZE
+        font.setPointSize(point_size - size_offset)
         qapp.setFont(font)
 
     # Make sure we use high resolution icons for HDPI displays.
@@ -56,3 +70,16 @@ def fix_tab_widget_fontsize(tab_widget):
         app = get_qapp()
         app_font = app.font()
         tab_widget.setStyleSheet('font-size: {0}px'.format(app_font.pointSize()))
+
+
+def update_global_font_size():
+    """Updates the global font size through the current UI backend
+    """
+    if qapp is None:
+        get_qapp()
+
+    font = qapp.font()
+    point_size = settings.FONT_SIZE
+    size_offset = __get_font_size_offset()
+    font.setPointSize(point_size - size_offset)
+    qapp.setFont(font)
['glue/config.py', 'glue/app/qt/preferences.ui', 'glue/app/qt/application.py', 'glue/utils/qt/app.py', 'glue/app/qt/preferences.py', 'glue/app/qt/tests/test_preferences.py']