def __init__(self, recipe, loader, app):

        self.state = self.STATE_READY

        self.app = app
        self.recipe = recipe
        self.loader = loader

        _id = self._get_id

        sm = get_scene_manager()
        self.scene_node = sm.createSceneNode()
        self.obtree = None
        self._actions = []

        self._user_navigated = False #: whether user moved around
            # in the viewport
        self._next_view_adjust = 1 #: adjust view (to fit screen)
            # after so many seconds

        # GUI widgets
        p = self.app.frame

        if not getattr(self, 'obtree_widget', None):
            self.obtree_widget = BuildTree(p, -1, (0, 0),
                                    wx.DefaultSize,
                                    wx.TR_DEFAULT_STYLE | wx.NO_BORDER,
                                    on_item_select=self.on_obtree_item_select)
            self.obtree_widget.Hide()

        if not getattr(self, 'xpath_widget', None):
            self.xpath_widget = XpathWidget(p)
            self.xpath_widget.Hide()

        if not getattr(self, 'conf_widget', None):
            self.gen_widget = GeneratorsWidget(p, self)
            self.mod_widget = ModificationsWidget(p, self)

            self.conf_widget = ConfWidget(p, self)
            self.conf_widget.Hide()

        # camera oriented members
        self.center_node = sm.createSceneNode()
        self.camera_node = self.center_node.createChildSceneNode()
        self.camera_node.lookAt((0, 0, 0), ogre.Node.TS_LOCAL)

        camera = sm.createCamera(_id('camera'))
        camera.nearClipDistance = .1
        camera.setAutoAspectRatio(True)
        self.camera_node.attachObject(camera)
        self.camera = camera

        # camera light
        light = sm.createLight('light_%x' % id(self))
        light.setType(ogre.Light.LT_POINT)
        light.setPosition((0, 0, 0))
        light.setSpecularColour((0.9, 0.9, 0.9))
        light.setDiffuseColour((0.7, 0.7, 0.7))
        self.light = light
        self.camera_node.attachObject(light)
        self.light.setVisible(False)

        # 3D navigation oriented members
        self.navigation_mode = self.DEFAULT_NAVIGATION_MODE
        self.move_scale = 0.0
        self.move_speed = 3.0
        self.num_moves = 0
        self.acceleration = 3
        self.last_time = time.time()

        self.x_start = 0
        self.y_start = 0
        self._click = False
        self._ray_scene_query = sm.createRayQuery(ogre.Ray())
        self._selection = set()
        self._sel_xpath = None
        self._sel_geom = None
        self._sel_xpaths = []
