Skip to content

Commit

Permalink
Get Font Awesome font family name dynamically upon startup
Browse files Browse the repository at this point in the history
Turns out that Font Awesome Free font family has different names
depending on operating system. This removes hard-coded font
family names and replaces them with a dynamic system where
we check the correct name in main().

Re #1265
  • Loading branch information
soininen committed Feb 10, 2025
1 parent 312cb4b commit f780eca
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 32 deletions.
40 changes: 40 additions & 0 deletions spinetoolbox/font.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
######################################################################################################################
# Copyright (C) 2017-2022 Spine project consortium
# Copyright Spine Toolbox contributors
# This file is part of Spine Toolbox.
# Spine Toolbox is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program 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 Lesser General
# Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
######################################################################################################################

"""Provides Toolbox icon font."""
import logging
from typing import ClassVar, Optional
from PySide6.QtGui import QFontDatabase
from PySide6.QtWidgets import QApplication

# Importing resources_icons_rc initializes resources and Font Awesome gets added to the application
from . import resources_icons_rc # pylint: disable=unused-import # isort: skip


class Font:
family: ClassVar[Optional[str]] = None

@staticmethod
def get_family_from_font_database():
"""Sets the family attribute to Font Awesome family name from font database.
QApplication must be instantiated before calling this function.
"""
font_id = QFontDatabase.addApplicationFont(":/fonts/fontawesome5-solid-webfont.ttf")

Check warning on line 32 in spinetoolbox/font.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/font.py#L32

Added line #L32 was not covered by tests
if font_id < 0:
logging.warning("Could not load fonts from resources file. Some icons may not render properly.")
Font.family = QApplication.font().family()

Check warning on line 35 in spinetoolbox/font.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/font.py#L34-L35

Added lines #L34 - L35 were not covered by tests
else:
Font.family = QFontDatabase().applicationFontFamilies(font_id)[0]

Check warning on line 37 in spinetoolbox/font.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/font.py#L37

Added line #L37 was not covered by tests


TOOLBOX_FONT = Font()
5 changes: 3 additions & 2 deletions spinetoolbox/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from typing import Sequence # pylint: disable=unused-import
from xml.etree import ElementTree
import matplotlib
from PySide6.QtCore import QEvent, QFile, QIODevice, QObject, QPoint, QRect, QSize, Qt, QUrl, Slot, QSettings
from PySide6.QtCore import QEvent, QFile, QIODevice, QObject, QPoint, QRect, QSettings, QSize, Qt, QUrl, Slot
from PySide6.QtCore import __version__ as qt_version
from PySide6.QtCore import __version_info__ as qt_version_info
from PySide6.QtGui import (
Expand Down Expand Up @@ -76,6 +76,7 @@
PROJECT_LOCAL_DATA_FILENAME,
SPECIFICATION_LOCAL_DATA_FILENAME,
)
from .font import TOOLBOX_FONT

if sys.platform == "win32":
import ctypes
Expand Down Expand Up @@ -520,7 +521,7 @@ def __init__(self, char, color=None):
super().__init__()
self.char = char
self.color = QColor(color)
self.font = QFont("Font Awesome 5 Free Solid")
self.font = QFont(TOOLBOX_FONT.family)

def paint(self, painter, rect, mode=None, state=None):
painter.save()
Expand Down
13 changes: 7 additions & 6 deletions spinetoolbox/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
QToolTip,
)
from spinetoolbox.helpers import color_from_index
from .font import TOOLBOX_FONT
from .project_item_icon import ConnectorButton

LINK_COLOR = color_from_index(0, 2, base_hue=60)
Expand Down Expand Up @@ -275,7 +276,7 @@ def __init__(self, x, y, w, h, parent, tooltip=None, active=True):
if tooltip:
self.setToolTip(tooltip)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=False)
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, enabled=False)
self.setBrush(palette.window())

def hoverEnterEvent(self, event):
Expand All @@ -300,7 +301,7 @@ def __init__(self, parent, extent, path, tooltip=None, active=False):
scale = 0.8 * self.rect().width() / self._renderer.defaultSize().width()
self._svg_item.setScale(scale)
self._svg_item.setPos(self.sceneBoundingRect().center() - self._svg_item.sceneBoundingRect().center())
self.setPen(Qt.NoPen)
self.setPen(Qt.PenStyle.NoPen)

