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)