class Document:
    FLY_MODE = 0
    EXAMINE_MODE = 1
    NAVIGATION_MODES = [FLY_MODE, EXAMINE_MODE]
    DEFAULT_NAVIGATION_MODE = EXAMINE_MODE

    STATE_READY = 0
    STATE_RUNNING = 1
    STATE_PAUSED = 2
    STATE_COMPLETED = 3

    @logmt
    def __init__(self, recipe, loader, app):

        self.state = self.STATE_READY

        self.app = app
        self.recipe = recipe
        self.loader = loader

        _id = self._get_id

        sm = get_scene_manager()
        self.scene_node = sm.createSceneNode()
        self.obtree = None
        self._actions = []

        self._user_navigated = False #: whether user moved around
            # in the viewport
        self._next_view_adjust = 1 #: adjust view (to fit screen)
            # after so many seconds

        # GUI widgets
        p = self.app.frame

        if not getattr(self, 'obtree_widget', None):
            self.obtree_widget = BuildTree(p, -1, (0, 0),
                                    wx.DefaultSize,
                                    wx.TR_DEFAULT_STYLE | wx.NO_BORDER,
                                    on_item_select=self.on_obtree_item_select)
            self.obtree_widget.Hide()

        if not getattr(self, 'xpath_widget', None):
            self.xpath_widget = XpathWidget(p)
            self.xpath_widget.Hide()

        if not getattr(self, 'conf_widget', None):
            self.gen_widget = GeneratorsWidget(p, self)
            self.mod_widget = ModificationsWidget(p, self)

            self.conf_widget = ConfWidget(p, self)
            self.conf_widget.Hide()

        # camera oriented members
        self.center_node = sm.createSceneNode()
        self.camera_node = self.center_node.createChildSceneNode()
        self.camera_node.lookAt((0, 0, 0), ogre.Node.TS_LOCAL)

        camera = sm.createCamera(_id('camera'))
        camera.nearClipDistance = .1
        camera.setAutoAspectRatio(True)
        self.camera_node.attachObject(camera)
        self.camera = camera

        # camera light
        light = sm.createLight('light_%x' % id(self))
        light.setType(ogre.Light.LT_POINT)
        light.setPosition((0, 0, 0))
        light.setSpecularColour((0.9, 0.9, 0.9))
        light.setDiffuseColour((0.7, 0.7, 0.7))
        self.light = light
        self.camera_node.attachObject(light)
        self.light.setVisible(False)

        # 3D navigation oriented members
        self.navigation_mode = self.DEFAULT_NAVIGATION_MODE
        self.move_scale = 0.0
        self.move_speed = 3.0
        self.num_moves = 0
        self.acceleration = 3
        self.last_time = time.time()

        self.x_start = 0
        self.y_start = 0
        self._click = False
        self._ray_scene_query = sm.createRayQuery(ogre.Ray())
        self._selection = set()
        self._sel_xpath = None
        self._sel_geom = None
        self._sel_xpaths = []

    @logmt
    def get_display_name(self):
        name, location, _id, version = self.recipe.generator
        _id = _id.split('.')
        _id = '.'.join(_id[-2:])
        return _id

    @logmt
    def get_properties(self):
        name, location, _id, version = self.recipe.generator

        properties = [
                        ('name', self.get_display_name()),
                        ('generator_id', _id),
                        ('location', location),
                        ('version', version)
                     ]

        return properties

    @logmt
    def on_root_gen_created(self):
        self.obtree_widget.set_build_tree(self.obtree)
        rg = self.obtree.root_gen
        self.conf_widget.update_panes(rg, 'gen', '//%s' % rg.generator.IDENT.id)
    
    @logmt
    def on_root_gen_destroyed(self):
        self.obtree_widget.clear_build_tree()

    def on_time_elapsed(self, time_elapsed):
        self._next_view_adjust -= 1

        if self._next_view_adjust <= 0 and not self._user_navigated:
            self._actions.append(lambda: self._set_view('perspective'))
            self._actions.append(lambda: self._fit_to_view())
            self._next_view_adjust = 10

    @logmt
    def _update_recipe(self, loader=None):
        loader = loader or self.loader

        old_loader = set_loader(self.loader)

        try:
            self.recipe.update()
        finally:
            set_loader(old_loader)

        self.loader = loader

    @logmt
    def run(self):
        if self.state != self.STATE_READY:
            raise PIDEException('not in ready state. please reset')

        self._update_recipe()

        gen_class = self.recipe.make_generator(self.recipe._root_generator)
        self.obtree = BuildTreeOverlay(gen_class, self)
        self.obtree.build()

        self.state = self.STATE_RUNNING

    @logmt
    def pause(self):
        if self.state != self.STATE_RUNNING:
            raise PIDEException('not running')

        self.obtree.pause()
        self.state = self.STATE_PAUSED

    @logmt
    def resume(self):
        if self.state != self.STATE_PAUSED:
            raise PIDEException('not paused')

        self.obtree.resume()
        self.state = self.STATE_RUNNING

    @logmt
    def stop(self):
        if self.state not in (self.STATE_PAUSED,
                              self.STATE_RUNNING):
            return

        self.obtree.stop()
        self.state = self.STATE_COMPLETED

    @logmt
    def rebuild(self, gen_infos, on_first_gen_created=None):

        if self.state == self.STATE_READY:
            raise PIDEException('not in required state. cannot reload')

        if self.state in (self.STATE_PAUSED,
                          self.STATE_RUNNING):
            self.stop()

        self._update_recipe()
        self.state = self.STATE_RUNNING

        self.obtree.rebuild(gen_infos, on_first_gen_created)

    @logmt
    def export_collada(self):
        if self.state == self.STATE_RUNNING:
            self.pause()

        # FIXME: complete this implementation

    @logmt
    def on_build_completed(self):
        self.state = self.STATE_COMPLETED
        self.app.on_document_done(self)

        obt = self.obtree_widget
        if obt:
            obt.Expand(obt.GetRootItem())

        if not self._user_navigated and self._actions is not None:
            self._actions.append(lambda: self._set_view('perspective'))
            self._actions.append(lambda: self._fit_to_view())

    @logmt
    def cleanup(self, preserve_gui_widgets=False):

        if self.state in (self.STATE_RUNNING,
                          self.STATE_PAUSED):
            self.stop()

        sm = get_scene_manager()

        if self.obtree:
            self.obtree.cleanup()
            self.obtree = None
            self.obtree_widget.clear_build_tree()

        parent_node = self.scene_node.getParent()
        if parent_node:
            parent_node.removeChild(self.scene_node)

        sm.destroySceneNode(self.scene_node)

        self.camera_node.detachObject(self.camera)
        sm.destroyCamera(self.camera)
        self.camera_node.detachObject(self.light)
        sm.destroyLight(self.light)

        self.center_node.removeChild(self.camera_node)
        sm.destroySceneNode(self.camera_node)
        sm.destroySceneNode(self.center_node)

        # cleanup GUI widgets!
        if preserve_gui_widgets:
            self.obtree_widget.DeleteAllItems()

        else:
            for widget_name in ('obtree_widget',
                                'xpath_widget', 'conf_widget'):
                widget = getattr(self, widget_name)
                widget.Destroy()
                setattr(self, widget_name, None)

        self._actions = []
        self.state = None

    @logmt
    def reset(self):
        self.cleanup(preserve_gui_widgets=True)
        self.__init__(self.recipe, self.loader, self.app)

    @logmt
    def save(self):
        if not self.recipe.package_dir or not self.recipe.fpath:
            return self.save_as()

        else:
            return self.recipe.save()

    @logmt
    def save_as(self):
        pdir = self.recipe.package_dir
        fpath = self.recipe.fpath

        dlg = SaveAsDialog(self.app.frame, -1)
        dlg.CenterOnParent()
        response = dlg.ShowModal()

        if response == wx.ID_CANCEL:
            return

        pdir = dlg.package_dir.GetValue()
        fpath = dlg.fpath.GetValue()

        self.recipe.save(package_dir=pdir, fpath=fpath)

        _pdir = pdir.rsplit(os.path.sep, 1)[-1]
        _fpath = fpath.rsplit('.')[0]
        self.app.frame.change_tabname(_pdir + '.' + _fpath)

        return pdir, fpath

    @logmt
    def reload(self):
        '''
        Reload code.
        '''
        self._update_recipe(Loader())

    def _get_id(self, name):
        return '%s_%x' % (name, id(self))

    def _camera_lookat(self, point=None):
        if not point:
            bbox = self.obtree.get_bounding_box()
            point = bbox.getCenter()

        if self.navigation_mode == self.EXAMINE_MODE:
            self.camera_node.lookAt(point, ogre.Node.TS_LOCAL)
        else:
            self.camera.lookAt(point)

    def _place_light(self):
        if self.navigation_mode != self.FLY_MODE:
            return

        self.light.setPosition(self.camera.getPosition())

    def _move_camera(self, vector):
        if self.navigation_mode == self.EXAMINE_MODE:
            self.camera_node.translate(vector)

        else:
            self.camera.moveRelative(vector)

        self._place_light()

    @logmt
    def set_navigation_mode(self, mode):
        mode = self.EXAMINE_MODE if mode == 'examine' else self.FLY_MODE
        self._actions.append(lambda: self._set_navigation_mode(mode))

    @logmt
    def toggle_navigation_mode(self):
        mode = self.FLY_MODE if self.navigation_mode == self.EXAMINE_MODE\
                                  else self.EXAMINE_MODE
        self._actions.append(lambda: self._set_navigation_mode(mode))

        return mode

    @logmt
    def _set_navigation_mode(self, mode):
        self._user_navigated = True

        self.center_node.resetOrientation()
        self.camera.setOrientation(ogre.Quaternion())
        self.camera.setPosition((0, 0, 0))
        self.camera_node.detachAllObjects()
        self.camera_node.setPosition((0, 0, 0))
        self.light.setPosition((0, 0, 0))

        if mode == self.EXAMINE_MODE:
            self.camera_node.attachObject(self.camera)
            self.camera_node.attachObject(self.light)

        self.navigation_mode = mode

    @logmt
    def set_view(self, view='perspective', delayed=True):
        self._user_navigated = True

        actions = []

        if view == 'fit':
            actions.append(lambda: self._fit_to_view())
        elif view == 'lookat':
            actions.append(lambda: self._camera_lookat())
        else:
            actions.append(lambda: self._set_view(view))
            actions.append(lambda: self._fit_to_view())

        if delayed:
            self._actions.extend(actions)
        else:
            for a in actions:
                a()

    @logmt
    def get_view(self):
        return self.navigation_mode

    @logmt
    def _set_view(self, view):

        if self.navigation_mode == self.EXAMINE_MODE:
            c = self.center_node
        else:
            c = self.camera

        if view == 'perspective':
            c.setOrientation(ogre.Quaternion(math.pi/4, (-1, 1, 0)))

        elif view == 'top':
            c.setOrientation(ogre.Quaternion(-math.pi/2, (1, 0, 0)))

        elif view == 'bottom':
            c.setOrientation(ogre.Quaternion(math.pi/2, (1, 0, 0)))

        elif view == 'right':
            c.setOrientation(ogre.Quaternion(math.pi/2, (0, 1, 0)))

        elif view == 'left':
            c.setOrientation(ogre.Quaternion(-math.pi/2, (0, 1, 0)))

        elif view == 'back':
            c.setOrientation(ogre.Quaternion(math.pi, (0, 1, 0)))

        else:
            # front is default
            c.setOrientation(ogre.Quaternion())

    @logmt
    def _fit_to_view(self, bbox=None):

        if bbox is None:
            if not self.obtree:
                return
            bbox = self.obtree.get_bounding_box()

        # http://www.ogre3d.org/forums/viewtopic.php?f=5&t=49611
        near_plane = self.camera.getNearClipDistance()
        theta = self.camera.getFOVy() / 2.

        aspect_ratio = self.camera.getAspectRatio()
        if aspect_ratio < 1.0:
            theta *= aspect_ratio

        sv = bbox.getSize()
        size = max(sv.x, sv.y, sv.z)
        theta = theta.valueRadians()
        distance = (size / math.sin(theta)) + near_plane
        distance /= 2.
        distance = distance + distance / 10.

        if self.navigation_mode == self.EXAMINE_MODE:
            self.camera_node.setPosition((0, 0, 0))
            self.center_node.setPosition(bbox.getCenter())
        else:
            self.camera.setPosition(bbox.getCenter())

        self._move_camera((0, 0, distance))
        self._place_light()

    def on_key_down(self, event):
        is_examine_mode = self.navigation_mode == self.EXAMINE_MODE

        k = event.m_keyCode
        t = ogre.Vector3().ZERO

        amount = self.move_scale
        amount = amount + amount * (self.num_moves / self.acceleration)

        if k in (wx.WXK_LEFT, ord('A')):
            if not is_examine_mode:
                t.x = -amount

        elif k in (wx.WXK_RIGHT, ord('D')):
            if not is_examine_mode:
                t.x = amount

        elif k in (wx.WXK_UP, ord('W')):
            t.z =  -amount

        elif k in (wx.WXK_DOWN, ord('S')):
            t.z =  amount

        else:
            return

        self._user_navigated = True

        if event.ShiftDown():
            self.num_moves += 1
        else:
            self.num_moves = 0

        self._move_camera(t)

    @logmt
    def _cast_ray(self, ray):
        '''
        http://www.ogre3d.org/addonforums/viewtopic.php?f=3&t=11621
        '''

        sm = get_scene_manager()

        # variable to hold resulting entity
        closest_entity = None

        # variable to hold point of hit
        closest_result = None

        # create a query object
        ray_query = sm.createRayQuery(ray)
        ray_query.setSortByDistance(True)

        # execute the query, returns a vector of hits
        ray_query.execute(ray_query)
        result = ray_query.getLastResults()

        if len(result) == 0:
            # raycast did not hit an objects bounding box
            return None;

        # at this point we have raycast to a series of different objects
        # bounding boxes. we need to test these different objects to see
        # which is the first polygon hit. there are some minor
        # optimizations (distance based) that mean we wont have to
        # check all of the objects most of the time, but the worst case
        # scenario is that we need to test every triangle of every object.
        closest_dist = -1.0

        for item in result:

            # stop checking if we have found a raycast hit that is closer
            # than all remaining entities
            if closest_dist >= 0.0 and closest_dist < item.distance:
                break

            # only check this result if its a hit against an entity
            if not item.movable:
                continue

            if item.movable.getMovableType() != "Entity":
                continue

            # get the entity to check
            entity = sm.getEntity(item.movable.getName())

            # get the buffers from the mesh
            buffers = self._get_mesh_info(entity.getMesh())
            vertex_buffer = buffers[0]
            index_buffer = buffers[1]

            # get the world position, orientation (as a Matrix) and scale
            position = entity.getParentNode()._getDerivedPosition()
            orientation = entity.getParentNode()._getDerivedOrientation()
            mat_orient = ogre.Matrix3()
            orientation.ToRotationMatrix(mat_orient)
            scale = entity.getParentNode()._getDerivedScale()

            # Now test for hitting individual triangles on the mesh
            new_closest = False

            # get the positions of the vertices from the vertex buffer
            # three vertices each form a triangle
            triangle = []
            i = 1

            for vertex_num in index_buffer:
                start = vertex_num * 8

                pt = ogre.Vector3(vertex_buffer[start],
                                  vertex_buffer[start + 1],
                                  vertex_buffer[start + 2])

                # factor in the world position, orientation and scale
                pt = (mat_orient * (pt * scale)) + position
                triangle.append(pt)

                if i % 3 == 0:
                    # check for a hit against this triangle
                    hit = ogre.Math.intersects(ray,
                            triangle[0], triangle[1], triangle[2],
                            True, False)

                    # if it was a hit check if its the closest
                    if hit.first:
                        if closest_dist < 0.0 or hit.second < closest_dist:
                            # this is the closest so far, save it off
                            closest_dist = hit.second
                            new_closest = True

                    # reset the triangle
                    triangle = []

                i = i + 1

            # if we found a new closest raycast for this object, update the
            # closest_result and closest_entity before moving on to the next
            # object.
            if new_closest:
                closest_entity = entity
                closest_result = ray.getPoint(closest_dist)

        #destroy the query
        sm.destroyQuery(ray_query)

        # return the result
        return [closest_entity, closest_result]

    def _get_mesh_info(self, mesh):
        '''
        http://www.ogre3d.org/addonforums/viewtopic.php?f=3&t=11621
        '''

        added_shared = False

        current_offset = 0
        shared_offset = 0
        next_offset = 0
        index_offset = 0

        vertex_count = 0
        index_count = 0

        # Calculate how many vertices and indices we're going to need
        num_sub_meshes = mesh.getNumSubMeshes()
        for i in range(0, num_sub_meshes):
            submesh = mesh.getSubMesh(i)

            # We only need to add the shared vertices once
            if submesh.useSharedVertices:
                if not added_shared:
                    vertex_count += mesh.sharedVertexData.vertexCount
                    added_shared = True
            else:
                vertex_count += submesh.vertexData.vertexCount

            # Add the indices
            index_count += submesh.indexData.indexCount

        added_shared = False;

        # Run through the submeshes again, adding the data into the arrays
        for i in range(0, num_sub_meshes):
            submesh = mesh.getSubMesh(i)

            vertexData = None
            if submesh.useSharedVertices:
                vertexData =  mesh.sharedVertexData
            else:
                vertexData = submesh.vertexData

            if not submesh.useSharedVertices or\
                (submesh.useSharedVertices and not added_shared):

                if submesh.useSharedVertices:
                    added_shared = True
                    shared_offset = current_offset

                # retrieve index buffer for this submesh
                indexData = submesh.indexData
                ibuf = indexData.indexBuffer
                pointer = ibuf.lock(ogre.HardwareBuffer.HBL_READ_ONLY)
                index_buffer = None

                if bool(ibuf.getType() == ogre.HardwareIndexBuffer.IT_32BIT):
                    index_buffer = ogre.getUint32(pointer, index_count)
                else:
                    index_buffer = ogre.getUint16(pointer, index_count)
                ibuf.unlock()


                # retrieve vertex buffer for this submesh
                posElem = vertexData.vertexDeclaration\
                                .findElementBySemantic(ogre.VES_POSITION)
                vbuf = vertexData.vertexBufferBinding\
                                .getBuffer(posElem.getSource())
                pointer = vbuf.lock(ogre.HardwareBuffer.HBL_READ_ONLY)

                # There are 8 float entries for each vertex in the buffer
                # 3 for position, 3 for normal, 2 for texture coordinate.
                # We only need the position.
                vertex_buffer = ogre.getFloat(pointer, vertex_count * 8)
                vbuf.unlock()

        return [vertex_buffer, index_buffer]

    def _add_to_selection(self, gen_info):
        self._selection.add(gen_info)
        gen_info.highlight()

    def _clear_selection(self):
        for gen_info in self._selection:
            gen_info.unhighlight()

        if self._sel_geom:
            self._sel_geom.unhighlight()
            self._sel_geom = None

        self._selection.clear()

    @logmt
    def _on_manual_selection(self):
        self._sel_xpath = None
        self._sel_xpaths = []

        self._sel_xpaths = self.get_xpath_suggestions()
        if self._sel_xpaths:
            self._sel_xpath = self._sel_xpaths[0]

        self._on_selection_changed('gen')

    @logmt
    def _on_selection_changed(self, sel_type):
        self.update_conf_widget(sel_type)
        self.xpath_widget.update(self._sel_xpaths, self)

    @logmt
    def update_conf_widget(self, sel_type):
        if sel_type == 'geom':
            self.conf_widget.update_panes(self._sel_geom, sel_type,
                                          self._sel_xpath[0])

        elif sel_type == 'class':
            xpath, desc, _class = self._sel_xpath
            self.conf_widget.update_panes(_class, 'class',
                                          self._sel_xpath[0])

        else:
            gen = list(self._selection)[0]
            self.conf_widget.update_panes(gen, sel_type,
                                          self._sel_xpath[0])

    @logmt
    def pick_generator(self, x, y, width, height):
        mouse_ray = self.camera.getCameraToViewportRay(x / float(width),
                                                       y / float(height))

        data = self._cast_ray(mouse_ray)
        if not data:
            return

        entity, point = data
        if not entity:
            return

        gen_info = self.obtree.pick_entity(entity)
        return gen_info

    @logmt
    def select_xpath(self, xpath, description=''):
        self._clear_selection()
        self._sel_xpath = None

        gen_infos = self.query(xpath)
        if not gen_infos:
            return

        for g in gen_infos:
            self._add_to_selection(g)

        g = gen_infos[0]
        self._sel_xpath = (xpath, description, g.generator.__class__)

        sel_type = 'gen' if len(self._selection) == 1 else 'class'

        self.update_conf_widget(sel_type)

    @logmt
    def on_mouse_click(self, event):
        wnd = event.GetEventObject()
        width, height = wnd.GetClientSizeTuple()
        x, y = event.GetPosition()
        gen_info = self.pick_generator(x, y, width, height)

        if not event.ControlDown() or self._sel_geom:
            self._clear_selection()

        if not gen_info:
            return

        self._add_to_selection(gen_info)