def wipe_out(self):
"""Cleans up icon's resources."""
Expand All @@ -315,12 +316,12 @@ class _TextIcon(_IconBase):
def __init__(self, parent, extent, char, tooltip=None, active=False):
super().__init__(0, 0, extent, extent, parent, tooltip=tooltip, active=active)
self._text_item = QGraphicsTextItem(self)
font = QFont("Font Awesome 5 Free Solid", weight=QFont.Bold)
font = QFont(TOOLBOX_FONT.family, weight=QFont.Weight.Bold)
self._text_item.setFont(font)
self._text_item.setDefaultTextColor(self._fg_color)
self._text_item.setPlainText(char)
self._text_item.setPos(self.sceneBoundingRect().center() - self._text_item.sceneBoundingRect().center())
self.setPen(Qt.NoPen)
self.setPen(Qt.PenStyle.NoPen)

def wipe_out(self):
"""Cleans up icon's resources."""
Expand All @@ -342,8 +343,8 @@ class JumpOrLink(LinkBase):

def __init__(self, toolbox, src_connector, dst_connector):
super().__init__(toolbox, src_connector, dst_connector)
self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True)
self.setFlag(QGraphicsItem.ItemIsFocusable, enabled=True)
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, enabled=True)
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsFocusable, enabled=True)
self._icon_extent = 3 * self.magic_number
self._icons = []
self._anim = self._make_execution_animation()
Expand Down
11 changes: 4 additions & 7 deletions spinetoolbox/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
######################################################################################################################

"""Provides the main() function."""
import asyncio

Check warning on line 14 in spinetoolbox/main.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/main.py#L14

Added line #L14 was not covered by tests
import multiprocessing
import os
import asyncio
import PySide6

dirname = os.path.dirname(PySide6.__file__)
Expand All @@ -25,15 +25,14 @@
import logging
import sys
from PySide6.QtCore import QTimer
from PySide6.QtGui import QFontDatabase
from PySide6.QtWidgets import QApplication
from .font import TOOLBOX_FONT

Check warning on line 29 in spinetoolbox/main.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/main.py#L29

Added line #L29 was not covered by tests
from .headless import Status, headless_main
from .helpers import pyside6_version_check
from .ui_main import ToolboxUI
from .version import __version__

# Importing resources_icons_rc initializes resources and Font Awesome gets added to the application
from . import resources_icons_rc # pylint: disable=unused-import # isort: skip
# MacOS complains about missing item icons without the following line.
from spine_items import resources_icons_rc # pylint: disable=unused-import # isort: skip


Expand All @@ -59,9 +58,7 @@ def main():
return return_code
app = QApplication(sys.argv)
app.setApplicationName("Spine Toolbox")
status = QFontDatabase.addApplicationFont(":/fonts/fontawesome5-solid-webfont.ttf")
if status < 0:
logging.warning("Could not load fonts from resources file. Some icons may not render properly.")
TOOLBOX_FONT.get_family_from_font_database()

Check warning on line 61 in spinetoolbox/main.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/main.py#L61

Added line #L61 was not covered by tests
window = ToolboxUI()
window.show()
QTimer.singleShot(0, lambda: window.init_tasks(args.project))
Expand Down
7 changes: 4 additions & 3 deletions spinetoolbox/project_item_icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from PySide6.QtSvg import QSvgRenderer
from PySide6.QtSvgWidgets import QGraphicsSvgItem
from PySide6.QtWidgets import (
QApplication,
QGraphicsColorizeEffect,
QGraphicsDropShadowEffect,
QGraphicsEllipseItem,
Expand All @@ -25,9 +26,9 @@
QGraphicsTextItem,
QStyle,
QToolTip,
QApplication,
)
from spine_engine.spine_engine import ItemExecutionFinishState
from .font import TOOLBOX_FONT
from .helpers import LinkType, fix_lightness_color
from .project_commands import MoveIconCommand

