Skip to content
manne ohrstrom edited this page Apr 30, 2014 · 58 revisions

The Shotgun Utils Framework contains a collection of helpers and tools to make it easy and painless to build consistent looking applications. It contains several modules which can each be used independently:

  • A Shotgun Data Model deriving from QStandardItemModel which makes it easy to build responsive, data rich applications quickly.
  • A delegates framework which makes it easy to build custom views inside QT's view classes. Typically used in conjunction with the Shotgun Model.
  • A help popup module which makes it easy to deliver interactive documentation queues to users.
  • A settings system handles per-user preferences and makes it easy to work with the QSettings class from within your app code.
  • A progress overlay module provides a standardized progress overlay widget which can easily be placed on top of any other QWidget to indicate that work is happening and potentially report messages back to the user.

In the following documentation sections, each module is presented in detail with API reference and code examples.

Shotgun Model

The shotgun data model helps you build responsive, data rich applications quickly and leverage QT's build in model/view framework

The Shotgun Model is a custom QT Model specialized for Shotgun Queries. It uses a disk based cache and runs queries asynchronously to Shotgun in the background for performance. In a nutshell, you derive your own model class from it, set up a query, and then hook up your model to a QT View which will draw the data. The class contains several callbacks and allows for extensive customization, yet tries to shadow and encapuslate a lot of the details.

For convenience, three different classes are provided, allowing you to choose the right level of encapsulation.

Why should I use the Shotgun Model?

Using the Shotgun Model means switching to Model/View based programming. While there is perhaps slighly more overhead to get started with this, there are many benefits. The Shotgun Model (and the corresponding delegates and Shotgun View components) is an attempt to bridge this gap and make it quick and painless to get started with QT Model/View programming.

QT provides a strong and mature Model/View hierarchy which is robust and easy to work with. If you are not familiar with it, please check the following links:

The benefits with this approach will become evident as you scale your UIs and their complexity. Developing code and tools where the data and the UI is combined will work in simple scenarios but for data rich applications this approach becomes hard to maintain, difficult to reuse and typically scales poorly as the dataset complexity grows. By leveraging QTs built-in functionality, you get access to a mature and well documented toolbox that makes it quick to develop tools:

  • A Shotgun model instance represents a single shotgun query. With two lines of code you can connect the resultset of such a query with a standard Qt list, tree or table.
  • The Shotgun model is cached, meaning that all data is fetched in the background in a worker thread. This means that the data in your UI will load up instantly and you never have to wait for shotgun. If the query result is different than the cached result, the view will be updated on the fly as the data arrives.
  • With QT you have access to SelectionModels, making it easy to create consistent selection behaviour, even across multiple views. With full keyboard support.
  • With QT proxy models you can easily create interactive searching and filtering on the client side.
  • Views and models are optimized and will perform nicely even if you have thousands of items loaded.
  • Through the shotgun view module, you can easily control the QT delegates system, making it easy to draw custom UIs for each cell in your view.

Shotgun Model Hello World

A hello world style example would look something like this, assuming this code is inside a toolkit app:

# Import the shotgun_model module from the shotgun utils framework
shotgun_model = tank.platform.import_framework("tk-framework-shotgunutils", "shotgun_model") 
# Set up alias
ShotgunModel = shotgun_model.ShotgunModel 

# Create a standard QT Tree View
view = QtGui.QTreeView(parent_widget)

# Set up our data backend
model = shotgun_model.SimpleShotgunModel(parent_widget)

# Tell the view to pull data from the model
view.setModel(model)

# load all assets from Shotgun
model.load_data(entity_type="Asset")

The above code will create a standard QT treeview of all assets in Shotgun.

Beyond Hello World

The simple setup outlined above could be extended in the following ways:

  • If you need more control of how the data is being retrieved, consider instead creating your own class and derive from ShotgunOverlayModel. This makes it possible to customize the shotgun data as it arrives from Shotgun, control the hierarchy grouping and many other things.
  • If you want to retrieve results from your view, connect signals to the view's selection model.
  • If you want to cull out items from the model, for example only to show items matching a particular search criteria, use a Proxy Model (typically QSortFilterProxyModel).
  • If you want to control the way items are displyed in the view, consider using the Shotgun delegates module documented below.

Class SimpleShotgunModel API Reference

