Source code for spinetoolbox.spine_db_icon_manager

######################################################################################################################
# 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 SpineDBIconManager."""
from PySide6.QtCore import Qt, QPointF, QRectF, QBuffer
from PySide6.QtWidgets import QGraphicsScene
from PySide6.QtGui import QIcon, QFont, QTextOption, QPainter
from PySide6.QtSvg import QSvgGenerator, QSvgRenderer
from .helpers import TransparentIconEngine, interpret_icon_id


[docs]def _align_text_in_item(item): document = item.document() document.setDocumentMargin(0) option = QTextOption(Qt.AlignCenter) document.setDefaultTextOption(option) item.adjustSize() rect = item.boundingRect() size = 0.875 * round(min(rect.width(), rect.height())) font = item.font() font.setPixelSize(max(1, size)) item.setFont(font)
[docs]def _center_scene(scene): rect = scene.itemsBoundingRect() center = rect.center() extent = 0.5 * max(rect.width(), rect.height()) top_left = center - QPointF(extent, extent) bottom_right = center + QPointF(extent, extent) square = QRectF(top_left, bottom_right) scene.setSceneRect(square) rect_item = scene.addRect(square) rect_item.setPen(Qt.NoPen)
[docs]class _SceneSvgRenderer(QSvgRenderer): def __init__(self, scene): buffer = QBuffer() generator = QSvgGenerator() generator.setOutputDevice(buffer) scene_rect = scene.sceneRect() generator.setViewBox(scene_rect) painter = QPainter(generator) scene.render(painter, scene_rect, scene_rect) painter.end() buffer.open(QBuffer.ReadOnly) super().__init__(buffer.readAll()) buffer.close() self.scene = scene
[docs]class SpineDBIconManager: """A class to manage object_class icons for spine db editors.""" def __init__(self): self.display_icons = {} # A mapping from object_class name to display icon code self._class_renderers = {} # A mapping from class name to associated renderer self._multi_class_renderers = {} # A mapping from dimension name list to associated renderer self._group_renderers = {} # A mapping from class name to associated group renderer self.icon_renderers = {}
[docs] def update_icon_caches(self, classes): """Called after adding or updating entity classes. Stores display_icons and clears obsolete entries from the relationship class and entity group renderer caches.""" for class_ in classes: self.display_icons[class_["name"]] = class_["display_icon"] class_names = [x["name"] for x in classes] dirty_keys = [k for k in self._multi_class_renderers if any(x in class_names for x in k)] for k in dirty_keys: del self._multi_class_renderers[k] for name in class_names: self._group_renderers.pop(name, None) self._class_renderers.pop(name, None)
[docs] def _create_icon_renderer(self, icon_code, color_code): scene = QGraphicsScene() font = QFont("Font Awesome 5 Free Solid") text_item = scene.addText(icon_code, font) text_item.setDefaultTextColor(color_code) _align_text_in_item(text_item) self.icon_renderers[icon_code, color_code] = _SceneSvgRenderer(scene)
[docs] def icon_renderer(self, icon_code, color_code): if (icon_code, color_code) not in self.icon_renderers: self._create_icon_renderer(icon_code, color_code) return self.icon_renderers[icon_code, color_code]
[docs] def color_class_renderer(self, entity_class, color_code): class_name = entity_class["name"] display_icon = self.display_icons.get(class_name, -1) icon_code, _ = interpret_icon_id(display_icon) return self.icon_renderer(chr(icon_code), color_code)
[docs] def _create_class_renderer(self, class_name): display_icon = self.display_icons.get(class_name, -1) icon_code, color_code = interpret_icon_id(display_icon) self._class_renderers[class_name] = self.icon_renderer(chr(icon_code), color_code)
[docs] def _create_multi_class_renderer(self, dimension_name_list): if not any(dimension_name_list): self._multi_class_renderers[dimension_name_list] = self.icon_renderer("\uf1b3", 0) return font = QFont("Font Awesome 5 Free Solid") scene = QGraphicsScene() x = 0 for j, dimension_name in enumerate(dimension_name_list): display_icon = self.display_icons.get(dimension_name, -1) icon_code, color_code = interpret_icon_id(display_icon) text_item = scene.addText(chr(icon_code), font) text_item.setDefaultTextColor(color_code) _align_text_in_item(text_item) if j % 2 == 0: y = 0 else: y = -0.875 * 0.75 * text_item.boundingRect().height() text_item.setZValue(-1) text_item.setPos(x, y) x += 0.875 * 0.5 * text_item.boundingRect().width() _center_scene(scene) self._multi_class_renderers[dimension_name_list] = _SceneSvgRenderer(scene)
[docs] def class_renderer(self, entity_class): name, dimension_name_list = entity_class["name"], entity_class["dimension_name_list"] if not dimension_name_list: if name not in self._class_renderers: self._create_class_renderer(name) return self._class_renderers[name] return self.multi_class_renderer(dimension_name_list)
[docs] def multi_class_renderer(self, dimension_name_list): if dimension_name_list not in self._multi_class_renderers: self._create_multi_class_renderer(dimension_name_list) return self._multi_class_renderers[dimension_name_list]
[docs] 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") scene = QGraphicsScene() x = 0 for _ in range(2): y = 0 for _ in range(2): text_item = scene.addText(chr(icon_code), font) text_item.setDefaultTextColor(color_code) text_item.setPos(x, y) y += 0.875 * text_item.boundingRect().height() x += 0.875 * text_item.boundingRect().width() scene.addRect(scene.itemsBoundingRect()) self._group_renderers[class_name] = _SceneSvgRenderer(scene)
[docs] def group_renderer(self, entity_class): class_name = entity_class["name"] if class_name not in self._group_renderers: self._create_group_renderer(class_name) return self._group_renderers[class_name]
@staticmethod
[docs] def icon_from_renderer(renderer): return QIcon(SceneIconEngine(renderer.scene))
[docs]class SceneIconEngine(TransparentIconEngine): """Specialization of QIconEngine used to draw scene-based icons.""" def __init__(self, scene): super().__init__() self.scene = scene
[docs] def paint(self, painter, rect, mode=None, state=None): painter.save() self.scene.render(painter, rect, self.scene.sceneRect()) painter.restore()