Expand Down Expand Up @@ -585,7 +586,7 @@ def __init__(self, parent):
self._parent = parent
self._execution_state = "not started"
self._text_item = QGraphicsTextItem(self)
font = QFont("Font Awesome 5 Free Solid")
font = QFont(TOOLBOX_FONT.family)
self._text_item.setFont(font)
parent_rect = parent.rect()
self.setRect(0, 0, 0.5 * parent_rect.width(), 0.5 * parent_rect.height())
Expand Down Expand Up @@ -660,7 +661,7 @@ def __init__(self, parent):
super().__init__(parent)
self._parent = parent
self._notifications = []
font = QFont("Font Awesome 5 Free Solid")
font = QFont(TOOLBOX_FONT.family)
font.setPixelSize(self.FONT_SIZE_PIXELS)
self.setFont(font)
self.setDefaultTextColor(QColor("red"))
Expand Down
19 changes: 13 additions & 6 deletions spinetoolbox/spine_db_editor/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
######################################################################################################################
# Copyright (C) 2017-2022 Spine project consortium
# Copyright Spine Toolbox contributors
# This file is part of Spine Toolbox.
# Spine Toolbox is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program 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 Lesser General
# Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
######################################################################################################################
#!/usr/bin/env python

from argparse import ArgumentParser
import locale
import logging
import sys
from PySide6.QtCore import QSettings
from PySide6.QtGui import QFontDatabase
from PySide6.QtWidgets import QApplication
from spinetoolbox import resources_icons_rc # pylint: disable=unused-import
from spinetoolbox.font import TOOLBOX_FONT

Check warning on line 19 in spinetoolbox/spine_db_editor/main.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/spine_db_editor/main.py#L19

Added line #L19 was not covered by tests
from spinetoolbox.helpers import pyside6_version_check
from spinetoolbox.spine_db_editor.widgets.multi_spine_db_editor import MultiSpineDBEditor
from spinetoolbox.spine_db_manager import SpineDBManager
Expand All @@ -20,9 +29,7 @@ def main():
parser = _make_argument_parser()
args = parser.parse_args()
app = QApplication(sys.argv)
status = QFontDatabase.addApplicationFont(":/fonts/fontawesome5-solid-webfont.ttf")
if status < 0:
logging.warning("Could not load fonts from resources file. Some icons may not render properly.")
TOOLBOX_FONT.get_family_from_font_database()

Check warning on line 32 in spinetoolbox/spine_db_editor/main.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/spine_db_editor/main.py#L32

Added line #L32 was not covered by tests
locale.setlocale(locale.LC_NUMERIC, "C")
settings = QSettings("SpineProject", "Spine Toolbox")
db_mngr = SpineDBManager(settings, None)
Expand Down
7 changes: 4 additions & 3 deletions spinetoolbox/spine_db_editor/widgets/custom_delegates.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
SearchBarEditor,
SearchBarEditorWithCreation,
)
from ...font import TOOLBOX_FONT
from ...helpers import object_icon
from ...mvcmodels.shared import DB_MAP_ROLE, INVALID_TYPE, PARAMETER_TYPE_VALIDATION_ROLE, PARSED_ROLE
from ...widgets.custom_delegates import CheckBoxDelegate, RankDelegate
Expand Down Expand Up @@ -289,7 +290,7 @@ def _make_exclamation_font():
Returns:
QFont: font
"""
font = QFont("Font Awesome 5 Free Solid")
font = QFont(TOOLBOX_FONT.family)
font.setPixelSize(12)
return font

Expand All @@ -298,7 +299,6 @@ class ParameterValueOrDefaultValueDelegate(TableDelegate):
"""A delegate for either the value or the default value."""

parameter_value_editor_requested = Signal(QModelIndex)
EXCLAMATION_FONT = _make_exclamation_font()
EXCLAMATION_COLOR = QColor("red")
INDICATOR_WIDTH = 18

Expand All @@ -309,6 +309,7 @@ def __init__(self, parent, db_mngr):
db_mngr (SpineDBManager): database manager
"""
super().__init__(parent, db_mngr)
self._exclamation_font = _make_exclamation_font()
self._db_value_list_lookup = {}

def paint(self, painter, option, index):
Expand All @@ -323,7 +324,7 @@ def paint(self, painter, option, index):
text_position = indicator_rect.center()
text_position.setY(text_position.y() + 5)
text_position.setX(text_position.x() - 5)
painter.setFont(self.EXCLAMATION_FONT)
painter.setFont(self._exclamation_font)