Convenience wrapper around the Shotgun model for quick and easy access. Use this when you want to prototype data modelling or if your are looking for a simple flat data set reflecting a shotgun query. All you need to do is to instantiate the class (typically once, in your constructor) and then call load_data to specify which shotgun query to load up in the model. Subsequently call load_data whenever you wish to change the Shotgun query associated with the model.

This class derives from ShotgunModel so all the customization methods available in the normal ShotgunModel can also be subclassed from this class.

SimpleShotgunModel Constructor

Class constructor. The simple shotgun model contains a progress spinner which will appear whenever the object is doesn't have any data to display. This progress spinner will be placed on top of the parent object specified in the parent constructor parameter.

SimpleShotgunModel(parent)

Constructor Parameters

  • QWidget parent - QWidget which this model will be parented under. This widget will also be used to paint a spinner and display error messages.

load_data()

Loads shotgun data into the model, using the cache if possible. The model is not nested and the first field that is specified via the fields parameter (code by default) will be used as the default name for all model items.

model_obj.load_data(entity_type, filters=None, fields=None)

Parameters & Return Value

  • str entity_type - Shotgun Entity Type to load data for.
  • list filters - Shotgun API find-style filter list. If no list is specified, all records for the given entity type will be retrieved.
  • list fields - List of Shotgun fields to retrieve. If not specified, the 'code' field will be retrieved.

Class ShotgunOverlayModel API Reference

Convenience wrapper around the ShotgunModel class which adds spinner and error reporting overlay functionality. Where the ShotgunModel is a classic model class which purely deals with data, this class connects with a QWidget in order to provide progress feedback whenever necessary. Internally, it holds an instance of the ShotgunOverlayWidget widget described further down in this documentation and will show this whenever there is no data to display in the view. This means that it is straight forward to create shotgun views with a spinner on top indicating when data is being loaded and where any errors are automatically reported to the user.

progress_spinner_start Signal

progress_spinner_start()

Signal that gets emitted whenever the model deems it appropriate to indicate that data is being loaded. Note that this signal is not emitted every time data is loaded from Shotgun, but only when there is no cached data available to display. This signal can be useful if an implementation wants to set up a custom overlay system instead of or in addition to the built in one that is provided via the set_overlay_parent() method.

ShotgunOverlayModel Constructor

Constructor. This will create a model which can later be used to load and manage Shotgun data.

ShotgunOverlayModel(parent, overlay_widget, download_thumbs=True, schema_generation=0)

Constructor Parameters

  • QObject parent - Parent object.
  • QWidget overlay_widget - Widget on which the spinner/info overlay should be positioned.
  • bool download_thumbs - Boolean to indicate if this model should attempt to download and process thumbnails for the downloaded data.
  • int schema_generation - Schema generation index. If you are changing the format of the data you are retrieving from Shotgun, and therefore want to invalidate any cache files that may already exist in the system, you can increment this integer.

_hide_overlay_info()

Hides any overlay that is currently shown, except for error messages.

model_obj._hide_overlay_info()

_show_overlay_pixmap()

Show an overlay status message in the form of a pixmap. This is for example useful if a particular query doesn't return any results. If an error message is already being shown, the pixmap will not replace the error message.

model_obj._hide_overlay_info(pixmap)

Parameters & Return Value

  • QPixmap pixmap - QPixmap object containing graphic to show

_show_overlay_info_message()

Show an overlay status message. If an error is already displayed, this info message will not be shown.

model_obj._show_overlay_info_message(msg)

Parameters & Return Value

  • str msg - message to display
  • Returns: True if the message was shown, False if not.

_show_overlay_error_message()

Show an overlay error message.

model_obj._show_overlay_error_message(msg)

Parameters & Return Value

  • str msg - message to display

Class ShotgunModel API Reference

Widget Delegate and Standard Widgets

The widget delegates module makes it easy to create custom UI experiences that are using data from the Shotgun Model (or any other QModel). It also contains a set of standard widgets.

