Skip to main content
`Ctrl`+`K`

# 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"""

    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

    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"""

    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."""

        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()

        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()

        def hasRegistredObjects(self):
            """Returns true if any object is registred.

            :rtype: bool
            """
            return len(self.__targets)

        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

        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)

    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())

    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()

    def getWaitIcon():
        """Returns a cached version of the waiting AbstractAnimatedIcon.

        :rtype: AbstractAnimatedIcon
        """
        return getAnimatedIcon("process-working")

    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

    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

    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())

    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)