Check warning on line 327 in spinetoolbox/spine_db_editor/widgets/custom_delegates.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/spine_db_editor/widgets/custom_delegates.py#L327

Added line #L327 was not covered by tests
painter.setPen(self.EXCLAMATION_COLOR)
painter.drawText(text_position, "\uf06a")
super().paint(painter, option, index)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from PySide6.QtGui import QFont, QIcon
from PySide6.QtWidgets import QMenu, QStatusBar, QToolButton
from ...config import MAINWINDOW_SS, ONLINE_DOCUMENTATION_URL
from ...font import TOOLBOX_FONT
from ...helpers import CharIconEngine, open_url
from ...widgets.multi_tab_window import MultiTabWindow
from ...widgets.settings_widget import SpineDBEditorSettingsWidget
Expand Down Expand Up @@ -194,7 +195,7 @@ def __init__(self, parent=None):
"""
)
self._hide_button.setText("\uf00d")
self._hide_button.setFont(QFont("Font Awesome 5 Free Solid"))
self._hide_button.setFont(QFont(TOOLBOX_FONT.family))
self._hide_button.setFixedSize(24, 24)
self.insertPermanentWidget(0, self._hide_button)
self.setSizeGripEnabled(False)
Expand Down
7 changes: 4 additions & 3 deletions spinetoolbox/spine_db_icon_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from PySide6.QtGui import QFont, QIcon, QPainter, QTextOption
from PySide6.QtSvg import QSvgGenerator, QSvgRenderer
from PySide6.QtWidgets import QGraphicsScene
from .font import TOOLBOX_FONT
from .helpers import TransparentIconEngine, default_icon_id, interpret_icon_id


Expand Down Expand Up @@ -96,7 +97,7 @@ def update_icon_caches(self, classes):

def _create_icon_renderer(self, icon_code, color_code):
scene = QGraphicsScene()
font = QFont("Font Awesome 5 Free Solid")
font = QFont(TOOLBOX_FONT.family)
text_item = scene.addText(icon_code, font)
text_item.setDefaultTextColor(color_code)
_align_text_in_item(text_item)
Expand Down Expand Up @@ -128,7 +129,7 @@ def _create_multi_class_renderer(self, name, dimension_name_list, id_):
)
] = self.icon_renderer("\uf1b3", 0)
return
font = QFont("Font Awesome 5 Free Solid")
font = QFont(TOOLBOX_FONT.family)
scene = QGraphicsScene()
display_icon = self.display_icons.get(name, None)
if display_icon and display_icon != default_icon_id(): # If the entity class has an icon set, use that one.
Expand Down Expand Up @@ -228,7 +229,7 @@ def multi_class_renderer(self, name, dimension_name_list, id_):
def _create_group_renderer(self, class_name):
display_icon = self.display_icons.get(class_name, -1)
icon_code, color_code = interpret_icon_id(display_icon)
font = QFont("Font Awesome 5 Free Solid")
font = QFont(TOOLBOX_FONT.family)

Check warning on line 232 in spinetoolbox/spine_db_icon_manager.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/spine_db_icon_manager.py#L232

Added line #L232 was not covered by tests
scene = QGraphicsScene()
x = 0
for _ in range(2):
Expand Down
3 changes: 2 additions & 1 deletion spinetoolbox/widgets/custom_qwidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
QWidgetAction,
QWizardPage,
)
from ..font import TOOLBOX_FONT
from ..helpers import format_log_message
from .custom_qtextbrowser import MonoSpaceFontTextBrowser
from .select_database_items import SelectDatabaseItems
Expand Down Expand Up @@ -631,7 +632,7 @@ def __init__(self, text="", parent=None):
font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
line_edit.setFont(font)
button = QToolButton()
font = QFont("Font Awesome 5 Free Solid")
font = QFont(TOOLBOX_FONT.family)

Check warning on line 635 in spinetoolbox/widgets/custom_qwidgets.py

View check run for this annotation

Codecov / codecov/patch

spinetoolbox/widgets/custom_qwidgets.py#L635

Added line #L635 was not covered by tests
button.setFont(font)
button.setText("\uf0c5")
button.setToolTip("Copy text")
Expand Down

0 comments on commit f780eca

Please sign in to comment.