If you feel that the visuals that you get back from a standard QTreeView or QListView are not sufficient for your needs, the view utilities provide a collection of tools to help you quickly build consistent and nice looking user QT Views. These are typically used in conjunction with the ShotgunModel but this is not a requirement.

  • The WidgetDelegate helper class makes it easy to connect a QWidget of your choosing with a QT View. The WidgetDelegate will use your specified widget when the view is drawn and updated. This allows for full control of the visual appearance of any view.
  • For consistency reasons we also supply a couple of simple widget classes that are meant to be used in conjunction with the WidgetDelegate. By using these widgets in your code you get the same look and feel as all other apps that use the widgets.

QT allows for customization of cell contents inside of its standard view classes through the use of so called delegate classes, however this can be cumbersome and difficult to get right. In an attempt to simplify the process of view customization, the Shotgun Utils framework contains classes for making it easy to plugin in standard QWidgets and use these as "brushes" when QT is drawing its views. You can then quickly produce UI in for example the QT designer, hook up the data exchange between your model class and the widget in a delegate and quickly have some nice custom looking user interfaces!

The principle is that you derive a custom delegate class from the WidgetDelegate that is contained in the framework. This class contains for simple methods that you need to implement. As part of this you can also control the flow of data from the model into the widget each time it is being drawn, allowing for complex data to be easily passed from Shotgun via the model into your widget. For consistency, the framework also contains a number of standard widgets. Using these will provide you with a consistent looking set of visual components.

The following example shows some of the basic around using this delegate class with the Shotgun Model.

# import the shotgun_model and view modules from the shotgun utils framework
shotgun_model = tank.platform.import_framework("tk-framework-shotgunutils", "shotgun_model")
shotgun_view = tank.platform.import_framework("tk-framework-shotgunutils", "shotgun_view")


class ExampleDelegate(shotgun_view.WidgetDelegate):
    """
    Shows an example how to use a shotgun_view.ListWidget in a std view.
    """

    def __init__(self, view):
        """
        Constructor
        """
        shotgun_view.WidgetDelegate.__init__(self, view)
        
    def _create_widget(self, parent):
        """
        Returns the widget to be used when creating items
        """
        return shotgun_view.ListWidget(parent)
    
    def _on_before_paint(self, widget, model_index, style_options):
        """
        Called when a cell is being painted.
        """        
        # extract the standard icon associated with the item
        icon = model_index.data(QtCore.Qt.DecorationRole)
        thumb = icon.pixmap(512)
        widget.set_thumbnail(thumb)

        # get the shotgun query data for this model item     
        sg_item = shotgun_model.get_sg_data(model_index)   

        # get values and populate widget
        version_str = "Version %03d" % sg_item.get("version_number")            
        created_str = sg_item.get("created_at")            
        desc_str = sg_item.get("description")        
        author_str = "%s" % sg_item.get("created_by").get("name")
        
        header_str = "<b>%s</b>" % (version_str)
        body_str = "<b>%s</b> &mdash; %s<br><br><small>%s</small>" % (author_str, desc_str, created_str)
        widget.set_text(header_str, body_str)

    def _on_before_selection(self, widget, model_index, style_options):
        """
        Called when a cell is being selected.
        """
        # do std drawing first
        self._on_before_paint(widget, model_index, style_options)        
        widget.set_selected(True)        
            
    def sizeHint(self, style_options, model_index):
        """
        Base the size on the icon size property of the view
        """
        return shotgun_view.ListWidget.calculate_size()
             

The next section contains the detailed API reference for the delegate system.

Class WidgetDelegate API Reference

Convenience wrapper that makes it straight forward to use widgets inside of delegates. This class is basically an adapter which lets you connect a view (QAbstractItemView) with a QWidget of choice. This widget is used to "paint" the view when it is being rendered. This class can be used in conjunction with the various widgets found as part of the framework module (for example list_widget and thumb_widget). You use this class by subclassing it and implementing the three methods _create_widget(), _on_before_paint(), _on_before_selection() and sizeHint().