#        self._on_manual_selection()

        self.obtree_widget.select_generator(gen_info)

    def on_mouse_event(self, event):
        if self.navigation_mode == self.EXAMINE_MODE:
            c = self.center_node
        else:
            c = self.camera

        rotation = event.GetWheelRotation()
        rotation = rotation * 10 if event.ControlDown() else rotation
        rotation = rotation / 10.0 if event.ShiftDown() else rotation / 100.0
        self._move_camera((0, 0, rotation))

        if event.LeftDown():
            self.x_start, self.y_start = event.GetPosition()
            self._user_navigated = True
            self._click = True

        if event.LeftUp():
            if self._click:
                self._click = False
                self.on_mouse_click(event)

        if event.Dragging() and event.LeftIsDown():
            self._click = False
            x,y = event.GetPosition()
            dx = x - self.x_start
            dy = y - self.y_start
            self.x_start, self.y_start = x, y

            c.yaw(ogre.Degree( -dx / 3.0))
            c.pitch(ogre.Degree( -dy / 3.0))
            self._user_navigated = True

    def on_frame_started(self, event):
        cur_time = time.time()
        t = cur_time - self.last_time
        self.last_time = cur_time

        self.move_scale = self.move_speed * t

        # build tree construction activity
        if self.obtree:
            self.obtree.on_frame_started(event)

        # perform delayed actions
        for index, action in enumerate(self._actions):

            if action is None:
                continue

            try:
                action()
            finally:
                self._actions[index] = None

        while self._actions and self._actions[0] is None:
            self._actions.pop(0)

    def on_frame_ended(self, event):
        if self.obtree:
            self.obtree.on_frame_ended(event)

    def on_obtree_item_select(self, node, node_type):
        if node_type == 'gen':
            self._clear_selection()
            self._add_to_selection(node)
            self._on_manual_selection()

        elif node_type == 'geom':
            self._clear_selection()
            self._sel_geom = node
            node.highlight()
            self._on_selection_changed('geom')

    def _get_class(self, location, _id, version=None):
        old_loader = set_loader(self.loader)

        try:
            _class = get_class(location, _id, version)
        finally:
            set_loader(old_loader)

        return _class

    def get_generators_map(self):
        generators = {}

        for name, location, _id, version in self.recipe.generators:
            _class = self._get_class(location, _id, version)
            generators[_class] = name

        return generators

    @logmt
    def get_xpath_suggestions(self, gen_infos=None):
        gen_infos = gen_infos or self._selection
        names = self.get_generators_map()
        xpaths = self.obtree.suggest_xpaths(gen_infos, names)
        return xpaths

    @logmt
    def query(self, xpaths):
        match_fn = self.recipe.make_matcher(xpaths)
        return self.obtree.query(match_fn)