text: #! /usr/bin/env python
  #-******************************************************************************
  #
  # Copyright (c) 2012-2013,
  #  Sony Pictures Imageworks Inc. and
  #  Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
  #
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are
  # met:
  # *       Redistributions of source code must retain the above copyright
  # notice, this list of conditions and the following disclaimer.
  # *       Redistributions in binary form must reproduce the above
  # copyright notice, this list of conditions and the following disclaimer
  # in the documentation and/or other materials provided with the
  # distribution.
  # *       Neither the name of Sony Pictures Imageworks, nor
  # Industrial Light & Magic, nor the names of their contributors may be used
  # to endorse or promote products derived from this software without specific
  # prior written permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  #
  #-******************************************************************************
  
  import os
  import re
  import sys
  import math
  import time
  import logging
  import traceback
  from functools import wraps
  from copy import deepcopy
  
  from PyQt4 import QtCore
  from PyQt4 import QtGui
  from PyQt4 import uic
  
  import imath
  import alembic
  
  import abcview
  from abcview import log, style, config
  from abcview.io import Session, Scene, Camera, ICamera
  from abcview.gl import GLCamera, GLICamera, GLScene
  from abcview.gl import get_final_matrix
  from abcview.widget.console_widget import AbcConsoleWidget
  from abcview.widget.viewer_widget import GLWidget
  from abcview.widget.time_slider import TimeSlider
  from abcview.widget.tree_widget import *
  from abcview.utils import json
  
  __all__ = ['create_app', 'AbcView', 'io2gl', 'version_check', ]
  
  def make_dirty(func):
      """make abcview session dirty decorator"""
      @wraps(func)
      def with_wrapped_func(*args, **kwargs):
          return func(*args, **kwargs)
          if args[0].session:
              args[0].session.make_dirty()
      return with_wrapped_func
  
  def make_clean(func):
      """make abcview session clean decorator"""
      @wraps(func)
      def with_wrapped_func(*args, **kwargs):
          return func(*args, **kwargs)
          if args[0].session:
              args[0].session.make_clean()
      return with_wrapped_func
  
  def wait(func):
      """wait/arrow mouse cursor decorator"""
      @wraps(func)
      def with_wrapped_func(*args, **kwargs):
          args[0].setCursor(QtCore.Qt.WaitCursor)
          ret = func(*args, **kwargs)
          args[0].setCursor(QtCore.Qt.ArrowCursor)
          return ret
      return with_wrapped_func
  
  def message(info):
      dialog = QtGui.QMessageBox()
      dialog.setStyleSheet(style.DIALOG)
      dialog.setText(info)
      dialog.exec_()
  
  # global file load counter (for default scene colors)
  global COUNT; COUNT = 0
  
  def io2gl(item, viewer=None):
      """
      Recursively recasts an IO-module object to a GL-module object. 
      Used for loading session items into the GL viewer.
  
      :param item: Session, Scene, Camera or ICamera object
      :param viewer: GLWidget object
      """
      global COUNT; COUNT += 1
      if item.type() == Session.type():
          for child in item.walk():
              io2gl(child, viewer)
          return
      
      elif item.type() == Scene.type():
          if not os.path.isfile(item.filepath):
              log.warn("file not found %s" % item.filepath)
              return
          item.__class__ = GLScene
          item.init()
  
      elif item.type() == Camera.type():
          _translation = item.translation
          _rotation = item.rotation
          _scale = item.scale
          _center = item.center
          _near = item.near
          _far = item.far
          _ratio = item.aspect_ratio
          item.__class__ = GLCamera
          item.init(viewer, 
                    translation=_translation,
                    rotation=_rotation,
                    scale=_scale,
                    center=_center,
                    near=_near,
                    far=_far,
                    aspect_ratio=_ratio,
                   )
      
      elif item.type() == ICamera.type():
          item.__class__ = GLICamera
          item.init(viewer)
      
      else:
          return
  
  class QScriptAction(QtGui.QGroupBox):
      def __init__(self, name, filepath, action, doc="", version="", author=""):
          """
          :param name: name of the script
          :param action: QWidgetAction object
          :param doc: doc string
          :param version: version string
          :param author: author string
          """
          super(QScriptAction, self).__init__()
          self.action = action
          self.layout = QtGui.QHBoxLayout()
          self.layout.setMargin(0)
          self.layout.setSpacing(0)
          self.label = QtGui.QPushButton(os.path.basename(name))
          self.button = QtGui.QPushButton()
          self.button.setFixedSize(12, 12)
          self.button.setObjectName("edit_button")
          self.button.setIcon(QtGui.QIcon("%s/edit.png" % config.ICON_DIR))
          self.button.setIconSize(self.button.size())
          self.button.setFocusPolicy(QtCore.Qt.NoFocus)
          self.layout.addWidget(self.label)
          self.layout.addWidget(self.button)
          self.name = name
          self.filepath = filepath
          self.setLayout(self.layout)
          self.label.pressed.connect(self.handle_clicked)
          self.button.pressed.connect(self.handle_edit)
          self.setToolTip("%s %s\n(author: %s)\n\n%s" %(name, version, author, doc))
  
      def handle_clicked(self):
          """script click handler"""
          self.action.trigger()
  
      def handle_edit(self):
          """script edit button handler"""
          cmd = " ".join([config.SCRIPT_EDITOR, self.filepath])
          log.debug(cmd)
          os.system(cmd)
  
  class AbcMenuBar(QtGui.QMenuBar):
      def __init__(self, parent, main):
          super(AbcMenuBar, self).__init__(parent)
          self.main = main
          self.setFocusPolicy(QtCore.Qt.ClickFocus)
  
          self.file_menu = QtGui.QMenu("File")
          self.widget_menu = QtGui.QMenu("Widgets")
          self.script_menu = QtGui.QMenu("Scripts")
          self.help_menu = QtGui.QMenu("Help")
          
          self.file_menu.setStyleSheet(style.MAIN)
          self.widget_menu.setStyleSheet(style.MAIN)
          self.script_menu.setStyleSheet(style.MAIN)
          self.help_menu.setStyleSheet(style.MAIN)
          
          self.setup_file_menu()
          self.setup_wid_menu()
          self.setup_script_menu()
          self.setup_help_menu()
  
      def setup_file_menu(self):
          self.file_menu.addAction("New", self.main.handle_new, "Ctrl+N")
          self.file_menu.addAction("Open", self.main.handle_open, "Ctrl+O")
          self.file_menu.addAction("Import", self.main.handle_import, "Ctrl+I")
          self.file_menu.addAction("Reload", self.main.handle_reload, "Ctrl+R")
  
          self.file_menu.addSeparator()
          self.file_menu.addAction("Save", self.main.handle_save, "Ctrl+S")
          self.file_menu.addAction("Save As..", self.main.handle_save_as, "Ctrl+Shift+S")
          self.file_menu.addSeparator()
          self.file_menu.addAction("Save Layout", self.main.save_settings, "Ctrl+Alt+S")
          self.file_menu.addAction("Reset Layout", self.main.reset_settings)
          self.file_menu.addAction("Review Mode", self.main.review_settings)
          self.file_menu.addSeparator()
          self.file_menu.addAction("Quit", self.main.close, "Ctrl+Q") 
          self.addMenu(self.file_menu)
          
      def setup_wid_menu(self):
          self.console_action = QtGui.QAction("Console", self)
          self.console_action.setShortcut("Ctrl+Shift+C")
          self.console_action.setCheckable(True)
          self.console_action.setChecked(True)
          self.connect(self.console_action, QtCore.SIGNAL("toggled (bool)"), self.handle_show_console)
          self.widget_menu.addAction(self.console_action)
  
          self.object_action = QtGui.QAction("Objects", self)
          self.object_action.setShortcut("Ctrl+Shift+O")
          self.object_action.setCheckable(True)
          self.object_action.setChecked(True)
          self.connect(self.object_action, QtCore.SIGNAL("toggled (bool)"), self.handle_show_objects)
          self.widget_menu.addAction(self.object_action)
  
          self.props_action = QtGui.QAction("Properties", self)
          self.props_action.setShortcut("Ctrl+Shift+P")
          self.props_action.setCheckable(True)
          self.props_action.setChecked(True)
          self.connect(self.props_action, QtCore.SIGNAL("toggled (bool)"), self.handle_show_props)
          self.widget_menu.addAction(self.props_action)
  
          self.time_slider_action = QtGui.QAction("Timeline", self)
          self.time_slider_action.setShortcut("Ctrl+Shift+T")
          self.time_slider_action.setCheckable(True)
          self.time_slider_action.setChecked(True)
          self.connect(self.time_slider_action, QtCore.SIGNAL("toggled (bool)"), self.handle_show_timeline)
          self.widget_menu.addAction(self.time_slider_action)
  
          self.viewer_action = QtGui.QAction("Viewer", self)
          self.viewer_action.setShortcut("Ctrl+Shift+V")
          self.viewer_action.setCheckable(True)
          self.viewer_action.setChecked(True)
          self.connect(self.viewer_action, QtCore.SIGNAL("toggled (bool)"), self.handle_show_viewer)
          self.widget_menu.addAction(self.viewer_action)
  
          self.addMenu(self.widget_menu)
  
      def setup_script_menu(self):
          self.addMenu(self.script_menu)
          self.handle_refresh_scripts()
  
      def setup_help_menu(self):
          self.help_menu.addAction("About Alembic", self.handle_about_abc)
          self.help_menu.addAction("About AbcView", self.handle_about_abcview)
          self.addMenu(self.help_menu)
  
      def handle_refresh_scripts(self):
          self.script_menu.clear()
          self.script_menu.addAction("Refresh", self.handle_refresh_scripts)
          self.script_menu.addSeparator()
  
          def get_docs(script):
              """
              parses a python file looking for docstring, __name__ and __author__.
  
              :param script: filepath to .py file
              """
              import ast
  
              doc = ""
              name = os.path.basename(script)
              version = ""
              author = ""
              
              try:
                  m = ast.parse(''.join(open(script)))
                  doc = ast.get_docstring(m)
                  assigns = [node for node in m.body if type(node) == ast.Assign]
                  names = [a.value.s for a in assigns if getattr(a.targets[0], 'id', None) == '__name__']
                  authors = [a.value.s for a in assigns if getattr(a.targets[0],'id', None) == '__author__']
                  versions = [a.value.s for a in assigns if getattr(a.targets[0], 'id', None) == '__version__']
  
                  if names:
                      name = names[0]
                  else:
                      name = os.path.basename(script)
                  if authors:
                      author = authors[0]
                  if versions:
                      version = versions[0]
  
              except Exception, e:
                  log.error(e)
  
              return doc, name, version, author
  
          def find_scripts(path):
              """
              walks scripts dir looking for .py files
  
              :param path: directory containing scripts
              """
              _scripts = {}
              if not path or not os.path.isdir(path):
                  return
              for r, d, f in os.walk(path):
                  for files in f:
                      name = os.path.basename(files)
                      if name == "__init__.py":
                          continue
                      if files.endswith(".py"):
                          script = os.path.join(r, files)
                          doc, name, version, author = get_docs(script)
                          _scripts[name] = {
                              "name": name,
                              "filepath": script,
                              "doc": doc,
                              "version": version,
                              "author": author
                          }
              return _scripts
  
          def build_scripts(scripts):
              """
              builds up the script menu from a dict of found scripts
              """
              for name, meta in scripts.items():
                  doc = meta.get("doc")
                  version = meta.get("version")
                  author = meta.get("author")
                  filepath = meta.get("filepath")
                  script_act = QtGui.QWidgetAction(self.script_menu)
                  script_act.setDefaultWidget(QScriptAction(name, filepath, script_act, doc, version, author))
                  script_act.setData(filepath)
                  script_act.triggered.connect(self.handle_run_script)
                  self.script_menu.addAction(script_act)
  
          # built-in scripts that ship with abcview
          scripts = find_scripts(config.SCRIPT_DIR)
  
          # user-defined script paths
          for path in config.USER_SCRIPT_DIR:
              scripts.update(find_scripts(path) or {})
  
          # build the scripts menu
          build_scripts(scripts)
  
      def handle_run_script(self):
          script_path = str(self.sender().data().toString())
          self.main.load_script(script_path)
  
      def handle_show_console(self, toggled):
          self.main.toggle_widget(self.main.console)
  
      def handle_show_objects(self):
          self.main.toggle_widget(self.main.objects_group)
  
      def handle_show_props(self):
          self.main.toggle_widget(self.main.properties_splitter)
  
      def handle_show_timeline(self):
          self.main.toggle_widget(self.main.time_slider_toolbar)
      
      def handle_show_viewer(self):
          self.main.toggle_widget(self.main.viewer_group)
  
      def handle_about_abc(self):
          message("Using Alembic %s" % alembic.Abc.GetLibraryVersionShort())
  
      def handle_about_abcview(self):
          _v = " ".join([config.__prog__, config.__version__])
          message("\n".join([_v, abcview.__doc__]))
  
  class FindLineEdit(QtGui.QLineEdit):
      """
      Auto-unfocus line editor used for search bar.
      """
      def __init__(self, parent):
          super(FindLineEdit, self).__init__(parent)
          self._parent = parent
          self.setSizePolicy(QtGui.QSizePolicy.Expanding, 
                                          QtGui.QSizePolicy.Maximum)
          self.setFocusPolicy(QtCore.Qt.ClickFocus)
  
      def leaveEvent(self, event):
          self._parent.setFocus()
          super(FindLineEdit, self).leaveEvent(event)
  
  class Splash(QtGui.QSplashScreen):
      """
      AbcView splash screen.
      """
      def __init__(self, parent):
          super(Splash, self).__init__(parent)
          self._parent = parent
          self.setStyleSheet(style.SPLASH)
          self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
  
          # logo
          self.logo = QtGui.QLabel()
          self.logo.setPixmap(QtGui.QPixmap("%s/logo.png" % config.ICON_DIR))
         
          self.resize(600, 350)
          self.move(0, 0) 
  
          # layout
          layout = QtGui.QVBoxLayout()
          layout.setSpacing(0)
          layout.setMargin(0)
          layout.addWidget(self.logo)
          self.text = QtGui.QLineEdit()
          self.progress = QtGui.QProgressBar()
          self.progress.setMaximum(100)
          self.progress.setMinimum(0)
          self.progress.setValue(0)
          self.progress.setTextVisible(False)
          layout.addWidget(self.progress)
          layout.addWidget(self.text)
          self.setLayout(layout)
  
      def updateProgress(self, value):
          """
          Updates the splash screen progress bar.
  
          :param value: new value (must be b/w min and max values)
          """
          self.progress.setValue(value)
  
      def setMessage(self, message):
          """
          Sets the message to be displayed in the splash screen.
  
          :param message: message as string
          """
          self.text.setText(message)
  
  ## MAIN -----------------------------------------------------------------------
  class AbcView(QtGui.QMainWindow):
      '''
      Main application. The best way to instantiate this class is to use
      the :py:func:`.create_app` function.
      '''
  
      TITLE = " ".join([config.__prog__, config.__version__])
  
      def __init__(self, filepath=None):
          """
          Creates an instance of the AbcView Main Window.
  
          :param filepath: file to load (.io or .abc)
          """
          QtGui.QMainWindow.__init__(self)
          self.setWindowState(QtCore.Qt.WindowActive)
          self.setWindowFlags(QtCore.Qt.Window)
          self.setWindowTitle(self.TITLE)
          self.setStyle(QtGui.QStyleFactory.create('cleanlooks'))
          self.setStyleSheet(style.MAIN)
          self.setMinimumSize(200, 200)
          self.setFocusPolicy(QtCore.Qt.StrongFocus)
  
          # deferred load list, files loaded after event loop starts
          self._load_files = []
  
          # overrides are used for deferring frame range overrides
          self._overrides = {}
  
          # for storing session data
          self.settings = QtCore.QSettings("Alembic", 
                             "-".join([config.__prog__, config.__version__]))
          self.session = Session(filepath)
  
          # main menu
          self.main_menu = AbcMenuBar(self, main=self)
          self.setMenuBar(self.main_menu)
  
          self.objects_group = QtGui.QGroupBox(self)
          self.objects_group.setLayout(QtGui.QVBoxLayout())
          
          #TODO: refactor these signals
          self.objects_tree = ObjectTreeWidget(self, main=self)
          self.objects_tree.signal_item_removed.connect(self.handle_item_removed)
          self.objects_tree.signal_view_camera.connect(self.handle_view_camera)
          self.objects_tree.itemSelectionChanged.connect(self.handle_object_selection)
          self.objects_tree.itemClicked.connect(self.handle_item_selected)
          
          self.find_line_edit = FindLineEdit(self)
          self.objects_group.layout().setSpacing(0)
          self.objects_group.layout().setMargin(0)
          self.objects_group.layout().addWidget(self.find_line_edit)
          self.objects_group.layout().addWidget(self.objects_tree)
  
          # tree widgets
          self.properties_tree = PropertyTreeWidget(self, main=self)
          self.samples_tree = SampleTreeWidget(self, main=self)
          self.array_tree = ArrayTreeWidget(self, main=self)
  
          # console widget
          self.console = AbcConsoleWidget(self)
          self.console.updateNamespace({
              'exit': self.console.exit,
              'find': self.find,
              'self': self, # legacy, don't use
              'app': self,
              'objects': self.objects_tree,
              'properties': self.properties_tree,
              'samples': self.samples_tree,
              'alembic': alembic,
              'abcview': abcview
              })
  
          # viewer
          self.viewer = GLWidget(self)
          self.viewer_group = QtGui.QGroupBox(self)
          self.viewer_group.setLayout(QtGui.QVBoxLayout())
          self.viewer_group.layout().setSpacing(0)
          self.viewer_group.layout().setMargin(0)
          self.viewer_group.layout().addWidget(self.viewer)
          self.main_menu.setFocusProxy(self.viewer)
          
          # viewer/state connections
          self.viewer.signal_scene_error.connect(self.handle_viewer_error)
          self.viewer.signal_scene_opened.connect(self.handle_scene_opened)
          self.viewer.signal_set_camera.connect(self.handle_set_camera)
          self.viewer.signal_new_camera.connect(self.handle_new_camera)
          self.viewer.state.signal_current_frame.connect(self.handle_update_frame)
          self.viewer.state.signal_play_fwd.connect(self.handle_state_play_fwd)
          self.viewer.state.signal_play_stop.connect(self.handle_state_play_stop)
          self.viewer.signal_scene_selected.connect(self.handle_scene_selected)
          self.viewer.signal_object_selected.connect(self.handle_object_selected)
          self.viewer.signal_clear_selection.connect(self.objects_tree.clearSelection)
          self.viewer.signal_undrawable_scene.connect(self.handle_bad_scene)
  
          # time slider
          self.time_slider = TimeSlider(self)
          self.time_slider.setFocus(True)
          self.time_slider.signal_play_fwd.connect(self.handle_play)
          self.time_slider.signal_play_stop.connect(self.handle_stop)
          #self.time_slider.signal_frame_changed.connect(self.handle_time_slider_change)
          self.time_slider.signal_first_frame_changed.connect(self.handle_first_frame_change)
          self.time_slider.signal_last_frame_changed.connect(self.handle_last_frame_change)
          self.time_slider_toolbar = QtGui.QToolBar(self)
          self.time_slider_toolbar.setObjectName("time_slider_toolbar")
          self.time_slider_toolbar.addWidget(self.time_slider)
          self.time_slider_toolbar.setMovable(False)
          self.addToolBar(QtCore.Qt.BottomToolBarArea, self.time_slider_toolbar)
  
          # splitters
          self.main_splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self)
          self.console_splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self)
          self.objects_splitter = QtGui.QSplitter(QtCore.Qt.Horizontal, self)
          self.properties_splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self)
          self.properties_splitter.addWidget(self.properties_tree)
          self.properties_splitter.addWidget(self.samples_tree)
          self.properties_splitter.addWidget(self.array_tree)
          self.objects_splitter.addWidget(self.objects_group)
          self.objects_splitter.addWidget(self.viewer_group)
          self.objects_splitter.addWidget(self.properties_splitter)
          self.main_splitter.addWidget(self.objects_splitter)
          self.console_splitter.addWidget(self.main_splitter)
          self.console_splitter.addWidget(self.console)
          self.setCentralWidget(self.console_splitter)
  
          # TODO: refactor signals to new signal object method
          self.properties_tree.connect(self.objects_tree, QtCore.SIGNAL("itemSelectionChanged()"),
                  self.properties_tree.clear)
          self.samples_tree.connect(self.properties_tree, QtCore.SIGNAL("itemSelectionChanged()"),
                  self.samples_tree.clear)
          self.array_tree.connect(self.samples_tree, QtCore.SIGNAL("itemSelectionChanged()"),
                  self.array_tree.clear)
          self.connect(self.objects_tree, QtCore.SIGNAL("itemLoaded (PyQt_PyObject)"), 
                  self.handle_item_loaded)
          self.connect(self.objects_tree, QtCore.SIGNAL("itemUnloaded (PyQt_PyObject)"), 
                  self.handle_item_unloaded)
          self.properties_tree.connect(self.objects_tree, QtCore.SIGNAL("itemClicked (PyQt_PyObject)"), 
                  self.handle_object_clicked)
          self.samples_tree.connect(self.properties_tree, QtCore.SIGNAL("itemClicked (PyQt_PyObject)"), 
                  self.handle_property_clicked)
          self.array_tree.connect(self.samples_tree, QtCore.SIGNAL("itemClicked (PyQt_PyObject)"), 
                  self.array_tree.show_values)
          self.connect(self.find_line_edit, QtCore.SIGNAL("returnPressed ()"), 
                  self.handle_find)
     
          # wait for main event loop to start
          QtGui.QApplication.instance().signal_starting_up.connect(self._start)
         
          # create the splash screen
          self.splash = Splash(self)
  
          # open a session
          if filepath and filepath.endswith(Session.EXT):
              self.open_file(filepath)
  
      def __repr__(self):
          return "<%s %s>" % (self.__class__.__name__, id(self))
  
      @make_clean
      def clear(self):
          """
          Clears session and viewer.
          """
          self.session.clear()
          self.viewer.clear()
          self.objects_tree.clear()
          self.properties_tree.clear()
          self.samples_tree.clear()
          self.array_tree.clear()
  
      def confirm_close(self, message):
          """
          Window close confirmation.
  
          :param message: text to display
          """
          msg = QtGui.QMessageBox()
          msg.setStyleSheet(style.DIALOG)
          msg.setText(message);
          msg.setInformativeText("Do you want to save your changes?");
          msg.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel);
          msg.setDefaultButton(QtGui.QMessageBox.Save);
          return msg.exec_()
  
      ## settings
  
      def _settings(self, width, height):
          # default position settings
          self.setGeometry(200, 200, width, height)
  
          # default splitter locations
          self.objects_splitter.setSizes([50, 200, 50])
  
          # default widgets settings
          self.main_menu.object_action.setChecked(False)
          self.main_menu.props_action.setChecked(False)
          self.main_menu.console_action.setChecked(False)
          self.main_menu.viewer_action.setChecked(True)
          self.main_menu.time_slider_action.setChecked(True)
  
          # default viewer settings
          self.viewer.resize(self.width(), self.height())
          self.viewer.draw_hud = False
  
      def reset_settings(self, width=550, height=500):
          """
          Resets the AbcView layout settings to default values.
          """
          self.settings.clear()
          self._settings(width, height)
          self.viewer.camera.draw_grid = True
          self.viewer.camera.draw_hud = False
          self.viewer.camera.draw_normals = False
          self.viewer.camera.fixed = False
          self.viewer.camera.visible = True
  
      def review_settings(self):
          """
          Loads "review" display settings. Does not affect saved settings.
          """
          self._settings(1200, 600)
          self.viewer.camera.draw_grid = False
          self.viewer.camera.draw_hud = True
          self.viewer.camera.draw_normals = False
          self.viewer.camera.fixed = True
          self.viewer.camera.visible = True
  
      def load_settings(self):
          """
          Loads AbcView layout settings from a PyQt settings file.
          """
          geom = self.settings.value('geometry')
          if geom.isNull() or not geom.isValid():
              self.reset_settings()
              return
  
          # general settings
          self.restoreGeometry(geom.toByteArray())
          self.restoreState(self.settings.value('window_state').toByteArray())
         
          # layout settings
          self.settings.beginGroup("layout")
          self.main_splitter.restoreState(
                  self.settings.value('main_splitter').toByteArray())
          self.objects_splitter.restoreState(
                  self.settings.value('objects_splitter').toByteArray())
          self.properties_splitter.restoreState(
                  self.settings.value('properties_splitter').toByteArray())
          self.console_splitter.restoreState(
                  self.settings.value('console_splitter').toByteArray())
          self.main_menu.object_action.setChecked(not self.settings.value('objects_hidden').toBool())
          self.objects_group.setHidden(self.settings.value('objects_hidden').toBool())
          self.main_menu.props_action.setChecked(not self.settings.value('properties_hidden').toBool())
          self.properties_splitter.setHidden(self.settings.value('properties_hidden').toBool())
          self.main_menu.console_action.setChecked(not self.settings.value('console_hidden').toBool())
          self.console.setHidden(self.settings.value('console_hidden').toBool())
          self.main_menu.time_slider_action.setChecked(not self.settings.value('time_slider_hidden').toBool())
          self.time_slider_toolbar.setHidden(self.settings.value('time_slider_hidden').toBool())
          self.main_menu.viewer_action.setChecked(not self.settings.value('viewer_hidden').toBool())
          self.viewer_group.setHidden(self.settings.value('viewer_hidden').toBool())
          self.settings.endGroup()
  
          # restore viewer settings
          self.settings.beginGroup("viewer")
          self.viewer.restoreGeometry(self.settings.value("geometry").toByteArray())
  
          # drawing mode
          mode, found = self.settings.value("draw_mode").toInt()
          if found:
              self.viewer.mode = mode
  
          self.settings.endGroup()
  
      def save_settings(self, settings=None):
          """
          Saves layout settings to a PyQt settings file.
          """
          if settings is None:
              settings = self.settings
  
          # general settings
          settings.setValue("geometry", self.saveGeometry())
          settings.setValue("window_state", self.saveState())
  
          # layout settings
          settings.beginGroup("layout")
          settings.setValue("main_splitter", self.main_splitter.saveState())
          settings.setValue("objects_splitter", self.objects_splitter.saveState())
          settings.setValue("properties_splitter", self.properties_splitter.saveState())
          settings.setValue("console_splitter", self.console_splitter.saveState())
          settings.setValue("objects_hidden", self.objects_group.isHidden())
          settings.setValue("properties_hidden", self.properties_splitter.isHidden())
          settings.setValue("console_hidden", self.console.isHidden())
          settings.setValue("time_slider_hidden", self.time_slider_toolbar.isHidden())
          settings.setValue("viewer_hidden", self.viewer_group.isHidden())
          settings.endGroup()
  
          # save viewer settings
          settings.beginGroup("viewer")
          settings.setValue("geometry", self.saveGeometry())
  
          # save timeslider settings
          settings.endGroup()
  
      @make_dirty
      def toggle_widget(self, widget):
          """
          Toggles visibilty of given widget.
  
          :param widget: AbcView widget object.
          """
          if widget.isHidden():
              widget.show()
          else:
              widget.hide()
  
      def get_selected(self):
          """
          Returns the actively selected AbcView object.
          """
          items = self.objects_tree.selectedItems()
          if not items:
              return
          return items[0].object
  
      ## file io
  
      def set_default_mode(self, mode):
          """
          Sets the default display mode that overrides anything in the
          session file(s). Disable by setting mode to None. 
  
          :param mode: alembic.io.Mode value, or None
          """
          log.debug("[%s].set_default_mode: %s" % (self, mode))
          self._overrides["mode"] = mode
  
      def set_first_frame(self, frame):
          log.debug("[%s.set_first_frame] %s" % (self, frame))
          self._overrides["first_frame"] = frame
          self.viewer.state.min_time = frame / self.viewer.state.frames_per_second
          self.time_slider.set_minimum(frame)
          self.time_slider.slider.setMinimum(frame)
          self.viewer.state.min_frame = frame
  
      def set_last_frame(self, frame):
          log.debug("[%s.set_last_frame] %s" % (self, frame))
          self._overrides["last_frame"] = frame
          self.viewer.state.max_time = frame / self.viewer.state.frames_per_second
          self.time_slider.set_maximum(frame)
          self.time_slider.slider.setMaximum(frame)
          self.viewer.state.max_frame = frame
  
      def set_current_frame(self, frame):
          log.debug("[%s.set_current_frame] %s" % (self, frame))
          self._overrides["current_frame"] = frame
          self.viewer.state.current_frame = frame
          self.time_slider.set_value(frame)
          self.time_slider.slider.setValue(frame)
          self.viewer.state.current_frame = frame
  
      def set_load_files(self, filepaths):
          """
          Sets the deferred load list. Loads files after the main window and
          event loop is up and running. 
  
          :param filepath: list of files to load
          :param mode: Override display mode (abcview.io.Mode)
          """
          self._load_files = deepcopy(filepaths)
          self.splash.progress.setMaximum(len(filepaths))
  
      def set_frames_from_session(self, session):
          """
          Updates the time slider's frame range according to the min/max
          frames of the given session.
  
          :param session: GLScene object.
          """
          assert session.type() == Session.type(), "Invalid session"
          self.viewer.state.min_time = session.min_time
          self.viewer.state.max_time = session.max_time
          self.viewer.state.current_time = session.current_time
          self.time_slider.set_minimum(session.min_time * session.frames_per_second)
          self.time_slider.set_maximum(session.max_time * session.frames_per_second)
          self.time_slider.set_value(session.current_time * session.frames_per_second)
  
      def set_frames_from_scene(self, scene):
          """
          Updates the time slider's frame range according to the min/max
          frames of the given scene.
  
          :param scene: GLScene object.
          """
          assert scene.type() == GLScene.type(), "Invalid scene"
          self.viewer.state.min_time = scene.min_time()
          self.viewer.state.max_time = scene.max_time()
          self.time_slider.set_minimum(scene.min_time() * self.session.frames_per_second)
          self.time_slider.set_maximum(scene.max_time() * self.session.frames_per_second)
  
      def _start(self):
          """
          This is the startup callback function for when the QApplication starts 
          its event loop. This handles deferred file loading.
          """
          log.debug("[%s]._start" % self)
          self.viewer.setDisabled(True)
  
          self.splash.setMessage("starting up")
          self._wait()
          start = time.time()
          self.splash.show()
  
          # build the abcview session
          try:
              self._load()
          except Exception, e:
              traceback.print_exc()
              log.error(e)
  
          log.debug("session loaded in %.2fs"  % (time.time() - start))
          self.splash.close()
          self.time_slider.signal_frame_changed.connect(self.handle_time_slider_change)
  
          self.viewer.setEnabled(True)
  
      def _load(self):
          """
          Loads all the files and cameras in the load list, sets display 
          overrides and creates tree widget items.
          """
          self.viewer.setDisabled(True)
  
          # track files that can't be loaded
          _bad_files = []
  
          # if just one session file, replace current session
          if len(self._load_files) == 1 and self._load_files[0].endswith(Session.EXT):
              self.session = Session(self._load_files[0])
  
          # otherwise, add each file to current session
          else:
              for filepath in self._load_files:
                  if not os.path.isfile(filepath):
                      _bad_files.append(filepath)
                      continue
                  try:
                      self.session.add_file(filepath)
                  except abcview.io.AbcViewError, e:
                      log.debug(str(e))
                      _bad_files.append(filepath)
  
          # display a warning for bad files, bail if all are bad
          if len(_bad_files) > 0:
              message("The follow files could not be loaded:\n%s" \
                      %("\n".join(_bad_files)))
  
              if len(_bad_files) == len(self._load_files):
                  self.viewer.setDisabled(False)
                  return
  
          # convert the session items to gl objects
          io2gl(self.session, self.viewer)
  
          # item color range
          COLORS = style.gen_colors(COUNT)
          self.splash.progress.setMaximum(COUNT)
  
          # walk session looking for cameras and load them
          for index, item in enumerate(self.session.walk()):
              self.splash.setMessage("loading %s" % item.name)
  
              if item.type() in [GLCamera.type(), GLICamera.type()]:
                  self.viewer.add_camera(item)
                  if item.loaded:
                      self.viewer.set_camera(item)
              
              # set display overrides
              else:
                  if self._overrides.get("mode") is not None:
                      item.mode = self._overrides.get("mode")
             
                  if not item.properties.get("color", None):
                      item.color = COLORS[index]
  
          # default frame range
          self.viewer.state.min_frame = 0
          self.viewer.state.max_frame = 100
          self.time_slider.set_minimum(0)
          self.time_slider.set_maximum(100)
  
          # set the frame range from loaded files
          if len(self._load_files) == 1:
              if self._load_files[0].endswith(Session.EXT):
                  self.set_frames_from_session(self.session)
              elif self._load_files[0].endswith(Scene.EXT) and \
                   self._overrides.get("mode") != Mode.OFF:
                  self.set_frames_from_scene(self.session.items[0])
          
          # frame range overrides
          if self._overrides.get("first_frame"):
              first_frame = self._overrides.get("first_frame")
              self.viewer.state.min_frame = first_frame
              self.time_slider.set_minimum(first_frame)
          if self._overrides.get("last_frame"):
              last_frame = self._overrides.get("last_frame")
              self.viewer.state.max_frame = last_frame
              self.time_slider.set_maximum(last_frame)
          if self._overrides.get("current_frame"):
              curr_frame = int(self._overrides.get("current_frame"))
              self.viewer.state.current_frame = curr_frame
              self.time_slider.set_value(curr_frame)
  
          # frame boundary adjustments
          if self.viewer.state.current_time < self.viewer.state.min_time:
              self.viewer.state.current_time = self.viewer.state.min_time
          elif self.viewer.state.current_time > self.viewer.state.max_time:
              self.viewer.state.current_time = self.viewer.state.max_time
  
          # create trees for top-level session items, load items last
          for item in self.session.items:
              tree = None
              if item.type() == Session.type():
                  tree = SessionTreeWidgetItem(self.objects_tree, item)
              elif item.type() == GLScene.type():
                  tree = SceneTreeWidgetItem(self.objects_tree, item)
              if item.loaded and tree is not None:
                  tree.load()
              item.tree = tree
  
          # frame the viewer if we're just loading one abc file
          if len(self._load_files) == 1 and self._load_files[0].endswith(Scene.EXT):
              self.viewer.frame()
              
          self.viewer.setDisabled(False)
  
      def _wait(self):
          """
          Waits for the window to be drawn before proceeding.
          """
          app = QtGui.QApplication.instance()
          while QtGui.QApplication.instance().startingUp():
              pass
          while not self.isVisible():
              pass
  
      def open_file(self, filepath):
          """
          File loader
  
          :param filepath: file path to load, replaces session
          """
          self.set_load_files([filepath])
          self._start()
  
      def import_file(self, filepath, overrides=None):
          """
          File importer
  
          :param filepath: file path to import, adds to session
          :param overrides: parameter overrides dict
          """
          self.viewer.setDisabled(True)
  
          if filepath.endswith(Session.EXT):
              item = Session(filepath)
              num_items = len(item.items)
              io2gl(item, self.viewer)
          elif filepath.endswith(Scene.EXT):
              item = GLScene(filepath)
              num_items = 1
          
          # update item from params override
          if overrides:
              item.name = overrides.get("name", item.name)
              item.loaded = overrides.get("loaded", item.loaded)
              item.color = overrides.get("color", item.color)
              item.properties.update(overrides.get("properties"))
  
          self.session.add_item(item)
          COLORS = style.gen_colors(num_items)
  
          if self._overrides.get("mode") is not None:
              item.mode = self._overrides.get("mode")
  
          if item.type() == Session.type():
              item = SessionTreeWidgetItem(self.objects_tree, item)
          elif item.type() == GLScene.type():
              item = SceneTreeWidgetItem(self.objects_tree, item)
          if item.object.loaded:
              item.load()
  
          self.viewer.setDisabled(False)
  
      @make_clean
      def save_session(self, filepath=None):
          """
          Saves the current AbcView session to  a given filepath, or
          to the current filepath if filepath is None.
  
          :param filepath: save to filepath (passing no args or None
                           overrwrites current session file.
          """
          try:
              self.session.min_time = self.viewer.state.min_time
              self.session.max_time = self.viewer.state.max_time
              self.session.current_time = self.viewer.state.current_time
              self.session.frames_per_second = self.viewer.state.frames_per_second
              self.session.save(filepath)
          except Exception, e:
              traceback.print_exc()
              message("Error saving file:\n\n%s" % str(e))
  
      def load_script(self, script):
          """
          Loads and executes a python AbcView script.
  
          :param script: code or path to file containing python code.
          """
          log.debug("[%s.load_script] %s" % (self, script))
          if os.path.exists(script):
              self.console.runScript(script)
          else:
              self.console.setCommand(script)
              self.console.runCommand()
  
      ## event handlers
  
      def handle_play(self):
          """
          Playback handler, starts playing the viewer.
          """
          self.viewer.state.play()
  
      def handle_stop(self):
          """
          Viewer playback stop handler, stops the viewer playback.
          """
          self.viewer.state.stop()
  
      @make_dirty
      def handle_item_removed(self, item=None):
          self.viewer.state.stop()
          if item is None:
              item = self.objects_tree.selectedItems()[0]
          log.debug("[%s].handle_item_removed: %s" %(self, item))
          self.viewer.remove_scene(item.object)
          self.session.remove_item(item.object)
  
      def handle_scene_opened(self, scene):
          """
          Scene open signal handler.
          """
          frame_range = self.viewer.state.frame_range()
          self.time_slider.set_minimum(frame_range[0])
          self.time_slider.set_maximum(frame_range[1])
  
      def handle_viewer_error(self, msg):
          message(msg)
  
      def handle_time_slider_change(self, frame):
          """
          handles frame changes coming from time slider
  
          :param frame: frame number
          """
          self.viewer.state.current_frame = frame
  
      def handle_first_frame_change(self, frame):
          """
          handles first frame change from time slider
  
          :param frame: frame number
          """
          self.viewer.state.min_frame = frame
          if self.session.frames_per_second > 0:
              self.session.min_time = frame / self.session.frames_per_second
  
      def handle_last_frame_change(self, frame):
          """
          handles last frame change from time slider
  
          :param frame: frame number
          """
          self.viewer.state.max_frame = frame
          if self.session.frames_per_second > 0:
              self.session.max_time = frame / self.session.frames_per_second
  
      def handle_state_play_fwd(self):
          """
          viewer state play foward signal handler
          """
          self.time_slider.playing = True
  
      def handle_state_play_stop(self):
          """
          viewer state play stop signal handler
          """
          self.time_slider.playing = False
  
      def handle_update_frame(self, frame):
          """
          handles frame changes coming from viewer
          """
          self.time_slider.set_value(frame)
  
          # update the samples tree selected item
          if not self.properties_splitter.isHidden() and \
              self.samples_tree.topLevelItemCount() > 0:
              p = self.properties_tree.selected()
              ts = p.getTimeSampling()
              t = self.time_slider.value() \
                      / float(self.viewer.state.frames_per_second)
              index = ts.getNearIndex(t, len(p.samples))    
              item = self.samples_tree.topLevelItem(index)
              self.samples_tree.clearSelection()
              self.samples_tree.setItemSelected(item, True)
              self.samples_tree.scrollToItem(item)
    
      def handle_bad_scene(self, scene, frame):
          """
          Undrawabe scene signal handler.
  
          :param scene: GLScene
          """
          scene.tree.set_bad(True)
  
      @make_dirty 
      def handle_new_camera(self, camera):
          """
          handles adding a new camera to the session
  
          :param camera: GLCamera
          """
          log.debug("[%s.handle_new_camera] %s" % (self, camera))
          if camera.name != "interactive":
              self.session.add_camera(camera)
  
      @make_dirty
      def handle_set_camera(self, camera):
          """
          handles setting the camera name on the session
  
          :param camera: GLCamera
          """
          if camera.name != "interactive":
              self.session.set_camera(camera)
  
      @make_dirty
      def handle_view_camera(self, item):
          """
          object tree 'view through selected' menu handler
  
          :param item: CameraTreeWidgetItem
          """
          icamera = item.camera()
          if icamera.getName() not in [c.name for c in self.viewer.state.cameras]:
              camera = GLICamera(self.viewer, icamera)
              self.viewer.add_camera(camera)
              self.viewer.set_camera(camera.name)
          else:
              self.viewer.set_camera(icamera.getName())
  
      def handle_scene_selected(self, scene):
          """
          GLWidget scene selected handler.
  
          :param scene: GLScene
          """
          log.debug("[%s.handle_scene_selected] %s" % (self, scene))
          self.objects_tree.clearSelection()
          if scene:
              scene.tree.treeWidget().scrollToItem(scene.tree, 
                               QtGui.QAbstractItemView.PositionAtCenter)
              scene.tree.treeWidget().setItemSelected(scene.tree, True)
              self.handle_object_clicked(scene.tree)
  
      @wait
      def handle_object_selected(self, name):
          """
          GLWidget object selected handler.
  
          :param name: name of object
          """
          log.debug("[%s.handle_object_selected] %s" %(self, name))
          self.objects_tree.find(str(name.toAscii()))
  
      @wait
      def find(self, name):
          """
          searches for objects in the tree matching name
          """
          self.setFocus()
          self.objects_tree.find(name)
  
      @wait
      def handle_find(self, text=None):
          """
          handles input from the search box
          """
          if text is None:
              text = self.find_line_edit.text()
          if text:
              self.find(str(text.toAscii()))
  
      def handle_object_selection(self):
          for scene in self.viewer.state.scenes:
              scene.selected = False
          self.viewer.updateGL()
  
      def handle_item_selected(self, item):
          """
          item click handler
  
          :param item: SceneTreeWidgetItem, SessionTreeWidgetItem
          """
          if type(item) == SceneTreeWidgetItem:
              item.object.selected = True
          self.viewer.updateGL()
  
      @wait
      def handle_item_loaded(self, item):
          """
          load checkbox click handler
  
          :param item: SceneTreeWidgetItem, SessionTreeWidgetItem
          """
          def load(item):
              if type(item) == SceneTreeWidgetItem:
                  self.viewer.add_scene(item.object)
              elif type(item) == SessionTreeWidgetItem:
                  for child in item.children():
                      load(child)
          log.debug("[%s.handle_item_loaded] %s" % (self, item.object))
          self.splash.setMessage("loading %s" % item.object.name)
          self.splash.updateProgress(self.splash.progress.value() + 1)
          load(item)
  
      @wait
      def handle_item_unloaded(self, item):
          """
          Item unloaded handler.
  
          :param item: ObjectTreeWidgetItem
          """
          self.viewer.remove_scene(item.object)
  
      def handle_object_clicked(self, item):
          """
          Object tree item clicked handler.
  
          :param item: ObjectTreeWidgetItem
          """
          self.samples_tree.clear()
          self.array_tree.clear()
          self.properties_tree.show_properties(item)
  
      @wait
      def handle_property_clicked(self, item):
          """
          Property tree item clicked handler.
  
          :param item: PropertyTreeWidgetItem
          """
          self.array_tree.clear()
          self.samples_tree.show_samples(item)
  
      @wait
      def handle_new(self):
          """
          File->New menu handler
          """
          self.clear()
          self.setWindowTitle(self.TITLE)
  
      @make_clean
      def handle_open(self):
          """
          File->Open menud handler
          """
          filepath = QtGui.QFileDialog.getOpenFileName(self, "Open File", 
                      os.getcwd(), ("Alembic Files (*.%s *.%s)" 
                          % (Scene.EXT, Session.EXT)))
          if filepath:
              self.clear()
              self.set_load_files([str(filepath.toAscii())])
              self._load()
  
      @make_dirty
      def handle_import(self):
          """
          File->Import menud handler
          """
          filepath = QtGui.QFileDialog.getOpenFileName(self, "Open File", 
                      os.getcwd(), ("Alembic Files (*.%s *.%s)" 
                          % (Scene.EXT, Session.EXT)))
          if filepath:
              self.import_file(str(filepath.toAscii()))
  
      @make_clean
      def handle_reload(self):
          """
          File->Reload menu handler, reloads session
          """
          self.clear()
          self._load()
  
      def handle_frame_all(self):
          """
          Frame all. 
          """
          log.debug("[%s.handle_frame_all]" % self)
          self.viewer.frame()
  
      def handle_frame_scene(self, item):
          """
          Scene level framing handler.
  
          :param item: SceneTreeWidgetItem
          """
          log.debug("[%s.handle_frame_scene] %s" % (self, item))
          bounds = item.object.bounds(self.viewer.state.current_time)
          self.viewer.frame(bounds)
  
      def handle_frame_object(self, item=None):
          """
          Object level framing handler. Called when a user types 'f' while
          the object tree widget has focus. 
  
          :param item: ObjectTreeWidgetItem
          """
          log.debug("[%s.handle_frame_object] %s" % (self, item))
          if item is None:
              obj = self.objects_tree.selected()
          else:
              obj = item.object
          if obj.getFullName() == "/":
              self.handle_frame_scene()
          md = obj.getMetaData()
          if alembic.AbcGeom.IPolyMesh.matches(md) or \
             alembic.AbcGeom.ISubD.matches(md):
              meshObj = alembic.AbcGeom.IPolyMesh(obj.getParent(), obj.getName())
              mesh = meshObj.getSchema()
              bounds_prop = mesh.getSelfBoundsProperty()
          elif alembic.AbcGeom.IXform.matches(md):
              meshObj = alembic.AbcGeom.IXform(obj.getParent(), obj.getName())
              mesh = meshObj.getSchema()
              bounds_prop = mesh.getChildBoundsProperty()
          else:
              return
  
          # get the final accumulated xform values
          xf = get_final_matrix(meshObj)
          #iss = alembic.Abc.ISampleSelector(0)
          if not bounds_prop.valid():
              message("Object has invalid or no bounds set.")
              return
  
          # get the bounds value from the bounds property
          ts = bounds_prop.getTimeSampling()
          index = ts.getNearIndex(self.viewer.state.current_time, 
                          bounds_prop.getNumSamples())
          bounds = bounds_prop.getValue(index) #iss)
          log.debug("bounds at index %s: %s" % (index, bounds))
    
          #TODO: apply local transforms when framing
          if 0:
              min = bounds.min()
              max = bounds.max()
              item = self.objects_tree.selectedItems()[0]
              scene = item.scene().object
              if scene.properties.get("translate"):
                  max = max * imath.V3d(*scene.translate)
                  min = min * imath.V3d(*scene.translate)
              if scene.properties.get("scale"):
                  max = max * imath.V3d(*scene.scale)
                  min = min * imath.V3d(*scene.scale)
              bounds = imath.Box3d(min, max)
              
          self.viewer.frame(bounds * xf)
  
      def handle_save(self):
          if not self.session.filepath:
              self.handle_save_as()
          else:
              self.save_session()
  
      def handle_save_as(self):
          filepath = QtGui.QFileDialog.getSaveFileName(self, "Save File", 
                      os.getcwd(), ("Alembic Files (*.%s)" % Session.EXT))
          if filepath:
              self.save_session(str(filepath.toAscii()))
  
      ## base class overrides
  
      def resizeEvent(self, event):
          self.splash.move((event.size().width() / 2.0) - (self.splash.width() / 2.0), 
                           (event.size().height() / 2.0) - (self.splash.height() / 2.0))
          super(AbcView, self).resizeEvent(event)
  
      def keyPressEvent(self, event):
          """
          Handles key press events for the main application. If nothing
          matches, it defers to the viewer.
          """
          if event.key() == QtCore.Qt.Key_F:
              if len(self.objects_tree.selectedItems()) == 0:
                  self.handle_frame_all()
              else:
                  item = self.objects_tree.selectedItems()[0]
                  if type(item) == SceneTreeWidgetItem:
                      self.handle_frame_scene(item)
                  elif type(item) == ObjectTreeWidgetItem:
                      self.handle_frame_object(item)
                  else:
                      log.debug("Can't frame this object: %s" % item)
          elif event.key() == QtCore.Qt.Key_Space:
              if self.time_slider.playing:
                  self.handle_stop()
              else:
                  self.handle_play()
          elif event.key() == QtCore.Qt.Key_Backspace:
              item = self.objects_tree.selectedItems()[0]
              self.handle_item_removed(item)
          else:
              self.viewer.keyPressEvent(event)
  
      def closeEvent(self, event):
          if self.session and self.session.is_dirty():
              resp = self.confirm_close("This session has changed.")
              if resp == QtGui.QMessageBox.Cancel:
                  event.ignore()
                  return
              elif resp == QtGui.QMessageBox.Save:
                  self.handle_save()
          super(AbcView, self).closeEvent(event)
  
  class App(QtGui.QApplication):
      """
      QApplication subclass that emits a "startup" signal after
      the event loop has started. This may trigger deferred
      file loading, among other things.
      """
      signal_starting_up = QtCore.pyqtSignal()
  
      def __init__(self, args):
          super(App, self).__init__(args)
          # trigger the callback after the main event loop starts
          QtCore.QTimer.singleShot(1, self.starting)
  
      def starting(self):
          """
          Callback function for Timer timeout.
          """
          self.signal_starting_up.emit()
  
  def version_check():
      """
      Validates that alembic and alembicgl can be imported and
      that they meet the minimum requires versions.
  
      Current minimum version requirements for Alembic are 1.5.0.
  
      :return: 1 or 0
      """
      # Alembic minimum version requirement
      MAJOR = 1
      MINOR = 5
      MAINT = 0
  
      # validate the Alembic version
      version = alembic.Abc.GetLibraryVersionShort()
      major, minor, maint = version.split('.')
      if int(major) >= MAJOR and int(minor) >= MINOR:
          return 1
      else:
          print "%s %s requires Alembic %s.%s.%s or greater" \
                  % (config.__prog__, config.__version__, \
                     MAJOR, MINOR, MAINT)
          return 0
  
  def create_app(files = None,
             first_frame = None,
             last_frame = None,
             current_frame = None,
             fps = 24.0,
             script = None,
             bounds = False,
             mode = None,
             review = False,
             reset = False,
             verbose = False
            ):
      """
      Creates a new instance of an :py:class:`.AbcView` application.
      
      :param files: list of files to load
      :param first_frame: set first frame
      :param last_frame: set last frame
      :param current_frame: set current frame
      :param fps: frames per second (default 24)
      :param script: python script to load
      :param bounds: force scene bounding box mode
      :param mode: default object draw mode
      :param review: use review settings
      :param reset: reset layout settings
      :param verbose: verbose standard out
      :return: exit code
      """
      assert version_check()
      assert fps > 0.0, "fps must be greater than 0"
  
      # create application and widget
      app = App(sys.argv)
      win = AbcView()
      
      # verbosity
      if verbose:
          log.setLevel(logging.DEBUG)
  
      # settings
      if reset:
          win.reset_settings()
      elif review:
          win.review_settings()
      else:
          win.load_settings()
   
      # show widgets (Qt oddity this is done before event loop)
      win.show()
      win.raise_()
  
      # force scene bounds
      if bounds:
          win.set_default_mode(Mode.OFF)
          win.viewer.camera.mode = Mode.OFF
          win.viewer.camera.draw_bounds = 1
      elif mode is not None:
          win.set_default_mode(mode)
  
      # defer file loading until event loop starts
      win.set_load_files(files)
  
      # set frame range overrides
      if first_frame is not None:
          win.set_first_frame(first_frame)
      if last_frame is not None:
          win.set_last_frame(last_frame)
      if current_frame is not None:
          win.set_current_frame(current_frame)
  
      # execute some python
      if script:
          win.load_script(script)
  
      # start event loop
      return app.exec_()
  