Note! In order for this class to handle selection correctly, it needs to be attached to the view after the model has been attached. (This is to ensure that it is able to obtain the view's selection model correctly.)

Constructor Parameters

  • QWidget view - The view upon which the delegate should operate.

delegate_obj._create_widget()

This needs to be implemented by any deriving classes. Should return a QWidget that will be used when grid cells are drawn.

delegate_obj._create_widget( parent )

Parameters & Return Value

  • QWidget parent - QWidget to parent the widget to.
  • Returns: QWidget that will be used to paint grid cells in the view.

delegate_obj._on_before_paint()

This needs to be implemented by any deriving classes. This is called just before a cell is painted. This method should configure values on the widget (such as labels, thumbnails etc) based on the data contained in the model index parameter which is being passed.

delegate_obj._on_before_paint( widget, model_index, style_options )

Parameters & Return Value

  • QWidget widget - The QWidget (constructed in _create_widget()) which will be used to paint the cell.
  • QModelIndex index - QModelIndex object representing the data of the object that is about to be drawn.
  • QStyleOptionViewItem style_options - QStyleOptionViewItem object containing specifics about the view related state of the cell.

delegate_obj._on_before_selection()

This needs to be implemented by any deriving classes. This method is called just before a cell is selected. This method should configure values on the widget (such as labels, thumbnails etc) based on the data contained in the model index parameter which is being passed.

delegate_obj._on_before_selection( widget, model_index, style_options )

Parameters & Return Value

  • QWidget widget - The QWidget (constructed in _create_widget()) which will be used to paint the cell.
  • QModelIndex index - QModelIndex object representing the data of the object that is about to be drawn.
  • QStyleOptionViewItem style_options - QStyleOptionViewItem object containing specifics about the view related state of the cell.

Class ListWidget and ThumbWidget API Reference

The ListWidget and ThumbWidget classes are simple standard building blocks that you can use in your views to present lists with thumbnails and action menus. They are suitable for horizontally scrolling collections of tiems such as Shots, Assets or Notes.

widget_obj.set_actions()

Adds a list of QActions to add to the actions menu for this widget.

widget_obj.set_actions( actions )

Parameters & Return Value

  • list actions - List of QActions to be added to this widget's action menu

widget_obj.set_selected()

Adjust the style sheet to indicate selection or not

widget_obj.set_selected( selected )

Parameters & Return Value

  • bool selected - Indicates if the widget should appear to be selected or not.

widget_obj.set_thumbnail()

Assign a thumbnail to the widget. The pixmap must be 100x100 or it will appear squeezed.

widget_obj.set_thumbnail( pixmap )

Parameters & Return Value

  • QPixmap pixmap - Icon to assign.

widget_obj.set_text()

Populates the widget text

widget_obj.set_text( header, body )

Parameters & Return Value

  • str header - Header text
  • str body - Body text

widget_obj.calculate_size()

Returns the size suitable for this widget.

widget_obj.calculate_size()

Parameters & Return Value

  • Returns: QSize value.

Help Popup Window

The help popup is a window which presents a series of help images to the user, typically displayed at startup to help introduce new feature or a new application to a user. The information is presented as an interactive slide show where the user can easily navigate between sides.

You simply provide it with a list of transparent bitmaps sized 650x400 and it will create a slideshow with animated transitions and a link to the bundle's associated documentation (retrieved from the manifest).

The help_screen module contains a single method that takes care of everything. Simply create the number of 650x400 images that you want and export them as transparent pngs and ensure that the background is transparent (this is not a strict requirement, but it will most likely look strange with a non-transparent background). Add the images to the QT resource section of your app and then execute the following code to launch the help screen:

# example of how the help screen can be used within your app code

# import the module - note that this is using the special
# import_framework code so it won't work outside an app
help_screen = sgtk.platform.import_framework("tk-framework-shotgunutils", "help_screen")

# generate pixmaps of the help screen resources we want to display
help_pix = [ QtGui.QPixmap(":/res/help_1.png"), 
             QtGui.QPixmap(":/res/help_2.png"), 
             QtGui.QPixmap(":/res/help_3.png"),
             QtGui.QPixmap(":/res/help_4.png") ] 

# get the current app object
app = sgtk.platform.current_bundle()

# get the current QT UI window 
window = current_dialog_object.window()

# and present the help screen. This is a non-blocking call 
# and the application flow will continue
help_screen.show_help_screen(self.window(), app, help_pix)

Settings module

The settings module makes it easy to store things like user preferences, app related state etc. For example, if you want your app to remember the state of a checkbox across sessions, you can use the settings module to store this value. Adding persisting settings to an app can quickly drastically improve the user experience at a very low cost.

This settings module wraps around QSettings. This means that the settings data will be stored on your local machine and that they are for the current user only. If you need to share a preference or setting between multiple users, this module is most likely not the right one to use.

Settings can have different scope. This indicates how the setting should be shared between instances of apps, shotgun setups etc. Please note that the setting is still per-user and per-machine, so if it is scoped to be "global", it means that it will be shared across all different apps, engines, configurations, projects and shotgun sites for the current user on their local machine.

  • SCOPE_GLOBAL - No restriction.
  • SCOPE_SITE - Settings are per Shotgun site.
  • SCOPE_PROJECT - Settings are per Shotgun project.
  • SCOPE_CONFIG - Settings are per Shotgun Pipeline Configuration.
  • SCOPE_INSTANCE - Settings are per app or engine instance. For example, if your app contains a set of filters, and you want these to be remembered across sessions, you would typically use this scope. Each instance of the app will remember its own filters, so when you run it in the asset environment, one set of filters are remembered, when you run it in the shot environment, another set of filters etc.
  • SCOPE_ENGINE - One setting per engine. This makes it possible to store one set of preferences for apps running in Photoshop, Maya, Nuke etc. This makes it possible to for example store a setting that remembers if a "welcome screen" for your app has been displayed - so that it is only displayed once in Maya, once in Nuke etc.
# example of how the settings module can be used within your app code
# import the module - note that this is using the special
# import_framework code so it won't work outside an app
settings = sgtk.platform.import_framework("tk-framework-shotgunutils", "settings")

# typically in the constructor of your main dialog or in the app, create a settings object:
self._settings_manager = settings.UserSettings(sgtk.platform.current_bundle())

# the settings system will handle serialization and management of data types
# so you can pass simple types such as strings, ints and lists and dicts of these.
#
# retrieve a settings value and default to a value if no settings was found
scale_val = self._settings_manager.retrieve("thumb_size_scale", 140)

# or store the same value
self._settings_manager.store("thumb_size_scale", 140)

# by default, things are scoped with `SCOPE_GLOBAL`. 
# If you want to specify another scope, add a scope parameter.

# Fetch a prefeence with a specific scope
ui_launched = self._settings_manager.retrieve("ui_launched", False, self._settings_manager.SCOPE_ENGINE)

# And store a preference with a specific scope
self._settings_manager.store("ui_launched", True, self._settings_manager.SCOPE_ENGINE)

Progress Overlay

The progress overlay module provides a standardized progress overlay widget which can easily be placed on top of any other QWidget to indicate that work is happening and potentially report messages back to the user. Once you have instantiated and placed it on top of another widget, you can execute various methods to control its state.

# example of how the overlay can be used within your app code

# import the module - note that this is using the special
# import_framework code so it won't work outside an app
overlay = sgtk.platform.import_framework("tk-framework-shotgunutils", "overlay_widget") 

# now inside your app constructor, create an overlay and parent it to something
self._overlay = overlay.ShotgunOverlayWidget(my_widget)

# now you can use the overlay to report things to the user
try:
   self._overlay.start_spin()
   run_some_code_here()
except Exception, e:
   self._overlay.show_error_message("An error was reported: %s" % e)
finally:
   self._overlay.hide()

Please note that the example above is crude and for heavy computational work we recommend an asynchronous approach with a worker thread for better UI responsiveness.

API Reference

ShotgunOverlayWidget Constructor

ShotgunOverlayWidget(parent)

Constructor Parameters

  • QWidget parent - Parent object.

start_spin()

Show a spinner animation.

overlay_obj.start_spin()

Parameters & Return Value

  • QWidget parent_widget - A QWidget object on top of which any progress overlays will be rendered.

show_error_message()

Display an error message centered on the overlay.

overlay_obj.show_error_message(msg)

Parameters & Return Value

  • str msg - Error message to display

show_message()

Display a message centered on the overlay.

overlay_obj.show_message(msg)

Parameters & Return Value

  • str msg - Message to display

show_message_pixmap()

Display a pixmap centered on the overlay

overlay_obj.show_message_pixmap(pixmap)

Parameters & Return Value

  • QPixmap pixmap - Pixmap to display

hide()

Hides the overlay.

overlay_obj.hide(hide_errors=True)

Parameters & Return Value

  • bool hide_errors - If this flag is set to false, the hide operation will only hide the overlay in case no errors have been reported.
Clone this wiki locally