Source code for silx.gui.icons

# /*##########################################################################
#
# Copyright (c) 2016-2023 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""Set of icons for buttons.

Use :func:`getQIcon` to create Qt QIcon from the name identifying an icon.
"""

__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "07/01/2019"


import os
import logging
import weakref
from . import qt
import silx.resources
from silx.utils import weakref as silxweakref


_logger = logging.getLogger(__name__)
"""Module logger"""


_cached_icons = None
"""Cache loaded icons in a weak structure"""


[docs] def getIconCache(): """Get access to all cached icons :rtype: dict """ global _cached_icons if _cached_icons is None: _cached_icons = weakref.WeakValueDictionary() # Clean up the cache before leaving the application # See https://github.com/silx-kit/silx/issues/1771 qt.QApplication.instance().aboutToQuit.connect(cleanIconCache) return _cached_icons
[docs] def cleanIconCache(): """Clean up the icon cache""" _logger.debug("Clean up icon cache") _cached_icons.clear()
_supported_formats = None """Order of file format extension to check"""
[docs] class AbstractAnimatedIcon(qt.QObject): """Store an animated icon. It provides an event containing the new icon everytime it is updated.""" def __init__(self, parent=None): """Constructor :param qt.QObject parent: Parent of the QObject :raises: ValueError when name is not known """ qt.QObject.__init__(self, parent) self.__targets = silxweakref.WeakList() self.__currentIcon = None iconChanged = qt.Signal(qt.QIcon) """Signal sent with a QIcon everytime the animation changed."""
[docs] def register(self, obj): """Register an object to the AbstractAnimatedIcon. If no object are registred, the animation is paused. Object are stored in a weaked list. :param object obj: An object """ if obj not in self.__targets: self.__targets.append(obj) self._updateState()
[docs] def unregister(self, obj): """Remove the object from the registration. If no object are registred the animation is paused. :param object obj: A registered object """ if obj in self.__targets: self.__targets.remove(obj) self._updateState()
[docs] def hasRegistredObjects(self): """Returns true if any object is registred. :rtype: bool """ return len(self.__targets)
[docs] def isRegistered(self, obj): """Returns true if the object is registred in the AbstractAnimatedIcon. :param object obj: An object :rtype: bool """ return obj in self.__targets
[docs] def currentIcon(self): """Returns the icon of the current frame. :rtype: qt.QIcon """ return self.__currentIcon
def _updateState(self): """Update the object according to the connected objects.""" pass def _setCurrentIcon(self, icon): """Store the current icon and emit a `iconChanged` event. :param qt.QIcon icon: The current icon """ self.__currentIcon = icon self.iconChanged.emit(self.__currentIcon)
[docs] class MovieAnimatedIcon(AbstractAnimatedIcon): """Store a looping QMovie to provide icons for each frames. Provides an event with the new icon everytime the movie frame is updated.""" def __init__(self, filename, parent=None): """Constructor :param str filename: An icon name to an animated format :param qt.QObject parent: Parent of the QObject :raises: ValueError when name is not known """ AbstractAnimatedIcon.__init__(self, parent) qfile = getQFile(filename) self.__movie = qt.QMovie(qfile.fileName(), qt.QByteArray(), parent) self.__movie.setCacheMode(qt.QMovie.CacheAll) self.__movie.frameChanged.connect(self.__frameChanged) self.__cacheIcons = {} self.__movie.jumpToFrame(0) self.__updateIconAtFrame(0) def __frameChanged(self, frameId): """Callback everytime the QMovie frame change :param int frameId: Current frame id """ self.__updateIconAtFrame(frameId) def __updateIconAtFrame(self, frameId): """ Update the current stored QIcon :param int frameId: Current frame id """ if frameId in self.__cacheIcons: icon = self.__cacheIcons[frameId] else: icon = qt.QIcon(self.__movie.currentPixmap()) self.__cacheIcons[frameId] = icon self._setCurrentIcon(icon) def _updateState(self): """Update the movie play according to internal stat of the MovieAnimatedIcon.""" self.__movie.setPaused(not self.hasRegistredObjects())
[docs] class MultiImageAnimatedIcon(AbstractAnimatedIcon): """Store a looping QMovie to provide icons for each frames. Provides an event with the new icon everytime the movie frame is updated.""" def __init__(self, filename, parent=None): """Constructor :param str filename: An icon name to an animated format :param qt.QObject parent: Parent of the QObject :raises: ValueError when name is not known """ AbstractAnimatedIcon.__init__(self, parent) self.__frames = [] for i in range(100): try: frame_filename = os.sep.join((filename, ("%02d" % i))) frame_file = getQFile(frame_filename) except ValueError: break try: icon = qt.QIcon(frame_file.fileName()) except ValueError: break self.__frames.append(icon) if len(self.__frames) == 0: raise ValueError("Animated icon '%s' do not exists" % filename) self.__frameId = -1 self.__timer = qt.QTimer(self) self.__timer.timeout.connect(self.__increaseFrame) self.__updateIconAtFrame(0) def __increaseFrame(self): """Callback called every timer timeout to change the current frame of the animation """ frameId = (self.__frameId + 1) % len(self.__frames) self.__updateIconAtFrame(frameId) def __updateIconAtFrame(self, frameId): """ Update the current stored QIcon :param int frameId: Current frame id """ self.__frameId = frameId icon = self.__frames[frameId] self._setCurrentIcon(icon) def _updateState(self): """Update the object to wake up or sleep it according to its use.""" if self.hasRegistredObjects(): if not self.__timer.isActive(): self.__timer.start(100) else: if self.__timer.isActive(): self.__timer.stop()
[docs] def getWaitIcon(): """Returns a cached version of the waiting AbstractAnimatedIcon. :rtype: AbstractAnimatedIcon """ return getAnimatedIcon("process-working")
[docs] def getAnimatedIcon(name): """Create an AbstractAnimatedIcon from a resource name. The resource name can be prefixed by the name of a resource directory. For example "silx:foo.png" identify the resource "foo.png" from the resource directory "silx". If no prefix are specified, the file with be returned from the silx resource directory with a specific path "gui/icons". See also :func:`silx.resources.register_resource_directory`. Try to load a mng or a gif file, then try to load a multi-image animated icon. In Qt5 mng or gif are not used, because the transparency is not very well managed. :param str name: Name of the icon, in one of the defined icons in this module. :return: Corresponding AbstractAnimatedIcon :raises: ValueError when name is not known """ key = name + "__anim" cached_icons = getIconCache() if key not in cached_icons: qtMajorVersion = int(qt.qVersion().split(".")[0]) icon = None # ignore mng and gif in Qt5 if qtMajorVersion != 5: try: icon = MovieAnimatedIcon(name) except ValueError: icon = None if icon is None: try: icon = MultiImageAnimatedIcon(name) except ValueError: icon = None if icon is None: raise ValueError("Not an animated icon name: %s", name) cached_icons[key] = icon else: icon = cached_icons[key] return icon
[docs] def getQIcon(name): """Create a QIcon from its name. The resource name can be prefixed by the name of a resource directory. For example "silx:foo.png" identify the resource "foo.png" from the resource directory "silx". If no prefix are specified, the file with be returned from the silx resource directory with a specific path "gui/icons". See also :func:`silx.resources.register_resource_directory`. :param str name: Name of the icon, in one of the defined icons in this module. :return: Corresponding QIcon :raises: ValueError when name is not known """ cached_icons = getIconCache() if name not in cached_icons: qfile = getQFile(name) icon = qt.QIcon(qfile.fileName()) cached_icons[name] = icon else: icon = cached_icons[name] return icon
[docs] def getQPixmap(name): """Create a QPixmap from its name. The resource name can be prefixed by the name of a resource directory. For example "silx:foo.png" identify the resource "foo.png" from the resource directory "silx". If no prefix are specified, the file with be returned from the silx resource directory with a specific path "gui/icons". See also :func:`silx.resources.register_resource_directory`. :param str name: Name of the icon, in one of the defined icons in this module. :return: Corresponding QPixmap :raises: ValueError when name is not known """ qfile = getQFile(name) return qt.QPixmap(qfile.fileName())
[docs] def getQFile(name): """Create a QFile from an icon name. Filename is found according to supported Qt formats. The resource name can be prefixed by the name of a resource directory. For example "silx:foo.png" identify the resource "foo.png" from the resource directory "silx". If no prefix are specified, the file with be returned from the silx resource directory with a specific path "gui/icons". See also :func:`silx.resources.register_resource_directory`. :param str name: Name of the icon, in one of the defined icons in this module. :return: Corresponding QFile :rtype: qt.QFile :raises: ValueError when name is not known """ global _supported_formats if _supported_formats is None: _supported_formats = [] supported_formats = qt.supportedImageFormats() order = ["mng", "gif", "svg", "png", "jpg"] for format_ in order: if format_ in supported_formats: _supported_formats.append(format_) if len(_supported_formats) == 0: _logger.error("No format supported for icons") else: _logger.debug("Format %s supported", ", ".join(_supported_formats)) for format_ in _supported_formats: format_ = str(format_) filename = silx.resources._resource_filename( "%s.%s" % (name, format_), default_directory="gui/icons" ) qfile = qt.QFile(filename) if qfile.exists(): return qfile _logger.debug("File '%s' not found.", filename) raise ValueError("Not an icon name: %s" % name)