Source code for lib.ui_utils
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
""" Module ui_utils: This module contains useful classes for dealing with
the Mari UI."""
import logging
import os
import traceback
import mari
from PySide.QtCore import Qt
from PySide import QtGui
from abc import abstractmethod
from stkMariTools.lib.pyside_utils import get_mari_main_window
class _GCProtector(object):
"""
This class acts as a holder for a static class var in order
to prevent Qt _widgets from being garbage-collected.
"""
# declare static class variable to hold all widgets
widgets = []
[docs]class MariToolsMenuItem(object):
"""
This base class is used for adding a Mari Menu item.
"""
def __init__(
self,
actionIdentifier=None,
actionCommand=None,
actionPath=None,
actionIcon=None,
addBefore=''
):
"""
The constructor.
:param actionIdentifier: ``str`` determining the name of the
menu command in the UI.
:param actionCommand: ``str`` that is a function to be run
when the command is executed.
:param actionPath: ``str`` determining where the
command will show up in the UI
:param addBefore: ``str`` indicating which menu item this menu item
should appear before in the ordering of the menu.
:param actionIcon: ``str`` determining what icon the action
will have in the Mari menu.
.. tip::
This should be the name of any valid icon in the
``/Mari/Bundle/Media/Icons`` directory e.g. ('About')
:return:
"""
self.logger = logging.getLogger(__name__)
self.actionIdentifier = actionIdentifier
self.actionCommand = actionCommand
self.actionPath = actionPath
self.addBefore = addBefore
self.actionIcon = actionIcon
[docs] def registerMariToolsPlugin(self):
"""
This method acts as a identifier method to be run automatically in order
to register this plugin within Mari.
:return:
"""
# Create new instance of the class
self.logger.debug('Registering plugin {0}...'.format(self.__class__.__name__))
object.__new__(self.__class__)
[docs] def addMariToolsMenuItem(self):
"""
This method should be overriden in subclasses to add a Mari menu item.
:return:
"""
# Attempt to create the Mari action and add it to the menus
try:
action = mari.actions.create(self.actionIdentifier, self.actionCommand)
mari.menus.addAction(
action,
self.actionPath,
self.addBefore
)
except ValueError:
self.logger.error('### Could not add action:{0}!!!\n{1}'
.format(self.actionIdentifier, traceback.print_exc()))
return ValueError
# Attempt to set the icon for the menu item
icon = self.actionIcon
if icon:
# Get all valid icon names
icon_path = mari.resources.path(mari.resources.ICONS)
validIconNames = []
for root, dirs, filenames in os.walk(icon_path):
for filename in filenames:
if os.path.splitext(filename)[-1] == '.png':
validIconNames.append(os.path.splitext(filename)[0])
if icon in validIconNames:
icon_path = os.path.abspath(os.path.join(icon_path, icon+'.png'))
self.logger.debug('Setting icon for: {0} to {1}...'
.format(self.actionIdentifier, icon_path))
action.setIconPath(icon_path)
[docs]class MariWidget(QtGui.QMainWindow):
"""
This class instantiates a QWidget that can either be a standalone window or
a dockable palette in Mari.
"""
logger = logging.getLogger(__name__)
def __init__(
self,
parent=get_mari_main_window(),
title='',
widgetType='window',
*args,
**kwargs
):
"""
The constructor.
:param parent: ``QWidget`` to parent the created widget to. If ``None``
specified, will default to the Mari main window instance.
:param title: ``str`` determining the title that will appear in the window.
Must be a unique name.
:param widgetType: ``str`` determining the type of widget that will be created.
Acceptable values are:
- 'window' : Creates a standalone QWidget in a separate window.
- 'palette' : Creates widgets inside a dock-able Mari palette.
:param args:
:param kwargs:
:return: ``None``
"""
# Call base QWidget constructor
super( MariWidget, self ).__init__(parent=parent)
if title:
self.logger.info('Opening widget: {0}!'.format(title))
self.title = title
else:
# Use the class name as the default title of the widget
self.title = self.__class__.__name__
# Check if creating a new QWidget window
if widgetType == 'window':
# Get list of all top-level QWidgets and close this one if it's
# already running
current_widgets = []
[current_widgets.append(widget) for widget in QtGui.QApplication.topLevelWidgets()]
for widget in current_widgets:
if title == widget.objectName():
self.logger.debug('Existing instance of widget detected, '
'closing first...')
widget.close()
self.qt_window_type = Qt.Window
# ... Or Mari palette
elif widgetType == 'palette':
# Check if Mari palette has already been created and remove it
palette_exists = mari.palettes.find(title)
if palette_exists:
mari.palettes.remove(title)
self.palette_holder = mari.palettes.create(title, None)
self.qt_window_type = Qt.Tool
# Create a parent widget to hold the new widgets that will be setup to it.
self.widget_holder = QtGui.QWidget(parent=self)
self.setWindowFlags(self.qt_window_type)
# Show the UI
self.defineUi()
self.initializeUi(title, widgetType)
self.logger.debug('Widget {0} opened!'.format(self.windowTitle()))
@abstractmethod
[docs] def defineUi(self, *args, **kwargs):
"""
This abstract method should be overloaded to handle defining the actual
UI interface.
It is run before populateData() and makeConnections().
:return:
"""
pass
[docs] def initializeUi(self, title, widgetType, *args, **kwargs):
"""
This creates the UI and shows it.
:param title: ``str`` determining the title that will appear in the window.
Must be a unique name.
:param widgetType: ``str`` determining the type of widget that will be created.
Acceptable values are:
- 'window' : Creates a standalone QWidget in a separate window.
- 'palette' : Creates widgets inside a dock-able Mari palette.
:param args:
:param kwargs:
:return: ``None``
"""
# Set window data
self.setObjectName(title)
self.setWindowTitle(title)
self.widget_holder.setWindowTitle(title)
self.widget_holder.setObjectName(title)
try: self.populateData(*args, **kwargs)
except Exception:
self.logger.error('### Failed to populate data to widget:\n{widget}\n{0}'
.format(traceback.print_exc(),
widget=self))
raise RuntimeError
try: self.makeConnections(*args, **kwargs)
except Exception:
self.logger.error('### Failed to make signal/slot connections to '
'widget:\n{widget}\n{0}'
.format(traceback.print_exc(),
widget=self))
raise RuntimeError
# Add widgets to protection from garbage collection
_GCProtector.widgets.append(self.widget_holder)
if widgetType == 'window':
# Have the QMainWindow take ownership of the widget so that it can be
# deleted at the appropriate time.
self.setCentralWidget(self.widget_holder)
# self.widget_holder.show()
self.show()
elif widgetType == 'palette':
# Create the Mari Palette
self.palette_holder.setBodyWidget(self.widget_holder)
self.widget_holder.show()
self.palette_holder.showInFront()
else:
self.logger.error('### Invalid widget type was specified! '
'Cannot initialize the UI!!!\n{0}'
.format(traceback.print_exc()))
raise TypeError
@abstractmethod
[docs] def makeConnections(self, *args, **kwargs):
"""
This abstract method should be implemented to handle connecting
QSignals/QSlots.
:return: ``None``
"""
# Connect Mari main application closing with widget exit
mari.utils.connect(mari.app.exiting, self.cancelAction)
self.logger.debug('Successfully made widget connections!')
self.restoreUIStates(*args, **kwargs)
[docs] def restoreUIStates(self, *args, **kwargs):
"""
This method should be overloaded to handle restoring previous UI states
from QSettings. It is called after makeConnections().
:param args:
:param kwargs:
:return:
"""
pass
@abstractmethod
[docs] def populateData(self, *args, **kwargs):
"""
This abstract method should be implemented to populate any data needed
to the widget UI controls.
:return:
"""
pass
[docs] def cancelAction(self):
"""
This method closes the widget. Any UI widget which is intended to close
the UI should call this instance method.
:return:
"""
# self.widget_holder.close()
self.close()
[docs] def hidePalette(self):
"""
This method hides the palette. Any UI widget which is intended to hide the
palette should call this instance method.
:return:
"""
# Hide the palette and de-register it
self.palette_holder.hide()
self.palette_holder.remove(self.title)
self.close()
[docs] def closeEvent(self, event):
"""
This overrides the base event in ``QWidget``.
Removes the widget from garbage collection protection.
:param event:
:return:
"""
# Remove widget objects from garbage collection protection
if self in _GCProtector.widgets:
_GCProtector.widgets.remove(self)