Source code for pydm.utilities.iconfont

"""
iconfont provides a barebones system to get QIcons from icon fonts (like Font Awesome).
The inspiration and methodology come from the 'QtAwesome' module, which does exactly this, but
is a little too big, complicated, and flexible for PyDM's needs.
"""
import json
import os
import sys
from typing import Optional

from qtpy import QtGui, QtWidgets
from qtpy.QtCore import QPoint, QRect, Qt, qRound
from qtpy.QtGui import QColor, QFont, QFontDatabase, QIcon, QIconEngine, QPainter, QPixmap

if sys.version_info[0] == 3:
    unichr = chr


[docs]class IconFont(object): """IconFont represents an icon font. Users will generally want to use IconFont.icon() to get a QIcon object for the character they want.""" __instance = None def __init__(self): if self.__initialized: return self.font_file = "fontawesome.otf" # specify these relative to this file. self.charmap_file = "fontawesome-charmap.json" self.font_name = None self.char_map = {} self.loaded_fonts = {} self.__initialized = True def __new__(cls, *args, **kwargs): if cls.__instance is None: cls.__instance = object.__new__(IconFont) cls.__instance.__initialized = False return cls.__instance
[docs] def load_font(self, ttf_filename, charmap_filename): """ Load font from ``ttf_filename`` with a mapping defined in ``charmap_filename``. """ def hook(obj): result = {} for key in obj: result[key] = unichr(int(obj[key], 16)) return result if self.char_map: return cache_key = ttf_filename + "|" + charmap_filename ttf_fname = os.path.join(os.path.dirname(os.path.realpath(__file__)), ttf_filename) font_id = QFontDatabase.addApplicationFont(ttf_fname) if font_id >= 0: font_families = QFontDatabase.applicationFontFamilies(font_id) else: cache = self.loaded_fonts.get(cache_key, None) if cache is None: raise OSError("Could not load ttf file for icon font.") self.char_map = cache["char_map"] self.font_name = cache["font_name"] return self.font_name = font_families[0] filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), charmap_filename) with open(filename, "r") as codes: self.char_map = json.load(codes, object_hook=hook) self.loaded_fonts[cache_key] = { "char_map": self.char_map, "font_name": self.font_name, }
[docs] def get_char_for_name(self, name: str) -> str: """ Get a character icon for the given name from the character map. Parameters ---------- name : str The user-friendly icon name. Returns ------- str The Qt-facing icon text to use with the font. """ if name in self.char_map: return self.char_map[name] raise ValueError("Invalid icon name for font.")
def _load_font_if_needed(self) -> bool: """ Load the configured font if a QApplication is available and the font was not already loaded. Returns ------- bool Readiness indicator - return True if the font has been loaded. """ if QtWidgets.QApplication.instance() is None: return False if not self.char_map: self.load_font(self.font_file, self.charmap_file) # If it was loaded correctly, the char map will be populated: return bool(self.char_map)
[docs] def font(self, size: int) -> Optional[QtGui.QFont]: """ Load the font at a given pixel size. Returns ------- QtGui.QFont or None The font, if available. If a QApplication is not yet created, None will be returned. """ if not self._load_font_if_needed(): return None font = QFont(self.font_name) font.setPixelSize(int(size)) return font
[docs] def icon(self, name, color=None) -> Optional[QtGui.QIcon]: """ Retrieve the icon given a name and color. Parameters ---------- name : str The Icon string identifier. Icon strings can be found at: https://fontawesome.com/icons?d=gallery color : QColor, Optional The base color to use when constructing the Icon. Default is QColor(90, 90, 90). Returns ------- QIcon The desired Icon. ``None`` if a QApplication is not yet available. """ if not self._load_font_if_needed(): return None char = self.get_char_for_name(name) engine = CharIconEngine(self, char, color) return QIcon(engine)
class CharIconEngine(QIconEngine): """Subclass of QIconEngine that is designed to draw characters from icon fonts.""" def __init__(self, icon_font, char, color=None): super(CharIconEngine, self).__init__() self.icon_font = icon_font self.char = char if color is None: self._base_color = QColor(90, 90, 90) else: self._base_color = color self._disabled_color = QColor.fromHslF( self._base_color.hueF(), self._base_color.saturationF(), max(min(self._base_color.lightnessF() + 0.25, 1.0), 0.0), ) def paint(self, painter, rect, mode, state): painter.save() if mode == QIcon.Disabled: color = self._disabled_color else: color = self._base_color painter.setPen(color) scale_factor = 1.0 draw_size = 0.875 * qRound(rect.height() * scale_factor) painter.setFont(self.icon_font.font(draw_size)) painter.setOpacity(1.0) painter.drawText(rect, int(Qt.AlignCenter | Qt.AlignVCenter), self.char) painter.restore() def pixmap(self, size, mode, state): pm = QPixmap(size) pm.fill(Qt.transparent) self.paint(QPainter(pm), QRect(QPoint(0, 0), size), mode, state) return pm