Skip to content

Instantly share code, notes, and snippets.

@JustinPedersen
Last active March 8, 2022 09:54
Show Gist options
  • Save JustinPedersen/23aaf2d3b245444059ef4e382df69475 to your computer and use it in GitHub Desktop.
Save JustinPedersen/23aaf2d3b245444059ef4e382df69475 to your computer and use it in GitHub Desktop.
working implementation of gifs as tool tips for Maya
"""
Implementation of a gif as tool tip for Autodesk Maya.
Modified from: https://stackoverflow.com/questions/60521848/how-to-show-tooltip-image-when-hover-on-button-pyqt5
"""
import sys
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtWidgets
from shiboken2 import wrapInstance
import maya.OpenMayaUI as omui
def maya_main_window():
"""
Return the Maya main window widget as a Python object
"""
main_window_ptr = omui.MQtUtil.mainWindow()
if sys.version_info.major >= 3:
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
else:
return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)
class ToolTipAnimation(QtWidgets.QLabel):
def __init__(self, file, width=None, height=None):
super(ToolTipAnimation, self).__init__(parent=maya_main_window())
self.setMouseTracking(True)
# image loading doesn't happen immediately, as it could require some time;
# we store the information for later use
self._file = file
self._width = width
self._height = height
self._shown = False
# a timer that prevents the enterEvent to hide the tip immediately
self.showTimer = QtCore.QTimer(interval=100, singleShot=True)
# install an event filter for the application, so that we can be notified
# whenever the user performs any action
QtWidgets.QApplication.instance().installEventFilter(self)
def load(self):
movie = QtGui.QMovie(self._file)
if self._width and not self._height:
self._height = self._width
if self._width and self._height:
size = QtCore.QSize(self._width, self._height)
movie.setScaledSize(size)
else:
size = QtCore.QSize()
for f in range(movie.frameCount()):
movie.jumpToFrame(f)
size = size.expandedTo(movie.currentImage().size())
self.setFixedSize(size)
self.setMovie(movie)
self._shown = True
def show(self, pos=None):
if not self._shown:
self.load()
if pos is None:
pos = QtGui.QCursor.pos()
# ensure that the tooltip is always shown within the screen geometry
for screen in QtWidgets.QApplication.screens():
if screen.availableGeometry().contains(pos):
screen = screen.availableGeometry()
# add an offset so that the mouse cursor doesn't hide the tip
pos += QtCore.QPoint(2, 16)
if pos.x() < screen.x():
pos.setX(screen.x())
elif pos.x() + self.width() > screen.right():
pos.setX(screen.right() - self.width())
if pos.y() < screen.y():
pos.setY(screen.y())
elif pos.y() + self.height() > screen.bottom():
pos.setY(screen.bottom() - self.height())
break
self.move(pos)
super(ToolTipAnimation, self).show()
self.movie().start()
def maybeHide(self):
# if for some reason the tooltip is shown where the mouse is, we should
# not hide it if it's still within the parent's rectangle
if self.parent() is not None:
parent_pos = self.parent().mapToGlobal(QtCore.QPoint())
rect = QtCore.QRect(parent_pos, self.parent().size())
if rect.contains(QtGui.QCursor.pos()):
return
self.hide()
def eventFilter(self, source, event):
# hide the tip for any user interaction
if event.type() in (QtCore.QEvent.KeyPress, QtCore.QEvent.KeyRelease,
QtCore.QEvent.WindowActivate, QtCore.QEvent.WindowDeactivate,
QtCore.QEvent.FocusIn, QtCore.QEvent.FocusOut,
QtCore.QEvent.Leave, QtCore.QEvent.Close,
QtCore.QEvent.MouseButtonPress, QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick, QtCore.QEvent.Wheel):
self.hide()
return False
def mouseMoveEvent(self, event):
QtCore.QTimer.singleShot(100, self.hide)
def enterEvent(self, event):
# hide the tooltip when mouse enters, but not immediately, otherwise it
# will be shown right after from the parent widget
if not self.showTimer.isActive():
QtCore.QTimer.singleShot(100, self.hide)
def showEvent(self, event):
self.showTimer.start()
def hideEvent(self, event):
if self.movie():
self.movie().stop()
class ButtonIcon(QtWidgets.QPushButton):
toolTipAnimation = None
formats = tuple(str(fmt) for fmt in QtGui.QMovie.supportedFormats())
def setToolTipImage(self, image, width=None, height=None):
if not image or self.toolTipAnimation:
self.toolTipAnimation.hide()
self.toolTipAnimation.deleteLater()
self.toolTipAnimation = None
self.setToolTip('')
if not image:
return
if image.endswith(self.formats):
self.toolTipAnimation = ToolTipAnimation(image, width, height)
else:
if width and not height:
height = width
if width and height:
self.setToolTip(
'<img src="{}" width="{}" height="{}">'.format(
image, width, height))
else:
self.setToolTip('<img src="{}">'.format(image))
def event(self, event):
if (event.type() == QtCore.QEvent.ToolTip and self.toolTipAnimation and
not self.toolTipAnimation.isVisible()):
self.toolTipAnimation.show(event.globalPos())
return True
elif event.type() == QtCore.QEvent.Leave and self.toolTipAnimation:
self.toolTipAnimation.maybeHide()
return super(ButtonIcon, self).event(event)
class TestDialog(QtWidgets.QDialog):
def __init__(self, parent=maya_main_window()):
super(TestDialog, self).__init__(parent)
# ADD YOUR PATHS HERE
image_path = r'\clipart.png'
gif_path = r'\giphy.gif'
self.setWindowTitle("Test Dialog")
self.setMinimumWidth(200)
# Remove the ? from the dialog on Windows
self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
# Widgets
button_fixed = ButtonIcon('fixed image')
button_fixed.setToolTipImage(image_path)
button_animated = ButtonIcon('animated gif')
button_animated.setToolTipImage(gif_path, 200)
# Layouts
main_layout = QtWidgets.QVBoxLayout()
main_layout.addWidget(button_fixed)
main_layout.addWidget(button_animated)
self.setLayout(main_layout)
if __name__ == "__main__":
d = TestDialog()
d.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment